diff --git a/all/pom.xml b/all/pom.xml index 8919bc6cfc5..6a47bcbd03c 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty diff --git a/buffer/pom.xml b/buffer/pom.xml index 7fbf1886823..919dca106ab 100644 --- a/buffer/pom.xml +++ b/buffer/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-buffer diff --git a/buffer/src/main/java/io/netty/buffer/AbstractWrappedByteBuf.java b/buffer/src/main/java/io/netty/buffer/AbstractWrappedByteBuf.java index e40c1d14f04..912a46d0188 100644 --- a/buffer/src/main/java/io/netty/buffer/AbstractWrappedByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/AbstractWrappedByteBuf.java @@ -32,7 +32,7 @@ protected AbstractWrappedByteBuf(ByteOrder endianness, int maxCapacity) { @Override public WrappedByteBuf capacity(int newCapacity) { - throw new ReadOnlyBufferException(); + throw new UnsupportedOperationException(); } @Override diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index e7dcb133e15..be330a9e64f 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -33,8 +33,8 @@ public final class ByteBufUtil { static { final char[] DIGITS = "0123456789abcdef".toCharArray(); for (int i = 0; i < 256; i ++) { - HEXDUMP_TABLE[(i << 1) + 0] = DIGITS[i >>> 4 & 0x0F]; - HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i >>> 0 & 0x0F]; + HEXDUMP_TABLE[ i << 1 ] = DIGITS[i >>> 4 & 0x0F]; + HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F]; } } diff --git a/buffer/src/main/java/io/netty/buffer/DefaultCompositeByteBuf.java b/buffer/src/main/java/io/netty/buffer/DefaultCompositeByteBuf.java index 646bf33d0df..8c79994b0eb 100644 --- a/buffer/src/main/java/io/netty/buffer/DefaultCompositeByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/DefaultCompositeByteBuf.java @@ -61,7 +61,8 @@ public DefaultCompositeByteBuf(int maxNumComponents, ByteBuf... buffers) { this.maxNumComponents = maxNumComponents; - addComponents(0, buffers); + addComponents0(0, buffers); + consolidateIfNeeded(); setIndex(0, capacity()); } @@ -74,30 +75,41 @@ public DefaultCompositeByteBuf(int maxNumComponents, Iterable buffers) } this.maxNumComponents = maxNumComponents; - addComponents(0, buffers); + addComponents0(0, buffers); + consolidateIfNeeded(); setIndex(0, capacity()); + } @Override public CompositeByteBuf addComponent(ByteBuf buffer) { - addComponent(components.size(), buffer); + addComponent0(components.size(), buffer); + consolidateIfNeeded(); return this; } @Override public CompositeByteBuf addComponents(ByteBuf... buffers) { - addComponents(components.size(), buffers); + addComponents0(components.size(), buffers); + consolidateIfNeeded(); return this; } @Override public CompositeByteBuf addComponents(Iterable buffers) { - addComponents(components.size(), buffers); + addComponents0(components.size(), buffers); + consolidateIfNeeded(); return this; } @Override public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { + addComponent0(cIndex, buffer); + consolidateIfNeeded(); + return this; + } + + private int addComponent0(int cIndex, ByteBuf buffer) { checkComponentIndex(cIndex); if (buffer == null) { @@ -107,34 +119,12 @@ public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { if (buffer instanceof Iterable) { @SuppressWarnings("unchecked") Iterable composite = (Iterable) buffer; - addComponents(cIndex, composite); - return this; + return addComponents0(cIndex, composite); } int readableBytes = buffer.readableBytes(); if (readableBytes == 0) { - return this; - } - - // Consolidate if the number of components will exceed the allowed maximum by the current - // operation. - final int numComponents = components.size(); - if (numComponents >= maxNumComponents) { - final int capacity = components.get(numComponents - 1).endOffset + readableBytes; - - ByteBuf consolidated = buffer.unsafe().newBuffer(capacity); - for (int i = 0; i < numComponents; i ++) { - ByteBuf b = components.get(i).buf; - consolidated.writeBytes(b); - b.unsafe().release(); - } - consolidated.writeBytes(buffer, buffer.readerIndex(), readableBytes); - - Component c = new Component(consolidated); - c.endOffset = c.length; - components.clear(); - components.add(c); - return this; + return cIndex; } // No need to consolidate - just add a component to the list. @@ -152,74 +142,33 @@ public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) { components.add(cIndex, c); updateComponentOffsets(cIndex); } - return this; + return cIndex; } - @Override public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) { + addComponents0(cIndex, buffers); + consolidateIfNeeded(); + return this; + } + + private int addComponents0(int cIndex, ByteBuf... buffers) { checkComponentIndex(cIndex); if (buffers == null) { throw new NullPointerException("buffers"); } - ByteBuf lastBuf = null; - int cnt = 0; + int readableBytes = 0; for (ByteBuf b: buffers) { if (b == null) { break; } - lastBuf = b; - cnt ++; readableBytes += b.readableBytes(); } if (readableBytes == 0) { - return this; - } - - // Consolidate if the number of components will exceed the maximum by this operation. - final int numComponents = components.size(); - if (numComponents + cnt >= maxNumComponents) { - final ByteBuf consolidated; - if (numComponents != 0) { - final int capacity = components.get(numComponents - 1).endOffset + readableBytes; - consolidated = lastBuf.unsafe().newBuffer(capacity); - for (int i = 0; i < cIndex; i ++) { - ByteBuf b = components.get(i).buf; - consolidated.writeBytes(b); - b.unsafe().release(); - } - - for (ByteBuf b: buffers) { - if (b == null) { - break; - } - consolidated.writeBytes(b, b.readerIndex(), b.readableBytes()); - } - - for (int i = cIndex; i < numComponents; i ++) { - ByteBuf b = components.get(i).buf; - consolidated.writeBytes(b); - b.unsafe().release(); - } - } else { - consolidated = lastBuf.unsafe().newBuffer(readableBytes); - for (ByteBuf b: buffers) { - if (b == null) { - break; - } - consolidated.writeBytes(b, b.readerIndex(), b.readableBytes()); - } - } - - Component c = new Component(consolidated); - c.endOffset = c.length; - components.clear(); - components.add(c); - updateComponentOffsets(0); - return this; + return cIndex; } // No need for consolidation @@ -227,21 +176,25 @@ public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) { if (b == null) { break; } - if (b.readable()) { - addComponent(cIndex ++, b); + cIndex = addComponent0(cIndex, b) + 1; int size = components.size(); if (cIndex > size) { - // was consolidated, so adjust index. #707 cIndex = size; } } } - return this; + return cIndex; } @Override public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { + addComponents0(cIndex, buffers); + consolidateIfNeeded(); + return this; + } + + private int addComponents0(int cIndex, Iterable buffers) { if (buffers == null) { throw new NullPointerException("buffers"); } @@ -252,8 +205,7 @@ public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { for (int i = 0; i < array.length; i ++) { array[i] = list.get(i).buf; } - addComponents(cIndex, array); - return this; + return addComponents0(cIndex, array); } if (buffers instanceof List) { @@ -262,8 +214,7 @@ public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { for (int i = 0; i < array.length; i ++) { array[i] = list.get(i); } - addComponents(cIndex, array); - return this; + return addComponents0(cIndex, array); } if (buffers instanceof Collection) { @@ -273,16 +224,39 @@ public CompositeByteBuf addComponents(int cIndex, Iterable buffers) { for (ByteBuf b: col) { array[i ++] = b; } - addComponents(cIndex, array); - return this; + return addComponents0(cIndex, array); } List list = new ArrayList(); for (ByteBuf b: buffers) { list.add(b); } - addComponents(cIndex, list.toArray(new ByteBuf[list.size()])); - return this; + return addComponents0(cIndex, list.toArray(new ByteBuf[list.size()])); + } + + + /** + * This should only be called as last operation from a method as this may adjust the underlying + * array of components and so affect the index etc. + */ + private void consolidateIfNeeded() { + // Consolidate if the number of components will exceed the allowed maximum by the current + // operation. + final int numComponents = components.size(); + if (numComponents > maxNumComponents) { + final int capacity = components.get(numComponents - 1).endOffset; + + ByteBuf consolidated = components.get(numComponents - 1).buf.unsafe().newBuffer(capacity); + for (int i = 0; i < numComponents; i ++) { + ByteBuf b = components.get(i).buf; + consolidated.writeBytes(b); + b.unsafe().release(); + } + Component c = new Component(consolidated); + c.endOffset = c.length; + components.clear(); + components.add(c); + } } private void checkComponentIndex(int cIndex) { @@ -1243,7 +1217,7 @@ public CompositeByteBuf discardReadBytes() { public String toString() { String result = super.toString(); result = result.substring(0, result.length() - 1); - return result + ", components=" + components.size() + ")"; + return result + ", components=" + components.size() + ')'; } private static final class Component { diff --git a/buffer/src/main/java/io/netty/buffer/DirectByteBuf.java b/buffer/src/main/java/io/netty/buffer/DirectByteBuf.java index 0c0072dc299..0273267f506 100644 --- a/buffer/src/main/java/io/netty/buffer/DirectByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/DirectByteBuf.java @@ -15,6 +15,8 @@ */ package io.netty.buffer; +import sun.misc.Cleaner; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -25,8 +27,6 @@ import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; -import sun.misc.Cleaner; - /** * A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)} * and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the @@ -208,8 +208,7 @@ public short getShort(int index) { @Override public int getUnsignedMedium(int index) { - return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | - (getByte(index + 2) & 0xff) << 0; + return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff; } @Override @@ -278,7 +277,7 @@ public ByteBuf setShort(int index, int value) { public ByteBuf setMedium(int index, int value) { setByte(index, (byte) (value >>> 16)); setByte(index + 1, (byte) (value >>> 8)); - setByte(index + 2, (byte) (value >>> 0)); + setByte(index + 2, (byte) value); return this; } diff --git a/buffer/src/main/java/io/netty/buffer/DuplicatedByteBuf.java b/buffer/src/main/java/io/netty/buffer/DuplicatedByteBuf.java index 809a02865d5..9118e42f259 100644 --- a/buffer/src/main/java/io/netty/buffer/DuplicatedByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/DuplicatedByteBuf.java @@ -108,11 +108,6 @@ public long getLong(int index) { return buffer.getLong(index); } - @Override - public ByteBuf duplicate() { - return new DuplicatedByteBuf(this); - } - @Override public ByteBuf copy(int index, int length) { return buffer.copy(index, length); diff --git a/buffer/src/main/java/io/netty/buffer/HeapByteBuf.java b/buffer/src/main/java/io/netty/buffer/HeapByteBuf.java index fc92fee7158..09dd6eca9d2 100644 --- a/buffer/src/main/java/io/netty/buffer/HeapByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/HeapByteBuf.java @@ -237,7 +237,7 @@ public short getShort(int index) { public int getUnsignedMedium(int index) { return (array[index] & 0xff) << 16 | (array[index + 1] & 0xff) << 8 | - (array[index + 2] & 0xff) << 0; + array[index + 2] & 0xff; } @Override @@ -245,7 +245,7 @@ public int getInt(int index) { return (array[index] & 0xff) << 24 | (array[index + 1] & 0xff) << 16 | (array[index + 2] & 0xff) << 8 | - (array[index + 3] & 0xff) << 0; + array[index + 3] & 0xff; } @Override @@ -257,13 +257,13 @@ public long getLong(int index) { ((long) array[index + 4] & 0xff) << 24 | ((long) array[index + 5] & 0xff) << 16 | ((long) array[index + 6] & 0xff) << 8 | - ((long) array[index + 7] & 0xff) << 0; + (long) array[index + 7] & 0xff; } @Override public ByteBuf setShort(int index, int value) { array[index] = (byte) (value >>> 8); - array[index + 1] = (byte) (value >>> 0); + array[index + 1] = (byte) value; return this; } @@ -271,7 +271,7 @@ public ByteBuf setShort(int index, int value) { public ByteBuf setMedium(int index, int value) { array[index] = (byte) (value >>> 16); array[index + 1] = (byte) (value >>> 8); - array[index + 2] = (byte) (value >>> 0); + array[index + 2] = (byte) value; return this; } @@ -280,7 +280,7 @@ public ByteBuf setInt(int index, int value) { array[index] = (byte) (value >>> 24); array[index + 1] = (byte) (value >>> 16); array[index + 2] = (byte) (value >>> 8); - array[index + 3] = (byte) (value >>> 0); + array[index + 3] = (byte) value; return this; } @@ -293,7 +293,7 @@ public ByteBuf setLong(int index, long value) { array[index + 4] = (byte) (value >>> 24); array[index + 5] = (byte) (value >>> 16); array[index + 6] = (byte) (value >>> 8); - array[index + 7] = (byte) (value >>> 0); + array[index + 7] = (byte) value; return this; } diff --git a/buffer/src/test/java/io/netty/buffer/AbstractCompositeChannelBufferTest.java b/buffer/src/test/java/io/netty/buffer/AbstractCompositeChannelBufferTest.java index b73513e358c..5929bbe6418 100644 --- a/buffer/src/test/java/io/netty/buffer/AbstractCompositeChannelBufferTest.java +++ b/buffer/src/test/java/io/netty/buffer/AbstractCompositeChannelBufferTest.java @@ -47,25 +47,25 @@ protected AbstractCompositeChannelBufferTest(ByteOrder order) { protected ByteBuf newBuffer(int length) { buffers = new ArrayList(); for (int i = 0; i < length + 45; i += 45) { - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[1])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[2])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[3])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[4])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[5])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[6])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[7])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[8])); - buffers.add(Unpooled.EMPTY_BUFFER); - buffers.add(Unpooled.wrappedBuffer(new byte[9])); - buffers.add(Unpooled.EMPTY_BUFFER); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[1])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[2])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[3])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[4])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[5])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[6])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[7])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[8])); + buffers.add(EMPTY_BUFFER); + buffers.add(wrappedBuffer(new byte[9])); + buffers.add(EMPTY_BUFFER); } buffer = Unpooled.wrappedBuffer( @@ -98,7 +98,7 @@ protected boolean discardReadBytesDoesNotMoveWritableBytes() { */ @Test public void testComponentAtOffset() { - CompositeByteBuf buf = (CompositeByteBuf) Unpooled.wrappedBuffer(new byte[] { 1, 2, 3, 4, 5 }, new byte[] {4, 5, 6, 7, 8, 9, 26}); + CompositeByteBuf buf = (CompositeByteBuf) wrappedBuffer(new byte[]{1, 2, 3, 4, 5}, new byte[]{4, 5, 6, 7, 8, 9, 26}); //Ensure that a random place will be fine assertEquals(buf.componentAtOffset(2).capacity(), 5); diff --git a/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java b/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java index a39e9157e96..90cd026abdb 100644 --- a/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java +++ b/buffer/src/test/java/io/netty/buffer/ChannelBuffersTest.java @@ -194,13 +194,13 @@ public void shouldReturnEmptyBufferWhenLengthIsZero() { @Test public void testCompare2() { assertTrue(ByteBufUtil.compare( - Unpooled.wrappedBuffer(new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}), - Unpooled.wrappedBuffer(new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00})) + wrappedBuffer(new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}), + wrappedBuffer(new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00})) > 0); assertTrue(ByteBufUtil.compare( - Unpooled.wrappedBuffer(new byte[]{(byte) 0xFF}), - Unpooled.wrappedBuffer(new byte[]{(byte) 0x00})) + wrappedBuffer(new byte[]{(byte) 0xFF}), + wrappedBuffer(new byte[]{(byte) 0x00})) > 0); } @@ -403,7 +403,7 @@ public void testUnmodifiableBuffer() throws Exception { @Test public void testWrapSingleInt() { - ByteBuf buffer = Unpooled.copyInt(42); + ByteBuf buffer = copyInt(42); assertEquals(4, buffer.capacity()); assertEquals(42, buffer.readInt()); assertFalse(buffer.readable()); @@ -411,7 +411,7 @@ public void testWrapSingleInt() { @Test public void testWrapInt() { - ByteBuf buffer = Unpooled.copyInt(1, 4); + ByteBuf buffer = copyInt(1, 4); assertEquals(8, buffer.capacity()); assertEquals(1, buffer.readInt()); assertEquals(4, buffer.readInt()); @@ -423,7 +423,7 @@ public void testWrapInt() { @Test public void testWrapSingleShort() { - ByteBuf buffer = Unpooled.copyShort(42); + ByteBuf buffer = copyShort(42); assertEquals(2, buffer.capacity()); assertEquals(42, buffer.readShort()); assertFalse(buffer.readable()); @@ -431,7 +431,7 @@ public void testWrapSingleShort() { @Test public void testWrapShortFromShortArray() { - ByteBuf buffer = Unpooled.copyShort(new short[] { 1, 4 }); + ByteBuf buffer = copyShort(new short[]{1, 4}); assertEquals(4, buffer.capacity()); assertEquals(1, buffer.readShort()); assertEquals(4, buffer.readShort()); @@ -443,7 +443,7 @@ public void testWrapShortFromShortArray() { @Test public void testWrapShortFromIntArray() { - ByteBuf buffer = Unpooled.copyShort(1, 4); + ByteBuf buffer = copyShort(1, 4); assertEquals(4, buffer.capacity()); assertEquals(1, buffer.readShort()); assertEquals(4, buffer.readShort()); @@ -455,7 +455,7 @@ public void testWrapShortFromIntArray() { @Test public void testWrapSingleMedium() { - ByteBuf buffer = Unpooled.copyMedium(42); + ByteBuf buffer = copyMedium(42); assertEquals(3, buffer.capacity()); assertEquals(42, buffer.readMedium()); assertFalse(buffer.readable()); @@ -463,7 +463,7 @@ public void testWrapSingleMedium() { @Test public void testWrapMedium() { - ByteBuf buffer = Unpooled.copyMedium(1, 4); + ByteBuf buffer = copyMedium(1, 4); assertEquals(6, buffer.capacity()); assertEquals(1, buffer.readMedium()); assertEquals(4, buffer.readMedium()); @@ -475,7 +475,7 @@ public void testWrapMedium() { @Test public void testWrapSingleLong() { - ByteBuf buffer = Unpooled.copyLong(42); + ByteBuf buffer = copyLong(42); assertEquals(8, buffer.capacity()); assertEquals(42, buffer.readLong()); assertFalse(buffer.readable()); @@ -483,7 +483,7 @@ public void testWrapSingleLong() { @Test public void testWrapLong() { - ByteBuf buffer = Unpooled.copyLong(1, 4); + ByteBuf buffer = copyLong(1, 4); assertEquals(16, buffer.capacity()); assertEquals(1, buffer.readLong()); assertEquals(4, buffer.readLong()); @@ -495,7 +495,7 @@ public void testWrapLong() { @Test public void testWrapSingleFloat() { - ByteBuf buffer = Unpooled.copyFloat(42); + ByteBuf buffer = copyFloat(42); assertEquals(4, buffer.capacity()); assertEquals(42, buffer.readFloat(), 0.01); assertFalse(buffer.readable()); @@ -503,7 +503,7 @@ public void testWrapSingleFloat() { @Test public void testWrapFloat() { - ByteBuf buffer = Unpooled.copyFloat(1, 4); + ByteBuf buffer = copyFloat(1, 4); assertEquals(8, buffer.capacity()); assertEquals(1, buffer.readFloat(), 0.01); assertEquals(4, buffer.readFloat(), 0.01); @@ -515,7 +515,7 @@ public void testWrapFloat() { @Test public void testWrapSingleDouble() { - ByteBuf buffer = Unpooled.copyDouble(42); + ByteBuf buffer = copyDouble(42); assertEquals(8, buffer.capacity()); assertEquals(42, buffer.readDouble(), 0.01); assertFalse(buffer.readable()); @@ -523,7 +523,7 @@ public void testWrapSingleDouble() { @Test public void testWrapDouble() { - ByteBuf buffer = Unpooled.copyDouble(1, 4); + ByteBuf buffer = copyDouble(1, 4); assertEquals(16, buffer.capacity()); assertEquals(1, buffer.readDouble(), 0.01); assertEquals(4, buffer.readDouble(), 0.01); @@ -535,7 +535,7 @@ public void testWrapDouble() { @Test public void testWrapBoolean() { - ByteBuf buffer = Unpooled.copyBoolean(true, false); + ByteBuf buffer = copyBoolean(true, false); assertEquals(2, buffer.capacity()); assertEquals(true, buffer.readBoolean()); assertEquals(false, buffer.readBoolean()); diff --git a/buffer/src/test/java/io/netty/buffer/ConsolidationTest.java b/buffer/src/test/java/io/netty/buffer/ConsolidationTest.java new file mode 100644 index 00000000000..57eb439cc13 --- /dev/null +++ b/buffer/src/test/java/io/netty/buffer/ConsolidationTest.java @@ -0,0 +1,57 @@ +/* + * 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.buffer; + +import static io.netty.buffer.Unpooled.wrappedBuffer; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Tests buffer consolidation + */ +public class ConsolidationTest { + + @Test + public void shouldWrapInSequence() { + ByteBuf currentBuffer = wrappedBuffer(wrappedBuffer("a".getBytes()), wrappedBuffer("=".getBytes())); + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("1".getBytes()), wrappedBuffer("&".getBytes())); + + String s = new String(currentBuffer.copy().array()); + assertEquals("a=1&", s); + } + + @Test + public void shouldConsolidationInSequence() { + ByteBuf currentBuffer = wrappedBuffer(wrappedBuffer("a".getBytes()), wrappedBuffer("=".getBytes())); + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("1".getBytes()), wrappedBuffer("&".getBytes())); + + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("b".getBytes()), wrappedBuffer("=".getBytes())); + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("2".getBytes()), wrappedBuffer("&".getBytes())); + + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("c".getBytes()), wrappedBuffer("=".getBytes())); + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("3".getBytes()), wrappedBuffer("&".getBytes())); + + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("d".getBytes()), wrappedBuffer("=".getBytes())); + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("4".getBytes()), wrappedBuffer("&".getBytes())); + + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("e".getBytes()), wrappedBuffer("=".getBytes())); + currentBuffer = wrappedBuffer(currentBuffer, wrappedBuffer("5".getBytes()), wrappedBuffer("&".getBytes())); + + String s = new String(currentBuffer.copy().array()); + assertEquals("a=1&b=2&c=3&d=4&e=5&", s); + } +} diff --git a/buffer/src/test/java/io/netty/buffer/ReadOnlyChannelBufferTest.java b/buffer/src/test/java/io/netty/buffer/ReadOnlyChannelBufferTest.java index 2182502734c..6e65cd12ab4 100644 --- a/buffer/src/test/java/io/netty/buffer/ReadOnlyChannelBufferTest.java +++ b/buffer/src/test/java/io/netty/buffer/ReadOnlyChannelBufferTest.java @@ -40,26 +40,26 @@ public void shouldNotAllowNullInConstructor() { @Test public void testUnmodifiableBuffer() { - assertTrue(Unpooled.unmodifiableBuffer(Unpooled.buffer(1)) instanceof ReadOnlyByteBuf); + assertTrue(unmodifiableBuffer(buffer(1)) instanceof ReadOnlyByteBuf); } @Test public void testUnwrap() { - ByteBuf buf = Unpooled.buffer(1); + ByteBuf buf = buffer(1); assertSame(buf, ((WrappedByteBuf) Unpooled.unmodifiableBuffer(buf)).unwrap()); } @Test public void shouldHaveSameByteOrder() { - ByteBuf buf = Unpooled.buffer(1); - assertSame(Unpooled.BIG_ENDIAN, Unpooled.unmodifiableBuffer(buf).order()); + ByteBuf buf = buffer(1); + assertSame(BIG_ENDIAN, Unpooled.unmodifiableBuffer(buf).order()); buf = buf.order(LITTLE_ENDIAN); - assertSame(Unpooled.LITTLE_ENDIAN, Unpooled.unmodifiableBuffer(buf).order()); + assertSame(LITTLE_ENDIAN, Unpooled.unmodifiableBuffer(buf).order()); } @Test public void shouldReturnReadOnlyDerivedBuffer() { - ByteBuf buf = Unpooled.unmodifiableBuffer(Unpooled.buffer(1)); + ByteBuf buf = unmodifiableBuffer(buffer(1)); assertTrue(buf.duplicate() instanceof ReadOnlyByteBuf); assertTrue(buf.slice() instanceof ReadOnlyByteBuf); assertTrue(buf.slice(0, 1) instanceof ReadOnlyByteBuf); @@ -68,7 +68,7 @@ public void shouldReturnReadOnlyDerivedBuffer() { @Test public void shouldReturnWritableCopy() { - ByteBuf buf = Unpooled.unmodifiableBuffer(Unpooled.buffer(1)); + ByteBuf buf = unmodifiableBuffer(buffer(1)); assertFalse(buf.copy() instanceof ReadOnlyByteBuf); } diff --git a/codec-http/pom.xml b/codec-http/pom.xml index cc38760aae6..119609dc59f 100644 --- a/codec-http/pom.xml +++ b/codec-http/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-codec-http diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java index 1810764d9d7..fa27d0988be 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/CookieDecoder.java @@ -15,6 +15,8 @@ */ package io.netty.handler.codec.http; +import io.netty.util.internal.StringUtil; + import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; @@ -40,7 +42,7 @@ */ public final class CookieDecoder { - private static final String COMMA = ","; + private static final char COMMA = ','; /** * Decodes the specified HTTP header value into {@link Cookie}s. @@ -120,11 +122,9 @@ public static Set decode(String header) { long maxAgeMillis = new HttpHeaderDateFormat().parse(value).getTime() - System.currentTimeMillis(); - if (maxAgeMillis <= 0) { - maxAge = 0; - } else { - maxAge = maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0? 1 : 0); - } + + maxAge = maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0? 1 : 0); + } catch (ParseException e) { // Ignore. } @@ -133,7 +133,7 @@ public static Set decode(String header) { } else if (CookieHeaderNames.VERSION.equalsIgnoreCase(name)) { version = Integer.parseInt(value); } else if (CookieHeaderNames.PORT.equalsIgnoreCase(name)) { - String[] portList = value.split(COMMA); + String[] portList = StringUtil.split(value, COMMA); for (String s1: portList) { try { ports.add(Integer.valueOf(s1)); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultCookie.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultCookie.java index 731e9f0c60d..7e6512196cb 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultCookie.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultCookie.java @@ -48,7 +48,7 @@ public DefaultCookie(String name, String value) { throw new NullPointerException("name"); } name = name.trim(); - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException("empty name"); } @@ -343,7 +343,7 @@ private static String validateValue(String name, String value) { return null; } value = value.trim(); - if (value.length() == 0) { + if (value.isEmpty()) { return null; } for (int i = 0; i < value.length(); i ++) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java index cf85a2f51ee..333c9060caf 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentCompressor.java @@ -18,6 +18,7 @@ import io.netty.channel.embedded.EmbeddedByteChannel; import io.netty.handler.codec.compression.ZlibCodecFactory; import io.netty.handler.codec.compression.ZlibWrapper; +import io.netty.util.internal.StringUtil; /** * Compresses an {@link HttpMessage} and an {@link HttpChunk} in {@code gzip} or @@ -126,7 +127,7 @@ protected ZlibWrapper determineWrapper(String acceptEncoding) { float starQ = -1.0f; float gzipQ = -1.0f; float deflateQ = -1.0f; - for (String encoding : acceptEncoding.split(",")) { + for (String encoding: StringUtil.split(acceptEncoding, ',')) { float q = 1.0f; int equalsPos = encoding.indexOf('='); if (equalsPos != -1) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java index 769d03c1903..76756b28121 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java @@ -1245,7 +1245,7 @@ public String setValue(String value) { @Override public String toString() { - return key + "=" + value; + return key + '=' + value; } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMessageDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMessageDecoder.java index 7ed31212c3f..46fdefe7160 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMessageDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMessageDecoder.java @@ -531,13 +531,13 @@ private ByteBuf read(ByteBuf buffer, int len) { } } - private State readHeaders(ByteBuf buffer) throws TooLongFrameException { + private State readHeaders(ByteBuf buffer) { headerSize = 0; final HttpMessage message = this.message; String line = readHeader(buffer); String name = null; String value = null; - if (line.length() != 0) { + if (!line.isEmpty()) { message.clearHeaders(); do { char firstChar = line.charAt(0); @@ -553,7 +553,7 @@ private State readHeaders(ByteBuf buffer) throws TooLongFrameException { } line = readHeader(buffer); - } while (line.length() != 0); + } while (!line.isEmpty()); // Add the last header. if (name != null) { @@ -577,17 +577,17 @@ private State readHeaders(ByteBuf buffer) throws TooLongFrameException { return nextState; } - private HttpChunkTrailer readTrailingHeaders(ByteBuf buffer) throws TooLongFrameException { + private HttpChunkTrailer readTrailingHeaders(ByteBuf buffer) { headerSize = 0; String line = readHeader(buffer); String lastHeader = null; - if (line.length() != 0) { + if (!line.isEmpty()) { HttpChunkTrailer trailer = new DefaultHttpChunkTrailer(); do { char firstChar = line.charAt(0); if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) { List current = trailer.getHeaders(lastHeader); - if (current.size() != 0) { + if (!current.isEmpty()) { int lastPos = current.size() - 1; String newString = current.get(lastPos) + line.trim(); current.set(lastPos, newString); @@ -606,7 +606,7 @@ private HttpChunkTrailer readTrailingHeaders(ByteBuf buffer) throws TooLongFrame } line = readHeader(buffer); - } while (line.length() != 0); + } while (!line.isEmpty()); return trailer; } @@ -614,7 +614,7 @@ private HttpChunkTrailer readTrailingHeaders(ByteBuf buffer) throws TooLongFrame return HttpChunk.LAST_CHUNK; } - private String readHeader(ByteBuf buffer) throws TooLongFrameException { + private String readHeader(ByteBuf buffer) { StringBuilder sb = new StringBuilder(64); int headerSize = this.headerSize; @@ -672,7 +672,7 @@ private static int getChunkSize(String hex) { return Integer.parseInt(hex, 16); } - private static String readLine(ByteBuf buffer, int maxLineLength) throws TooLongFrameException { + private static String readLine(ByteBuf buffer, int maxLineLength) { StringBuilder sb = new StringBuilder(64); int lineLength = 0; while (true) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java index 166df645c49..31b3c402d68 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpMethod.java @@ -110,7 +110,7 @@ public static HttpMethod valueOf(String name) { } name = name.trim(); - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException("empty name"); } @@ -137,7 +137,7 @@ public HttpMethod(String name) { } name = name.trim(); - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException("empty name"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java index f450c809da2..92ce39a9a76 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpRequestEncoder.java @@ -24,19 +24,26 @@ * a {@link ByteBuf}. */ public class HttpRequestEncoder extends HttpMessageEncoder { - - /** - * Creates a new instance. - */ - public HttpRequestEncoder() { - } + private static final char SLASH = '/'; @Override protected void encodeInitialLine(ByteBuf buf, HttpMessage message) throws Exception { HttpRequest request = (HttpRequest) message; buf.writeBytes(request.getMethod().toString().getBytes(CharsetUtil.US_ASCII)); buf.writeByte(SP); - buf.writeBytes(request.getUri().getBytes(CharsetUtil.UTF_8)); + + // Add / as absolute path if no is present. + // See http://tools.ietf.org/html/rfc2616#section-5.1.2 + String uri = request.getUri(); + int start = uri.indexOf("://"); + if (start != -1) { + int startIndex = start + 3; + if (uri.lastIndexOf(SLASH) <= startIndex) { + uri += SLASH; + } + } + buf.writeBytes(uri.getBytes("UTF-8")); + buf.writeByte(SP); buf.writeBytes(request.getProtocolVersion().toString().getBytes(CharsetUtil.US_ASCII)); buf.writeByte(CR); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java index 141b41d8e19..96e4814c83e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpResponseEncoder.java @@ -25,12 +25,6 @@ */ public class HttpResponseEncoder extends HttpMessageEncoder { - /** - * Creates a new instance. - */ - public HttpResponseEncoder() { - } - @Override protected void encodeInitialLine(ByteBuf buf, HttpMessage message) throws Exception { HttpResponse response = (HttpResponse) message; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpTransferEncoding.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpTransferEncoding.java index 507aa138086..5472014f3db 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpTransferEncoding.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpTransferEncoding.java @@ -56,7 +56,7 @@ public enum HttpTransferEncoding { private final boolean single; - private HttpTransferEncoding(boolean single) { + HttpTransferEncoding(boolean single) { this.single = single; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java index 28fdd45f257..88b1aaf9773 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java @@ -85,7 +85,7 @@ public HttpVersion(String text, boolean keepAliveDefault) { } text = text.trim().toUpperCase(); - if (text.length() == 0) { + if (text.isEmpty()) { throw new IllegalArgumentException("empty text"); } @@ -120,7 +120,7 @@ public HttpVersion( } protocolName = protocolName.trim().toUpperCase(); - if (protocolName.length() == 0) { + if (protocolName.isEmpty()) { throw new IllegalArgumentException("empty protocolName"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index 526a97112ea..f732770dbc7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -163,7 +163,7 @@ public QueryStringDecoder(URI uri, Charset charset, int maxParams) { hasPath = false; } // Also take care of cut of things like "http://localhost" - String newUri = rawPath + "?" + uri.getRawQuery(); + String newUri = rawPath + '?' + uri.getRawQuery(); // http://en.wikipedia.org/wiki/Query_string this.uri = newUri.replace(';', '&'); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java index 14ba09fc6e8..71d8acdb965 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringEncoder.java @@ -100,14 +100,14 @@ public String toString() { if (params.isEmpty()) { return uri; } else { - StringBuilder sb = new StringBuilder(uri).append("?"); + StringBuilder sb = new StringBuilder(uri).append('?'); for (int i = 0; i < params.size(); i++) { Param param = params.get(i); sb.append(encodeComponent(param.name, charset)); - sb.append("="); + sb.append('='); sb.append(encodeComponent(param.value, charset)); if (i != params.size() - 1) { - sb.append("&"); + sb.append('&'); } } return sb.toString(); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java new file mode 100644 index 00000000000..e7f3443cd24 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractDiskHttpData.java @@ -0,0 +1,360 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpConstants; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; + +import static io.netty.buffer.Unpooled.*; + +/** + * Abstract Disk HttpData implementation + */ +public abstract class AbstractDiskHttpData extends AbstractHttpData { + + protected File file; + private boolean isRenamed; + private FileChannel fileChannel; + + protected AbstractDiskHttpData(String name, Charset charset, long size) { + super(name, charset, size); + } + + /** + * + * @return the real DiskFilename (basename) + */ + protected abstract String getDiskFilename(); + /** + * + * @return the default prefix + */ + protected abstract String getPrefix(); + /** + * + * @return the default base Directory + */ + protected abstract String getBaseDirectory(); + /** + * + * @return the default postfix + */ + protected abstract String getPostfix(); + /** + * + * @return True if the file should be deleted on Exit by default + */ + protected abstract boolean deleteOnExit(); + + /** + * + * @return a new Temp File from getDiskFilename(), default prefix, postfix and baseDirectory + * @throws IOException + */ + private File tempFile() throws IOException { + String newpostfix = null; + String diskFilename = getDiskFilename(); + if (diskFilename != null) { + newpostfix = '_' + diskFilename; + } else { + newpostfix = getPostfix(); + } + File tmpFile; + if (getBaseDirectory() == null) { + // create a temporary file + tmpFile = File.createTempFile(getPrefix(), newpostfix); + } else { + tmpFile = File.createTempFile(getPrefix(), newpostfix, new File( + getBaseDirectory())); + } + if (deleteOnExit()) { + tmpFile.deleteOnExit(); + } + return tmpFile; + } + + @Override + public void setContent(ByteBuf buffer) throws IOException { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + size = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size) { + throw new IOException("Out of size: " + size + " > " + definedSize); + } + if (file == null) { + file = tempFile(); + } + if (buffer.readableBytes() == 0) { + // empty file + file.createNewFile(); + return; + } + FileOutputStream outputStream = new FileOutputStream(file); + FileChannel localfileChannel = outputStream.getChannel(); + ByteBuffer byteBuffer = buffer.nioBuffer(); + int written = 0; + while (written < size) { + written += localfileChannel.write(byteBuffer); + } + buffer.readerIndex(buffer.readerIndex() + written); + localfileChannel.force(false); + localfileChannel.close(); + outputStream.close(); + completed = true; + } + + @Override + public void addContent(ByteBuf buffer, boolean last) + throws IOException { + if (buffer != null) { + int localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + throw new IOException("Out of size: " + (size + localsize) + + " > " + definedSize); + } + ByteBuffer byteBuffer = buffer.hasNioBuffer() ? buffer.nioBuffer() : buffer.copy().nioBuffer(); + int written = 0; + if (file == null) { + file = tempFile(); + } + if (fileChannel == null) { + FileOutputStream outputStream = new FileOutputStream(file); + fileChannel = outputStream.getChannel(); + } + while (written < localsize) { + written += fileChannel.write(byteBuffer); + } + size += localsize; + buffer.readerIndex(buffer.readerIndex() + written); + } + if (last) { + if (file == null) { + file = tempFile(); + } + if (fileChannel == null) { + FileOutputStream outputStream = new FileOutputStream(file); + fileChannel = outputStream.getChannel(); + } + fileChannel.force(false); + fileChannel.close(); + fileChannel = null; + completed = true; + } else { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + } + } + + @Override + public void setContent(File file) throws IOException { + if (this.file != null) { + delete(); + } + this.file = file; + size = file.length(); + isRenamed = true; + completed = true; + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new NullPointerException("inputStream"); + } + if (file != null) { + delete(); + } + file = tempFile(); + FileOutputStream outputStream = new FileOutputStream(file); + FileChannel localfileChannel = outputStream.getChannel(); + byte[] bytes = new byte[4096 * 4]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + int read = inputStream.read(bytes); + int written = 0; + while (read > 0) { + byteBuffer.position(read).flip(); + written += localfileChannel.write(byteBuffer); + read = inputStream.read(bytes); + } + localfileChannel.force(false); + localfileChannel.close(); + size = written; + if (definedSize > 0 && definedSize < size) { + file.delete(); + file = null; + throw new IOException("Out of size: " + size + " > " + definedSize); + } + isRenamed = true; + completed = true; + } + + @Override + public void delete() { + if (! isRenamed) { + if (file != null) { + file.delete(); + } + } + } + + @Override + public byte[] get() throws IOException { + if (file == null) { + return new byte[0]; + } + return readFrom(file); + } + + @Override + public ByteBuf getByteBuf() throws IOException { + if (file == null) { + return EMPTY_BUFFER; + } + byte[] array = readFrom(file); + return wrappedBuffer(array); + } + + @Override + public ByteBuf getChunk(int length) throws IOException { + if (file == null || length == 0) { + return EMPTY_BUFFER; + } + if (fileChannel == null) { + FileInputStream inputStream = new FileInputStream(file); + fileChannel = inputStream.getChannel(); + } + int read = 0; + ByteBuffer byteBuffer = ByteBuffer.allocate(length); + while (read < length) { + int readnow = fileChannel.read(byteBuffer); + if (readnow == -1) { + fileChannel.close(); + fileChannel = null; + break; + } else { + read += readnow; + } + } + if (read == 0) { + return EMPTY_BUFFER; + } + byteBuffer.flip(); + ByteBuf buffer = wrappedBuffer(byteBuffer); + buffer.readerIndex(0); + buffer.writerIndex(read); + return buffer; + } + + @Override + public String getString() throws IOException { + return getString(HttpConstants.DEFAULT_CHARSET); + } + + @Override + public String getString(Charset encoding) throws IOException { + if (file == null) { + return ""; + } + if (encoding == null) { + byte[] array = readFrom(file); + return new String(array, HttpConstants.DEFAULT_CHARSET.name()); + } + byte[] array = readFrom(file); + return new String(array, encoding.name()); + } + + @Override + public boolean isInMemory() { + return false; + } + + @Override + public boolean renameTo(File dest) throws IOException { + if (dest == null) { + throw new NullPointerException("dest"); + } + if (!file.renameTo(dest)) { + // must copy + FileInputStream inputStream = new FileInputStream(file); + FileOutputStream outputStream = new FileOutputStream(dest); + FileChannel in = inputStream.getChannel(); + FileChannel out = outputStream.getChannel(); + int chunkSize = 8196; + long position = 0; + while (position < size) { + if (chunkSize < size - position) { + chunkSize = (int) (size - position); + } + position += in.transferTo(position, chunkSize , out); + } + in.close(); + out.close(); + if (position == size) { + file.delete(); + file = dest; + isRenamed = true; + return true; + } else { + dest.delete(); + return false; + } + } + file = dest; + isRenamed = true; + return true; + } + + /** + * Utility function + * @param src + * @return the array of bytes + * @throws IOException + */ + private static byte[] readFrom(File src) throws IOException { + long srcsize = src.length(); + if (srcsize > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "File too big to be loaded in memory"); + } + FileInputStream inputStream = new FileInputStream(src); + FileChannel fileChannel = inputStream.getChannel(); + byte[] array = new byte[(int) srcsize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(array); + int read = 0; + while (read < srcsize) { + read += fileChannel.read(byteBuffer); + } + fileChannel.close(); + return array; + } + + @Override + public File getFile() throws IOException { + return file; + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractHttpData.java new file mode 100644 index 00000000000..4a86534f7fd --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractHttpData.java @@ -0,0 +1,99 @@ +/* + * 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.multipart; + +import io.netty.handler.codec.http.HttpConstants; + +import java.nio.charset.Charset; + +/** + * Abstract HttpData implementation + */ +public abstract class AbstractHttpData implements HttpData { + + protected final String name; + protected long definedSize; + protected long size; + protected Charset charset = HttpConstants.DEFAULT_CHARSET; + protected boolean completed; + + protected AbstractHttpData(String name, Charset charset, long size) { + if (name == null) { + throw new NullPointerException("name"); + } + name = name.trim(); + if (name.isEmpty()) { + throw new IllegalArgumentException("empty 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 '\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); + } + } + this.name = name; + if (charset != null) { + setCharset(charset); + } + definedSize = size; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isCompleted() { + return completed; + } + + @Override + public Charset getCharset() { + return charset; + } + + @Override + public void setCharset(Charset charset) { + if (charset == null) { + throw new NullPointerException("charset"); + } + this.charset = charset; + } + + @Override + public long length() { + return size; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java new file mode 100644 index 00000000000..671c0406a8e --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/AbstractMemoryHttpData.java @@ -0,0 +1,230 @@ +/* + * 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.multipart; + +import static io.netty.buffer.Unpooled.EMPTY_BUFFER; +import static io.netty.buffer.Unpooled.buffer; +import static io.netty.buffer.Unpooled.wrappedBuffer; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpConstants; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; + +/** + * Abstract Memory HttpData implementation + */ +public abstract class AbstractMemoryHttpData extends AbstractHttpData { + + private ByteBuf byteBuf; + private int chunkPosition; + protected boolean isRenamed; + + protected AbstractMemoryHttpData(String name, Charset charset, long size) { + super(name, charset, size); + } + + @Override + public void setContent(ByteBuf buffer) throws IOException { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + long localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < localsize) { + throw new IOException("Out of size: " + localsize + " > " + + definedSize); + } + byteBuf = buffer; + size = localsize; + completed = true; + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new NullPointerException("inputStream"); + } + ByteBuf buffer = buffer(); + byte[] bytes = new byte[4096 * 4]; + int read = inputStream.read(bytes); + int written = 0; + while (read > 0) { + buffer.writeBytes(bytes); + written += read; + read = inputStream.read(bytes); + } + size = written; + if (definedSize > 0 && definedSize < size) { + throw new IOException("Out of size: " + size + " > " + definedSize); + } + byteBuf = buffer; + completed = true; + } + + @Override + public void addContent(ByteBuf buffer, boolean last) + throws IOException { + if (buffer != null) { + long localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + throw new IOException("Out of size: " + (size + localsize) + + " > " + definedSize); + } + size += localsize; + if (byteBuf == null) { + byteBuf = buffer; + } else { + byteBuf = wrappedBuffer(byteBuf, buffer); + } + } + if (last) { + completed = true; + } else { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + } + } + + @Override + public void setContent(File file) throws IOException { + if (file == null) { + throw new NullPointerException("file"); + } + long newsize = file.length(); + if (newsize > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "File too big to be loaded in memory"); + } + FileInputStream inputStream = new FileInputStream(file); + FileChannel fileChannel = inputStream.getChannel(); + byte[] array = new byte[(int) newsize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(array); + int read = 0; + while (read < newsize) { + read += fileChannel.read(byteBuffer); + } + fileChannel.close(); + inputStream.close(); + byteBuffer.flip(); + byteBuf = wrappedBuffer(byteBuffer); + size = newsize; + completed = true; + } + + @Override + public void delete() { + // nothing to do + } + + @Override + public byte[] get() { + if (byteBuf == null) { + return new byte[0]; + } + byte[] array = new byte[byteBuf.readableBytes()]; + byteBuf.getBytes(byteBuf.readerIndex(), array); + return array; + } + + @Override + public String getString() { + return getString(HttpConstants.DEFAULT_CHARSET); + } + + @Override + public String getString(Charset encoding) { + if (byteBuf == null) { + return ""; + } + if (encoding == null) { + return getString(HttpConstants.DEFAULT_CHARSET); + } + return byteBuf.toString(encoding); + } + + /** + * Utility to go from a In Memory FileUpload + * to a Disk (or another implementation) FileUpload + * @return the attached ByteBuf containing the actual bytes + */ + @Override + public ByteBuf getByteBuf() { + return byteBuf; + } + + @Override + public ByteBuf getChunk(int length) throws IOException { + if (byteBuf == null || length == 0 || byteBuf.readableBytes() == 0) { + chunkPosition = 0; + return EMPTY_BUFFER; + } + int sizeLeft = byteBuf.readableBytes() - chunkPosition; + if (sizeLeft == 0) { + chunkPosition = 0; + return EMPTY_BUFFER; + } + int sliceLength = length; + if (sizeLeft < length) { + sliceLength = sizeLeft; + } + ByteBuf chunk = byteBuf.slice(chunkPosition, sliceLength); + chunkPosition += sliceLength; + return chunk; + } + + @Override + public boolean isInMemory() { + return true; + } + + @Override + public boolean renameTo(File dest) throws IOException { + if (dest == null) { + throw new NullPointerException("dest"); + } + if (byteBuf == null) { + // empty file + dest.createNewFile(); + isRenamed = true; + return true; + } + int length = byteBuf.readableBytes(); + FileOutputStream outputStream = new FileOutputStream(dest); + FileChannel fileChannel = outputStream.getChannel(); + ByteBuffer byteBuffer = byteBuf.nioBuffer(); + int written = 0; + while (written < length) { + written += fileChannel.write(byteBuffer); + } + fileChannel.force(false); + fileChannel.close(); + outputStream.close(); + isRenamed = true; + return written == length; + } + + @Override + public File getFile() throws IOException { + throw new IOException("Not represented by a file"); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/Attribute.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/Attribute.java new file mode 100644 index 00000000000..7263b9f270f --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/Attribute.java @@ -0,0 +1,34 @@ +/* + * 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.multipart; + +import java.io.IOException; + +/** + * Attribute interface + */ +public interface Attribute extends HttpData { + /** + * Returns the value of this HttpData. + */ + String getValue() throws IOException; + + /** + * Sets the value of this HttpData. + * @param value + */ + void setValue(String value) throws IOException; +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/CaseIgnoringComparator.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/CaseIgnoringComparator.java new file mode 100644 index 00000000000..2f571744fcd --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/CaseIgnoringComparator.java @@ -0,0 +1,39 @@ +/* + * 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.multipart; + +import java.io.Serializable; +import java.util.Comparator; + +public final class CaseIgnoringComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 4582133183775373862L; + + public static final CaseIgnoringComparator INSTANCE = new CaseIgnoringComparator(); + + private CaseIgnoringComparator() { + } + + @Override + public int compare(String o1, String o2) { + return o1.compareToIgnoreCase(o2); + } + + @SuppressWarnings("MethodMayBeStatic") + private Object readResolve() { + return INSTANCE; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java new file mode 100644 index 00000000000..7e2f186557f --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DefaultHttpDataFactory.java @@ -0,0 +1,190 @@ +/* + * 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.multipart; + +import io.netty.handler.codec.http.HttpRequest; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Default factory giving Attribute and FileUpload according to constructor + * + * Attribute and FileUpload could be :
+ * - MemoryAttribute, DiskAttribute or MixedAttribute
+ * - MemoryFileUpload, DiskFileUpload or MixedFileUpload
+ * according to the constructor. + */ +public class DefaultHttpDataFactory implements HttpDataFactory { + /** + * Proposed default MINSIZE as 16 KB. + */ + public static final long MINSIZE = 0x4000; + + private final boolean useDisk; + + private final boolean checkSize; + + private long minSize; + + /** + * Keep all HttpDatas until cleanAllHttpDatas() is called. + */ + private final ConcurrentHashMap> requestFileDeleteMap = + new ConcurrentHashMap>(); + /** + * HttpData will be in memory if less than default size (16KB). + * The type will be Mixed. + */ + public DefaultHttpDataFactory() { + useDisk = false; + checkSize = true; + minSize = MINSIZE; + } + + /** + * HttpData will be always on Disk if useDisk is True, else always in Memory if False + * @param useDisk + */ + public DefaultHttpDataFactory(boolean useDisk) { + this.useDisk = useDisk; + checkSize = false; + } + + /** + * HttpData will be on Disk if the size of the file is greater than minSize, else it + * will be in memory. The type will be Mixed. + * @param minSize + */ + public DefaultHttpDataFactory(long minSize) { + useDisk = false; + checkSize = true; + this.minSize = minSize; + } + + /** + * + * @param request + * @return the associated list of Files for the request + */ + private List getList(HttpRequest request) { + List list = requestFileDeleteMap.get(request); + if (list == null) { + list = new ArrayList(); + requestFileDeleteMap.put(request, list); + } + return list; + } + + @Override + public Attribute createAttribute(HttpRequest request, String name) { + if (useDisk) { + Attribute attribute = new DiskAttribute(name); + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } else if (checkSize) { + Attribute attribute = new MixedAttribute(name, minSize); + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } + return new MemoryAttribute(name); + } + + @Override + public Attribute createAttribute(HttpRequest request, String name, String value) { + if (useDisk) { + Attribute attribute; + try { + attribute = new DiskAttribute(name, value); + } catch (IOException e) { + // revert to Mixed mode + attribute = new MixedAttribute(name, value, minSize); + } + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } else if (checkSize) { + Attribute attribute = new MixedAttribute(name, value, minSize); + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } + try { + return new MemoryAttribute(name, value); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public FileUpload createFileUpload(HttpRequest request, String name, String filename, + String contentType, String contentTransferEncoding, Charset charset, + long size) { + if (useDisk) { + FileUpload fileUpload = new DiskFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + List fileToDelete = getList(request); + fileToDelete.add(fileUpload); + return fileUpload; + } else if (checkSize) { + FileUpload fileUpload = new MixedFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size, minSize); + List fileToDelete = getList(request); + fileToDelete.add(fileUpload); + return fileUpload; + } + return new MemoryFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + } + + @Override + public void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data) { + if (data instanceof HttpData) { + List fileToDelete = getList(request); + fileToDelete.remove(data); + } + } + + @Override + public void cleanRequestHttpDatas(HttpRequest request) { + List fileToDelete = requestFileDeleteMap.remove(request); + if (fileToDelete != null) { + for (HttpData data: fileToDelete) { + data.delete(); + } + fileToDelete.clear(); + } + } + + @Override + public void cleanAllHttpDatas() { + for (HttpRequest request : requestFileDeleteMap.keySet()) { + List fileToDelete = requestFileDeleteMap.get(request); + if (fileToDelete != null) { + for (HttpData data: fileToDelete) { + data.delete(); + } + fileToDelete.clear(); + } + requestFileDeleteMap.remove(request); + } + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DiskAttribute.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DiskAttribute.java new file mode 100644 index 00000000000..750d241ede2 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DiskAttribute.java @@ -0,0 +1,149 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpConstants; + +import java.io.IOException; + +import static io.netty.buffer.Unpooled.*; + +/** + * Disk implementation of Attributes + */ +public class DiskAttribute extends AbstractDiskHttpData implements Attribute { + public static String baseDirectory; + + public static boolean deleteOnExitTemporaryFile = true; + + public static String prefix = "Attr_"; + + public static String postfix = ".att"; + + /** + * Constructor used for huge Attribute + * @param name + */ + public DiskAttribute(String name) { + super(name, HttpConstants.DEFAULT_CHARSET, 0); + } + /** + * + * @param name + * @param value + * @throws NullPointerException + * @throws IllegalArgumentException + * @throws IOException + */ + public DiskAttribute(String name, String value) throws IOException { + super(name, HttpConstants.DEFAULT_CHARSET, 0); // Attribute have no default size + setValue(value); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.Attribute; + } + + @Override + public String getValue() throws IOException { + byte [] bytes = get(); + return new String(bytes, charset.name()); + } + + @Override + public void setValue(String value) throws IOException { + if (value == null) { + throw new NullPointerException("value"); + } + byte [] bytes = value.getBytes(charset.name()); + ByteBuf buffer = wrappedBuffer(bytes); + if (definedSize > 0) { + definedSize = buffer.readableBytes(); + } + setContent(buffer); + } + + @Override + public void addContent(ByteBuf buffer, boolean last) throws IOException { + int localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + definedSize = size + localsize; + } + super.addContent(buffer, last); + } + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof Attribute)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((Attribute) arg0); + } + + public int compareTo(Attribute o) { + return getName().compareToIgnoreCase(o.getName()); + } + + @Override + public String toString() { + try { + return getName() + '=' + getValue(); + } catch (IOException e) { + return getName() + "=IoException"; + } + } + + @Override + protected boolean deleteOnExit() { + return deleteOnExitTemporaryFile; + } + + @Override + protected String getBaseDirectory() { + return baseDirectory; + } + + @Override + protected String getDiskFilename() { + return getName() + postfix; + } + + @Override + protected String getPostfix() { + return postfix; + } + + @Override + protected String getPrefix() { + return prefix; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DiskFileUpload.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DiskFileUpload.java new file mode 100644 index 00000000000..6a55b589083 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/DiskFileUpload.java @@ -0,0 +1,162 @@ +/* + * 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.multipart; + +import io.netty.handler.codec.http.HttpHeaders; + +import java.io.File; +import java.nio.charset.Charset; + +/** + * Disk FileUpload implementation that stores file into real files + */ +public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload { + public static String baseDirectory; + + public static boolean deleteOnExitTemporaryFile = true; + + public static String prefix = "FUp_"; + + public static String postfix = ".tmp"; + + private String filename; + + private String contentType; + + private String contentTransferEncoding; + + public DiskFileUpload(String name, String filename, String contentType, + String contentTransferEncoding, Charset charset, long size) { + super(name, charset, size); + setFilename(filename); + setContentType(contentType); + setContentTransferEncoding(contentTransferEncoding); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.FileUpload; + } + + @Override + public String getFilename() { + return filename; + } + + @Override + public void setFilename(String filename) { + if (filename == null) { + throw new NullPointerException("filename"); + } + this.filename = filename; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof FileUpload)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((FileUpload) arg0); + } + + public int compareTo(FileUpload o) { + int v; + v = getName().compareToIgnoreCase(o.getName()); + if (v != 0) { + return v; + } + // TODO should we compare size ? + return v; + } + + @Override + public void setContentType(String contentType) { + if (contentType == null) { + throw new NullPointerException("contentType"); + } + this.contentType = contentType; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + @Override + public void setContentTransferEncoding(String contentTransferEncoding) { + this.contentTransferEncoding = contentTransferEncoding; + } + + @Override + public String toString() { + return HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + + HttpPostBodyUtil.FORM_DATA + "; " + HttpPostBodyUtil.NAME + "=\"" + getName() + + "\"; " + HttpPostBodyUtil.FILENAME + "=\"" + filename + "\"\r\n" + + HttpHeaders.Names.CONTENT_TYPE + ": " + contentType + + (charset != null? "; " + HttpHeaders.Values.CHARSET + '=' + charset + "\r\n" : "\r\n") + + HttpHeaders.Names.CONTENT_LENGTH + ": " + length() + "\r\n" + + "Completed: " + isCompleted() + + "\r\nIsInMemory: " + isInMemory() + "\r\nRealFile: " + + file.getAbsolutePath() + " DefaultDeleteAfter: " + + deleteOnExitTemporaryFile; + } + + @Override + protected boolean deleteOnExit() { + return deleteOnExitTemporaryFile; + } + + @Override + protected String getBaseDirectory() { + return baseDirectory; + } + + @Override + protected String getDiskFilename() { + File file = new File(filename); + return file.getName(); + } + + @Override + protected String getPostfix() { + return postfix; + } + + @Override + protected String getPrefix() { + return prefix; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/FileUpload.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/FileUpload.java new file mode 100644 index 00000000000..3eb4596d566 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/FileUpload.java @@ -0,0 +1,60 @@ +/* + * 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.multipart; + +/** + * FileUpload interface that could be in memory, on temporary file or any other implementations. + * + * Most methods are inspired from java.io.File API. + */ +public interface FileUpload extends HttpData { + /** + * Returns the original filename in the client's filesystem, + * as provided by the browser (or other client software). + * @return the original filename + */ + String getFilename(); + + /** + * Set the original filename + * @param filename + */ + void setFilename(String filename); + + /** + * Set the Content Type passed by the browser if defined + * @param contentType Content Type to set - must be not null + */ + void setContentType(String contentType); + + /** + * Returns the content type passed by the browser or null if not defined. + * @return the content type passed by the browser or null if not defined. + */ + String getContentType(); + + /** + * Set the Content-Transfer-Encoding type from String as 7bit, 8bit or binary + * @param contentTransferEncoding + */ + void setContentTransferEncoding(String contentTransferEncoding); + + /** + * Returns the Content-Transfer-Encoding + * @return the Content-Transfer-Encoding + */ + String getContentTransferEncoding(); +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java new file mode 100644 index 00000000000..f3f4b0f90e8 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpData.java @@ -0,0 +1,181 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * Extended interface for InterfaceHttpData + */ +public interface HttpData extends InterfaceHttpData { + /** + * Set the content from the ChannelBuffer (erase any previous data) + * + * @param buffer + * must be not null + * @exception IOException + */ + void setContent(ByteBuf buffer) throws IOException; + + /** + * Add the content from the ChannelBuffer + * + * @param buffer + * must be not null except if last is set to False + * @param last + * True of the buffer is the last one + * @exception IOException + */ + void addContent(ByteBuf buffer, boolean last) throws IOException; + + /** + * Set the content from the file (erase any previous data) + * + * @param file + * must be not null + * @exception IOException + */ + void setContent(File file) throws IOException; + + /** + * Set the content from the inputStream (erase any previous data) + * + * @param inputStream + * must be not null + * @exception IOException + */ + void setContent(InputStream inputStream) throws IOException; + + /** + * + * @return True if the InterfaceHttpData is completed (all data are stored) + */ + boolean isCompleted(); + + /** + * Returns the size in byte of the InterfaceHttpData + * + * @return the size of the InterfaceHttpData + */ + long length(); + + /** + * Deletes the underlying storage for a file item, including deleting any + * associated temporary disk file. + */ + void delete(); + + /** + * Returns the contents of the file item as an array of bytes. + * + * @return the contents of the file item as an array of bytes. + * @exception IOException + */ + byte[] get() throws IOException; + + /** + * Returns the content of the file item as a ByteBuf + * + * @return the content of the file item as a ByteBuf + * @throws IOException + */ + ByteBuf getByteBuf() throws IOException; + + /** + * Returns a ChannelBuffer for the content from the current position with at + * most length read bytes, increasing the current position of the Bytes + * read. Once it arrives at the end, it returns an EMPTY_BUFFER and it + * resets the current position to 0. + * + * @param length + * @return a ChannelBuffer for the content from the current position or an + * EMPTY_BUFFER if there is no more data to return + * @throws IOException + */ + ByteBuf getChunk(int length) throws IOException; + + /** + * Returns the contents of the file item as a String, using the default + * character encoding. + * + * @return the contents of the file item as a String, using the default + * character encoding. + * @exception IOException + */ + String getString() throws IOException; + + /** + * Returns the contents of the file item as a String, using the specified + * charset. + * + * @param encoding + * the charset to use + * @return the contents of the file item as a String, using the specified + * charset. + * @exception IOException + */ + String getString(Charset encoding) throws IOException; + + /** + * Set the Charset passed by the browser if defined + * + * @param charset + * Charset to set - must be not null + */ + void setCharset(Charset charset); + + /** + * Returns the Charset passed by the browser or null if not defined. + * + * @return the Charset passed by the browser or null if not defined. + */ + Charset getCharset(); + + /** + * A convenience method to write an uploaded item to disk. If a previous one + * exists, it will be deleted. Once this method is called, if successful, + * the new file will be out of the cleaner of the factory that creates the + * original InterfaceHttpData object. + * + * @param dest + * destination file - must be not null + * @return True if the write is successful + * @exception IOException + */ + boolean renameTo(File dest) throws IOException; + + /** + * Provides a hint as to whether or not the file contents will be read from + * memory. + * + * @return True if the file contents is in memory. + */ + boolean isInMemory(); + + /** + * + * @return the associated File if this data is represented in a file + * @exception IOException + * if this data is not represented by a file + */ + File getFile() throws IOException; + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpDataFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpDataFactory.java new file mode 100644 index 00000000000..6faa5b64e31 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpDataFactory.java @@ -0,0 +1,80 @@ +/* + * 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.multipart; + +import java.nio.charset.Charset; + +import io.netty.handler.codec.http.HttpRequest; + +/** + * Interface to enable creation of InterfaceHttpData objects + */ +public interface HttpDataFactory { + /** + * + * @param request associated request + * @param name + * @return a new Attribute with no value + * @throws NullPointerException + * @throws IllegalArgumentException + */ + Attribute createAttribute(HttpRequest request, String name); + + /** + * + * @param request associated request + * @param name + * @param value + * @return a new Attribute + * @throws NullPointerException + * @throws IllegalArgumentException + */ + Attribute createAttribute(HttpRequest request, String name, String value); + + /** + * + * @param request associated request + * @param name + * @param filename + * @param contentType + * @param charset + * @param size the size of the Uploaded file + * @return a new FileUpload + */ + FileUpload createFileUpload(HttpRequest request, String name, String filename, + String contentType, String contentTransferEncoding, Charset charset, + long size); + + /** + * Remove the given InterfaceHttpData from clean list (will not delete the file, except if the file + * is still a temporary one as setup at construction) + * @param request associated request + * @param data + */ + void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data); + + /** + * Remove all InterfaceHttpData from virtual File storage from clean list for the request + * + * @param request associated request + */ + void cleanRequestHttpDatas(HttpRequest request); + + /** + * Remove all InterfaceHttpData from virtual File storage from clean list for all requests + */ + void cleanAllHttpDatas(); +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java new file mode 100644 index 00000000000..091ee345a4e --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java @@ -0,0 +1,233 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; + +import java.nio.charset.Charset; + +/** + * Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder + */ +final class HttpPostBodyUtil { + + public static int chunkSize = 8096; + /** + * HTTP content disposition header name. + */ + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + + public static final String NAME = "name"; + + public static final String FILENAME = "filename"; + + /** + * Content-disposition value for form data. + */ + public static final String FORM_DATA = "form-data"; + + /** + * Content-disposition value for file attachment. + */ + public static final String ATTACHMENT = "attachment"; + + /** + * Content-disposition value for file attachment. + */ + public static final String FILE = "file"; + + /** + * HTTP content type body attribute for multiple uploads. + */ + public static final String MULTIPART_MIXED = "multipart/mixed"; + + /** + * Charset for 8BIT + */ + public static final Charset ISO_8859_1 = CharsetUtil.ISO_8859_1; + + /** + * Charset for 7BIT + */ + public static final Charset US_ASCII = CharsetUtil.US_ASCII; + + /** + * Default Content-Type in binary form + */ + public static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream"; + + /** + * Default Content-Type in Text form + */ + public static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain"; + + /** + * Allowed mechanism for multipart + * mechanism := "7bit" + / "8bit" + / "binary" + Not allowed: "quoted-printable" + / "base64" + */ + public enum TransferEncodingMechanism { + /** + * Default encoding + */ + BIT7("7bit"), + /** + * Short lines but not in ASCII - no encoding + */ + BIT8("8bit"), + /** + * Could be long text not in ASCII - no encoding + */ + BINARY("binary"); + + private final String value; + + TransferEncodingMechanism(String value) { + this.value = value; + } + + TransferEncodingMechanism() { + value = name(); + } + + public String value() { + return value; + } + + @Override + public String toString() { + return value; + } + } + + private HttpPostBodyUtil() { + } + + + /** + * Exception when NO Backend Array is found + */ + static class SeekAheadNoBackArrayException extends Exception { + private static final long serialVersionUID = -630418804938699495L; + } + + /** + * This class intends to decrease the CPU in seeking ahead some bytes in + * HttpPostRequestDecoder + */ + static class SeekAheadOptimize { + byte[] bytes; + int readerIndex; + int pos; + int origPos; + int limit; + ByteBuf buffer; + + /** + * @param buffer + */ + SeekAheadOptimize(ByteBuf buffer) throws SeekAheadNoBackArrayException { + if (!buffer.hasArray()) { + throw new SeekAheadNoBackArrayException(); + } + this.buffer = buffer; + bytes = buffer.array(); + readerIndex = buffer.readerIndex(); + origPos = pos = buffer.arrayOffset() + readerIndex; + limit = buffer.arrayOffset() + buffer.writerIndex(); + } + + /** + * + * @param minus this value will be used as (currentPos - minus) to set + * the current readerIndex in the buffer. + */ + void setReadPosition(int minus) { + pos -= minus; + readerIndex = getReadPosition(pos); + buffer.readerIndex(readerIndex); + } + + /** + * + * @param index raw index of the array (pos in general) + * @return the value equivalent of raw index to be used in readerIndex(value) + */ + int getReadPosition(int index) { + return index - origPos + readerIndex; + } + + void clear() { + buffer = null; + bytes = null; + limit = 0; + pos = 0; + readerIndex = 0; + } + } + + /** + * Find the first non whitespace + * @param sb + * @param offset + * @return the rank of the first non whitespace + */ + static int findNonWhitespace(String sb, int offset) { + int result; + for (result = offset; result < sb.length(); result ++) { + if (!Character.isWhitespace(sb.charAt(result))) { + break; + } + } + return result; + } + + /** + * Find the first whitespace + * @param sb + * @param offset + * @return the rank of the first whitespace + */ + static int findWhitespace(String sb, int offset) { + int result; + for (result = offset; result < sb.length(); result ++) { + if (Character.isWhitespace(sb.charAt(result))) { + break; + } + } + return result; + } + + /** + * Find the end of String + * @param sb + * @return the rank of the end of string + */ + static int findEndOfString(String sb) { + int result; + for (result = sb.length(); result > 0; result --) { + if (!Character.isWhitespace(sb.charAt(result - 1))) { + break; + } + } + return result; + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java new file mode 100644 index 00000000000..f1e8adb3a97 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java @@ -0,0 +1,2126 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpChunk; +import io.netty.handler.codec.http.HttpConstants; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadNoBackArrayException; +import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize; +import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.TransferEncodingMechanism; +import io.netty.util.internal.StringUtil; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static io.netty.buffer.Unpooled.*; + +/** + * This decoder will decode Body and can handle POST BODY. + */ +public class HttpPostRequestDecoder { + /** + * Factory used to create InterfaceHttpData + */ + private final HttpDataFactory factory; + + /** + * Request to decode + */ + private final HttpRequest request; + + /** + * Default charset to use + */ + private final Charset charset; + + /** + * Does request have a body to decode + */ + private boolean bodyToDecode; + + /** + * Does the last chunk already received + */ + private boolean isLastChunk; + + /** + * HttpDatas from Body + */ + private final List bodyListHttpData = new ArrayList(); + + /** + * HttpDatas as Map from Body + */ + private final Map> bodyMapHttpData = new TreeMap>( + CaseIgnoringComparator.INSTANCE); + + /** + * The current channelBuffer + */ + private ByteBuf undecodedChunk; + + /** + * Does this request is a Multipart request + */ + private boolean isMultipart; + + /** + * Body HttpDatas current position + */ + private int bodyListHttpDataRank; + + /** + * If multipart, this is the boundary for the flobal multipart + */ + private String multipartDataBoundary; + + /** + * If multipart, there could be internal multiparts (mixed) to the global + * multipart. Only one level is allowed. + */ + private String multipartMixedBoundary; + + /** + * Current status + */ + private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED; + + /** + * Used in Multipart + */ + private Map currentFieldAttributes; + + /** + * The current FileUpload that is currently in decode process + */ + private FileUpload currentFileUpload; + + /** + * The current Attribute that is currently in decode process + */ + private Attribute currentAttribute; + + /** + * + * @param request + * the request to decode + * @throws NullPointerException + * for request + * @throws IncompatibleDataDecoderException + * if the request has no body to decode + * @throws ErrorDataDecoderException + * if the default charset was wrong when decoding or other + * errors + */ + public HttpPostRequestDecoder(HttpRequest request) throws ErrorDataDecoderException, + IncompatibleDataDecoderException { + this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET); + } + + /** + * + * @param factory + * the factory used to create InterfaceHttpData + * @param request + * the request to decode + * @throws NullPointerException + * for request or factory + * @throws IncompatibleDataDecoderException + * if the request has no body to decode + * @throws ErrorDataDecoderException + * if the default charset was wrong when decoding or other + * errors + */ + public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request) throws ErrorDataDecoderException, + IncompatibleDataDecoderException { + this(factory, request, HttpConstants.DEFAULT_CHARSET); + } + + /** + * + * @param factory + * the factory used to create InterfaceHttpData + * @param request + * the request to decode + * @param charset + * the charset to use as default + * @throws NullPointerException + * for request or charset or factory + * @throws IncompatibleDataDecoderException + * if the request has no body to decode + * @throws ErrorDataDecoderException + * if the default charset was wrong when decoding or other + * errors + */ + public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) + throws ErrorDataDecoderException, IncompatibleDataDecoderException { + if (factory == null) { + throw new NullPointerException("factory"); + } + if (request == null) { + throw new NullPointerException("request"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + this.request = request; + HttpMethod method = request.getMethod(); + if (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PATCH)) { + bodyToDecode = true; + } + this.charset = charset; + this.factory = factory; + // Fill default values + if (this.request.containsHeader(HttpHeaders.Names.CONTENT_TYPE)) { + checkMultipart(this.request.getHeader(HttpHeaders.Names.CONTENT_TYPE)); + } else { + isMultipart = false; + } + if (!bodyToDecode) { + throw new IncompatibleDataDecoderException("No Body to decode"); + } + if (!this.request.getTransferEncoding().isMultiple()) { + undecodedChunk = this.request.getContent(); + isLastChunk = true; + parseBody(); + } + } + + /** + * states follow NOTSTARTED PREAMBLE ( (HEADERDELIMITER DISPOSITION (FIELD | + * FILEUPLOAD))* (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE (MIXEDDELIMITER + * MIXEDDISPOSITION MIXEDFILEUPLOAD)+ MIXEDCLOSEDELIMITER)* CLOSEDELIMITER)+ + * EPILOGUE + * + * First status is: NOSTARTED + * + * Content-type: multipart/form-data, boundary=AaB03x => PREAMBLE in Header + * + * --AaB03x => HEADERDELIMITER content-disposition: form-data; name="field1" + * => DISPOSITION + * + * Joe Blow => FIELD --AaB03x => HEADERDELIMITER content-disposition: + * form-data; name="pics" => DISPOSITION Content-type: multipart/mixed, + * boundary=BbC04y + * + * --BbC04y => MIXEDDELIMITER Content-disposition: attachment; + * filename="file1.txt" => MIXEDDISPOSITION Content-Type: text/plain + * + * ... contents of file1.txt ... => MIXEDFILEUPLOAD --BbC04y => + * MIXEDDELIMITER Content-disposition: file; filename="file2.gif" => + * MIXEDDISPOSITION Content-type: image/gif Content-Transfer-Encoding: + * binary + * + * ...contents of file2.gif... => MIXEDFILEUPLOAD --BbC04y-- => + * MIXEDCLOSEDELIMITER --AaB03x-- => CLOSEDELIMITER + * + * Once CLOSEDELIMITER is found, last status is EPILOGUE + */ + private enum MultiPartStatus { + NOTSTARTED, PREAMBLE, HEADERDELIMITER, DISPOSITION, FIELD, FILEUPLOAD, MIXEDPREAMBLE, MIXEDDELIMITER, + MIXEDDISPOSITION, MIXEDFILEUPLOAD, MIXEDCLOSEDELIMITER, CLOSEDELIMITER, PREEPILOGUE, EPILOGUE + } + + /** + * Check from the request ContentType if this request is a Multipart + * request. + * + * @param contentType + * @throws ErrorDataDecoderException + */ + private void checkMultipart(String contentType) throws ErrorDataDecoderException { + // Check if Post using "multipart/form-data; boundary=--89421926422648" + String[] headerContentType = splitHeaderContentType(contentType); + if (headerContentType[0].toLowerCase().startsWith(HttpHeaders.Values.MULTIPART_FORM_DATA) + && headerContentType[1].toLowerCase().startsWith(HttpHeaders.Values.BOUNDARY)) { + String[] boundary = StringUtil.split(headerContentType[1], '='); + if (boundary.length != 2) { + throw new ErrorDataDecoderException("Needs a boundary value"); + } + multipartDataBoundary = "--" + boundary[1]; + isMultipart = true; + currentStatus = MultiPartStatus.HEADERDELIMITER; + } else { + isMultipart = false; + } + } + + /** + * True if this request is a Multipart request + * + * @return True if this request is a Multipart request + */ + public boolean isMultipart() { + return isMultipart; + } + + /** + * This method returns a List of all HttpDatas from body.
+ * + * If chunked, all chunks must have been offered using offer() method. If + * not, NotEnoughDataDecoderException will be raised. + * + * @return the list of HttpDatas from Body part for POST method + * @throws NotEnoughDataDecoderException + * Need more chunks + */ + public List getBodyHttpDatas() throws NotEnoughDataDecoderException { + if (!isLastChunk) { + throw new NotEnoughDataDecoderException(); + } + return bodyListHttpData; + } + + /** + * This method returns a List of all HttpDatas with the given name from + * body.
+ * + * If chunked, all chunks must have been offered using offer() method. If + * not, NotEnoughDataDecoderException will be raised. + * + * @param name + * @return All Body HttpDatas with the given name (ignore case) + * @throws NotEnoughDataDecoderException + * need more chunks + */ + public List getBodyHttpDatas(String name) throws NotEnoughDataDecoderException { + if (!isLastChunk) { + throw new NotEnoughDataDecoderException(); + } + return bodyMapHttpData.get(name); + } + + /** + * This method returns the first InterfaceHttpData with the given name from + * body.
+ * + * If chunked, all chunks must have been offered using offer() method. If + * not, NotEnoughDataDecoderException will be raised. + * + * @param name + * @return The first Body InterfaceHttpData with the given name (ignore + * case) + * @throws NotEnoughDataDecoderException + * need more chunks + */ + public InterfaceHttpData getBodyHttpData(String name) throws NotEnoughDataDecoderException { + if (!isLastChunk) { + throw new NotEnoughDataDecoderException(); + } + List list = bodyMapHttpData.get(name); + if (list != null) { + return list.get(0); + } + return null; + } + + /** + * Initialized the internals from a new chunk + * + * @param chunk + * the new received chunk + * @throws ErrorDataDecoderException + * if there is a problem with the charset decoding or other + * errors + */ + public void offer(HttpChunk chunk) throws ErrorDataDecoderException { + ByteBuf chunked = chunk.getContent(); + if (undecodedChunk == null) { + undecodedChunk = chunked; + } else { + // undecodedChunk = ByteBufs.wrappedBuffer(undecodedChunk, + // chunk.getContent()); + // less memory usage + undecodedChunk = wrappedBuffer(undecodedChunk, chunked); + } + if (chunk.isLast()) { + isLastChunk = true; + } + parseBody(); + } + + /** + * True if at current status, there is an available decoded + * InterfaceHttpData from the Body. + * + * This method works for chunked and not chunked request. + * + * @return True if at current status, there is a decoded InterfaceHttpData + * @throws EndOfDataDecoderException + * No more data will be available + */ + public boolean hasNext() throws EndOfDataDecoderException { + if (currentStatus == MultiPartStatus.EPILOGUE) { + // OK except if end of list + if (bodyListHttpDataRank >= bodyListHttpData.size()) { + throw new EndOfDataDecoderException(); + } + } + return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size(); + } + + /** + * Returns the next available InterfaceHttpData or null if, at the time it + * is called, there is no more available InterfaceHttpData. A subsequent + * call to offer(httpChunk) could enable more data. + * + * @return the next available InterfaceHttpData or null if none + * @throws EndOfDataDecoderException + * No more data will be available + */ + public InterfaceHttpData next() throws EndOfDataDecoderException { + if (hasNext()) { + return bodyListHttpData.get(bodyListHttpDataRank++); + } + return null; + } + + /** + * This method will parse as much as possible data and fill the list and map + * + * @throws ErrorDataDecoderException + * if there is a problem with the charset decoding or other + * errors + */ + private void parseBody() throws ErrorDataDecoderException { + if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) { + if (isLastChunk) { + currentStatus = MultiPartStatus.EPILOGUE; + } + return; + } + if (isMultipart) { + parseBodyMultipart(); + } else { + parseBodyAttributes(); + } + } + + /** + * Utility function to add a new decoded data + * + * @param data + */ + private void addHttpData(InterfaceHttpData data) { + if (data == null) { + return; + } + List datas = bodyMapHttpData.get(data.getName()); + if (datas == null) { + datas = new ArrayList(1); + bodyMapHttpData.put(data.getName(), datas); + } + datas.add(data); + bodyListHttpData.add(data); + } + + /** + * This method fill the map and list with as much Attribute as possible from + * Body in not Multipart mode. + * + * @throws ErrorDataDecoderException + * if there is a problem with the charset decoding or other + * errors + */ + private void parseBodyAttributesStandard() throws ErrorDataDecoderException { + int firstpos = undecodedChunk.readerIndex(); + int currentpos = firstpos; + int equalpos = firstpos; + int ampersandpos = firstpos; + if (currentStatus == MultiPartStatus.NOTSTARTED) { + currentStatus = MultiPartStatus.DISPOSITION; + } + boolean contRead = true; + try { + while (undecodedChunk.readable() && contRead) { + char read = (char) undecodedChunk.readUnsignedByte(); + currentpos++; + switch (currentStatus) { + case DISPOSITION:// search '=' + if (read == '=') { + currentStatus = MultiPartStatus.FIELD; + equalpos = currentpos - 1; + String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset), + charset); + currentAttribute = factory.createAttribute(request, key); + 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; + case FIELD:// search '&' or end of line + if (read == '&') { + currentStatus = MultiPartStatus.DISPOSITION; + ampersandpos = currentpos - 1; + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = true; + } else if (read == HttpConstants.CR) { + if (undecodedChunk.readable()) { + read = (char) undecodedChunk.readUnsignedByte(); + currentpos++; + if (read == HttpConstants.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos - 2; + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = false; + } else { + // Error + contRead = false; + throw new ErrorDataDecoderException("Bad end of line"); + } + } else { + currentpos--; + } + } else if (read == HttpConstants.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos - 1; + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = false; + } + break; + default: + // just stop + contRead = false; + } + } + if (isLastChunk && currentAttribute != null) { + // special case + ampersandpos = currentpos; + if (ampersandpos > firstpos) { + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + } else if (!currentAttribute.isCompleted()) { + setFinalBuffer(EMPTY_BUFFER); + } + firstpos = currentpos; + currentStatus = MultiPartStatus.EPILOGUE; + return; + } + if (contRead && currentAttribute != null) { + // reset index except if to continue in case of FIELD status + if (currentStatus == MultiPartStatus.FIELD) { + currentAttribute.addContent(undecodedChunk.slice(firstpos, currentpos - firstpos), false); + firstpos = currentpos; + } + undecodedChunk.readerIndex(firstpos); + } else { + // end of line so keep index + } + } catch (ErrorDataDecoderException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw e; + } catch (IOException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw new ErrorDataDecoderException(e); + } + } + + /** + * This method fill the map and list with as much Attribute as possible from + * Body in not Multipart mode. + * + * @throws ErrorDataDecoderException + * if there is a problem with the charset decoding or other + * errors + */ + private void parseBodyAttributes() throws ErrorDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArrayException e1) { + parseBodyAttributesStandard(); + return; + } + int firstpos = undecodedChunk.readerIndex(); + int currentpos = firstpos; + int equalpos = firstpos; + int ampersandpos = firstpos; + if (currentStatus == MultiPartStatus.NOTSTARTED) { + currentStatus = MultiPartStatus.DISPOSITION; + } + boolean contRead = true; + try { + loop: while (sao.pos < sao.limit) { + char read = (char) (sao.bytes[sao.pos++] & 0xFF); + currentpos++; + switch (currentStatus) { + case DISPOSITION:// search '=' + if (read == '=') { + currentStatus = MultiPartStatus.FIELD; + equalpos = currentpos - 1; + String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset), + charset); + currentAttribute = factory.createAttribute(request, key); + 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; + case FIELD:// search '&' or end of line + if (read == '&') { + currentStatus = MultiPartStatus.DISPOSITION; + ampersandpos = currentpos - 1; + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = true; + } else if (read == HttpConstants.CR) { + if (sao.pos < sao.limit) { + read = (char) (sao.bytes[sao.pos++] & 0xFF); + currentpos++; + if (read == HttpConstants.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos - 2; + sao.setReadPosition(0); + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = false; + break loop; + } else { + // Error + sao.setReadPosition(0); + contRead = false; + throw new ErrorDataDecoderException("Bad end of line"); + } + } else { + if (sao.limit > 0) { + currentpos--; + } + } + } else if (read == HttpConstants.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos - 1; + sao.setReadPosition(0); + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + firstpos = currentpos; + contRead = false; + break loop; + } + break; + default: + // just stop + sao.setReadPosition(0); + contRead = false; + break loop; + } + } + if (isLastChunk && currentAttribute != null) { + // special case + ampersandpos = currentpos; + if (ampersandpos > firstpos) { + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos)); + } else if (!currentAttribute.isCompleted()) { + setFinalBuffer(EMPTY_BUFFER); + } + firstpos = currentpos; + currentStatus = MultiPartStatus.EPILOGUE; + return; + } + if (contRead && currentAttribute != null) { + // reset index except if to continue in case of FIELD status + if (currentStatus == MultiPartStatus.FIELD) { + currentAttribute.addContent(undecodedChunk.slice(firstpos, currentpos - firstpos), false); + firstpos = currentpos; + } + undecodedChunk.readerIndex(firstpos); + } else { + // end of line so keep index + } + } catch (ErrorDataDecoderException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw e; + } catch (IOException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw new ErrorDataDecoderException(e); + } + } + + private void setFinalBuffer(ByteBuf buffer) throws ErrorDataDecoderException, IOException { + currentAttribute.addContent(buffer, true); + String value = decodeAttribute(currentAttribute.getByteBuf().toString(charset), charset); + currentAttribute.setValue(value); + addHttpData(currentAttribute); + currentAttribute = null; + } + + /** + * Decode component + * + * @param s + * @param charset + * @return the decoded component + * @throws ErrorDataDecoderException + */ + private static String decodeAttribute(String s, Charset charset) throws ErrorDataDecoderException { + if (s == null) { + return ""; + } + try { + return URLDecoder.decode(s, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new ErrorDataDecoderException(charset.toString(), e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e); + } + } + + /** + * Parse the Body for multipart + * + * @throws ErrorDataDecoderException + * if there is a problem with the charset decoding or other + * errors + */ + private void parseBodyMultipart() throws ErrorDataDecoderException { + if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) { + // nothing to decode + return; + } + InterfaceHttpData data = decodeMultipart(currentStatus); + while (data != null) { + addHttpData(data); + if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) { + break; + } + data = decodeMultipart(currentStatus); + } + } + + /** + * Decode a multipart request by pieces
+ *
+ * NOTSTARTED PREAMBLE (
+ * (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))*
+ * (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE
+ * (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+
+ * MIXEDCLOSEDELIMITER)*
+ * CLOSEDELIMITER)+ EPILOGUE
+ * + * Inspired from HttpMessageDecoder + * + * @param state + * @return the next decoded InterfaceHttpData or null if none until now. + * @throws ErrorDataDecoderException + * if an error occurs + */ + private InterfaceHttpData decodeMultipart(MultiPartStatus state) throws ErrorDataDecoderException { + switch (state) { + case NOTSTARTED: + throw new ErrorDataDecoderException("Should not be called with the current status"); + case PREAMBLE: + // Content-type: multipart/form-data, boundary=AaB03x + throw new ErrorDataDecoderException("Should not be called with the current status"); + case HEADERDELIMITER: { + // --AaB03x or --AaB03x-- + return findMultipartDelimiter(multipartDataBoundary, MultiPartStatus.DISPOSITION, + MultiPartStatus.PREEPILOGUE); + } + case DISPOSITION: { + // content-disposition: form-data; name="field1" + // content-disposition: form-data; name="pics"; filename="file1.txt" + // and other immediate values like + // Content-type: image/gif + // Content-Type: text/plain + // Content-Type: text/plain; charset=ISO-8859-1 + // Content-Transfer-Encoding: binary + // The following line implies a change of mode (mixed mode) + // Content-type: multipart/mixed, boundary=BbC04y + return findMultipartDisposition(); + } + case FIELD: { + // Now get value according to Content-Type and Charset + Charset localCharset = null; + Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaders.Values.CHARSET); + if (charsetAttribute != null) { + try { + localCharset = Charset.forName(charsetAttribute.getValue()); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + Attribute nameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.NAME); + if (currentAttribute == null) { + try { + currentAttribute = factory.createAttribute(request, nameAttribute.getValue()); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + if (localCharset != null) { + currentAttribute.setCharset(localCharset); + } + } + // load data + try { + loadFieldMultipart(multipartDataBoundary); + } catch (NotEnoughDataDecoderException e) { + return null; + } + Attribute finalAttribute = currentAttribute; + currentAttribute = null; + currentFieldAttributes = null; + // ready to load the next one + currentStatus = MultiPartStatus.HEADERDELIMITER; + return finalAttribute; + } + case FILEUPLOAD: { + // eventually restart from existing FileUpload + return getFileUpload(multipartDataBoundary); + } + case MIXEDDELIMITER: { + // --AaB03x or --AaB03x-- + // Note that currentFieldAttributes exists + return findMultipartDelimiter(multipartMixedBoundary, MultiPartStatus.MIXEDDISPOSITION, + MultiPartStatus.HEADERDELIMITER); + } + case MIXEDDISPOSITION: { + return findMultipartDisposition(); + } + case MIXEDFILEUPLOAD: { + // eventually restart from existing FileUpload + return getFileUpload(multipartMixedBoundary); + } + case PREEPILOGUE: + return null; + case EPILOGUE: + return null; + default: + throw new ErrorDataDecoderException("Shouldn't reach here."); + } + } + + /** + * Skip control Characters + * + * @throws NotEnoughDataDecoderException + */ + void skipControlCharacters() throws NotEnoughDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArrayException e) { + try { + skipControlCharactersStandard(); + } catch (IndexOutOfBoundsException e1) { + throw new NotEnoughDataDecoderException(e1); + } + return; + } + + while (sao.pos < sao.limit) { + char c = (char) (sao.bytes[sao.pos++] & 0xFF); + if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { + sao.setReadPosition(1); + return; + } + } + throw new NotEnoughDataDecoderException("Access out of bounds"); + } + + void skipControlCharactersStandard() { + for (;;) { + char c = (char) undecodedChunk.readUnsignedByte(); + if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1); + break; + } + } + } + + /** + * Find the next Multipart Delimiter + * + * @param delimiter + * delimiter to find + * @param dispositionStatus + * the next status if the delimiter is a start + * @param closeDelimiterStatus + * the next status if the delimiter is a close delimiter + * @return the next InterfaceHttpData if any + * @throws ErrorDataDecoderException + */ + private InterfaceHttpData findMultipartDelimiter(String delimiter, MultiPartStatus dispositionStatus, + MultiPartStatus closeDelimiterStatus) throws ErrorDataDecoderException { + // --AaB03x or --AaB03x-- + int readerIndex = undecodedChunk.readerIndex(); + try { + skipControlCharacters(); + } catch (NotEnoughDataDecoderException e1) { + undecodedChunk.readerIndex(readerIndex); + return null; + } + skipOneLine(); + String newline; + try { + newline = readDelimiter(delimiter); + } catch (NotEnoughDataDecoderException e) { + undecodedChunk.readerIndex(readerIndex); + return null; + } + if (newline.equals(delimiter)) { + currentStatus = dispositionStatus; + return decodeMultipart(dispositionStatus); + } else if (newline.equals(delimiter + "--")) { + // CLOSEDELIMITER or MIXED CLOSEDELIMITER found + currentStatus = closeDelimiterStatus; + if (currentStatus == MultiPartStatus.HEADERDELIMITER) { + // MIXEDCLOSEDELIMITER + // end of the Mixed part + currentFieldAttributes = null; + return decodeMultipart(MultiPartStatus.HEADERDELIMITER); + } + return null; + } + undecodedChunk.readerIndex(readerIndex); + throw new ErrorDataDecoderException("No Multipart delimiter found"); + } + + /** + * Find the next Disposition + * + * @return the next InterfaceHttpData if any + * @throws ErrorDataDecoderException + */ + private InterfaceHttpData findMultipartDisposition() throws ErrorDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + if (currentStatus == MultiPartStatus.DISPOSITION) { + currentFieldAttributes = new TreeMap(CaseIgnoringComparator.INSTANCE); + } + // read many lines until empty line with newline found! Store all data + while (!skipOneLine()) { + String newline; + try { + skipControlCharacters(); + newline = readLine(); + } catch (NotEnoughDataDecoderException e) { + undecodedChunk.readerIndex(readerIndex); + return null; + } + String[] contents = splitMultipartHeader(newline); + if (contents[0].equalsIgnoreCase(HttpPostBodyUtil.CONTENT_DISPOSITION)) { + boolean checkSecondArg = false; + if (currentStatus == MultiPartStatus.DISPOSITION) { + checkSecondArg = contents[1].equalsIgnoreCase(HttpPostBodyUtil.FORM_DATA); + } else { + checkSecondArg = contents[1].equalsIgnoreCase(HttpPostBodyUtil.ATTACHMENT) + || contents[1].equalsIgnoreCase(HttpPostBodyUtil.FILE); + } + if (checkSecondArg) { + // read next values and store them in the map as Attribute + for (int i = 2; i < contents.length; i++) { + String[] values = StringUtil.split(contents[i], '='); + Attribute attribute; + try { + attribute = factory.createAttribute(request, values[0].trim(), + decodeAttribute(cleanString(values[1]), charset)); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(attribute.getName(), attribute); + } + } + } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) { + Attribute attribute; + try { + attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, + cleanString(contents[1])); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, attribute); + } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) { + Attribute attribute; + try { + attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_LENGTH, + cleanString(contents[1])); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(HttpHeaders.Names.CONTENT_LENGTH, attribute); + } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) { + // Take care of possible "multipart/mixed" + if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) { + if (currentStatus == MultiPartStatus.DISPOSITION) { + String[] values = StringUtil.split(contents[2], '='); + multipartMixedBoundary = "--" + values[1]; + currentStatus = MultiPartStatus.MIXEDDELIMITER; + return decodeMultipart(MultiPartStatus.MIXEDDELIMITER); + } else { + throw new ErrorDataDecoderException("Mixed Multipart found in a previous Mixed Multipart"); + } + } else { + for (int i = 1; i < contents.length; i++) { + if (contents[i].toLowerCase().startsWith(HttpHeaders.Values.CHARSET)) { + String[] values = StringUtil.split(contents[i], '='); + Attribute attribute; + try { + attribute = factory.createAttribute(request, HttpHeaders.Values.CHARSET, + cleanString(values[1])); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(HttpHeaders.Values.CHARSET, attribute); + } else { + Attribute attribute; + try { + attribute = factory.createAttribute(request, contents[0].trim(), + decodeAttribute(cleanString(contents[i]), charset)); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(attribute.getName(), attribute); + } + } + } + } else { + throw new ErrorDataDecoderException("Unknown Params: " + newline); + } + } + // Is it a FileUpload + Attribute filenameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.FILENAME); + if (currentStatus == MultiPartStatus.DISPOSITION) { + if (filenameAttribute != null) { + // FileUpload + currentStatus = MultiPartStatus.FILEUPLOAD; + // do not change the buffer position + return decodeMultipart(MultiPartStatus.FILEUPLOAD); + } else { + // Field + currentStatus = MultiPartStatus.FIELD; + // do not change the buffer position + return decodeMultipart(MultiPartStatus.FIELD); + } + } else { + if (filenameAttribute != null) { + // FileUpload + currentStatus = MultiPartStatus.MIXEDFILEUPLOAD; + // do not change the buffer position + return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD); + } else { + // Field is not supported in MIXED mode + throw new ErrorDataDecoderException("Filename not found"); + } + } + } + + /** + * Get the FileUpload (new one or current one) + * + * @param delimiter + * the delimiter to use + * @return the InterfaceHttpData if any + * @throws ErrorDataDecoderException + */ + private InterfaceHttpData getFileUpload(String delimiter) throws ErrorDataDecoderException { + // eventually restart from existing FileUpload + // Now get value according to Content-Type and Charset + Attribute encoding = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING); + Charset localCharset = charset; + // Default + TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7; + if (encoding != null) { + String code; + try { + code = encoding.getValue().toLowerCase(); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value())) { + localCharset = HttpPostBodyUtil.US_ASCII; + } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value())) { + localCharset = HttpPostBodyUtil.ISO_8859_1; + mechanism = TransferEncodingMechanism.BIT8; + } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) { + // no real charset, so let the default + mechanism = TransferEncodingMechanism.BINARY; + } else { + throw new ErrorDataDecoderException("TransferEncoding Unknown: " + code); + } + } + Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaders.Values.CHARSET); + if (charsetAttribute != null) { + try { + localCharset = Charset.forName(charsetAttribute.getValue()); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + if (currentFileUpload == null) { + Attribute filenameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.FILENAME); + Attribute nameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.NAME); + Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_TYPE); + if (contentTypeAttribute == null) { + throw new ErrorDataDecoderException("Content-Type is absent but required"); + } + Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_LENGTH); + long size; + try { + size = lengthAttribute != null ? Long.parseLong(lengthAttribute.getValue()) : 0L; + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } catch (NumberFormatException e) { + size = 0; + } + try { + currentFileUpload = factory.createFileUpload(request, nameAttribute.getValue(), + filenameAttribute.getValue(), contentTypeAttribute.getValue(), mechanism.value(), localCharset, + size); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + // load data as much as possible + try { + readFileUploadByteMultipart(delimiter); + } catch (NotEnoughDataDecoderException e) { + // do not change the buffer position + // since some can be already saved into FileUpload + // So do not change the currentStatus + return null; + } + if (currentFileUpload.isCompleted()) { + // ready to load the next one + if (currentStatus == MultiPartStatus.FILEUPLOAD) { + currentStatus = MultiPartStatus.HEADERDELIMITER; + currentFieldAttributes = null; + } else { + currentStatus = MultiPartStatus.MIXEDDELIMITER; + cleanMixedAttributes(); + } + FileUpload fileUpload = currentFileUpload; + currentFileUpload = null; + return fileUpload; + } + // do not change the buffer position + // since some can be already saved into FileUpload + // So do not change the currentStatus + return null; + } + + /** + * Clean all HttpDatas (on Disk) for the current request. + */ + public void cleanFiles() { + factory.cleanRequestHttpDatas(request); + } + + /** + * Remove the given FileUpload from the list of FileUploads to clean + */ + public void removeHttpDataFromClean(InterfaceHttpData data) { + factory.removeHttpDataFromClean(request, data); + } + + /** + * Remove all Attributes that should be cleaned between two FileUpload in + * Mixed mode + */ + private void cleanMixedAttributes() { + currentFieldAttributes.remove(HttpHeaders.Values.CHARSET); + currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_LENGTH); + currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING); + currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TYPE); + currentFieldAttributes.remove(HttpPostBodyUtil.FILENAME); + } + + /** + * Read one line up to the CRLF or LF + * + * @return the String from one line + * @throws NotEnoughDataDecoderException + * Need more chunks and reset the readerInder to the previous + * value + */ + private String readLineStandard() throws NotEnoughDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + try { + StringBuilder sb = new StringBuilder(64); + while (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.CR) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + return sb.toString(); + } + } else if (nextByte == HttpConstants.LF) { + return sb.toString(); + } else { + sb.append((char) nextByte); + } + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + + /** + * Read one line up to the CRLF or LF + * + * @return the String from one line + * @throws NotEnoughDataDecoderException + * Need more chunks and reset the readerInder to the previous + * value + */ + private String readLine() throws NotEnoughDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArrayException e1) { + return readLineStandard(); + } + int readerIndex = undecodedChunk.readerIndex(); + try { + StringBuilder sb = new StringBuilder(64); + while (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + sao.setReadPosition(0); + return sb.toString(); + } + } else { + sb.append((char) nextByte); + } + } else if (nextByte == HttpConstants.LF) { + sao.setReadPosition(0); + return sb.toString(); + } else { + sb.append((char) nextByte); + } + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + + /** + * Read one line up to --delimiter or --delimiter-- and if existing the CRLF + * or LF Read one line up to --delimiter or --delimiter-- and if existing + * the CRLF or LF. Note that CRLF or LF are mandatory for opening delimiter + * (--delimiter) but not for closing delimiter (--delimiter--) since some + * clients does not include CRLF in this case. + * + * @param delimiter + * of the form --string, such that '--' is already included + * @return the String from one line as the delimiter searched (opening or + * closing) + * @throws NotEnoughDataDecoderException + * Need more chunks and reset the readerInder to the previous + * value + */ + private String readDelimiterStandard(String delimiter) throws NotEnoughDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + try { + StringBuilder sb = new StringBuilder(64); + int delimiterPos = 0; + int len = delimiter.length(); + while (undecodedChunk.readable() && delimiterPos < len) { + byte nextByte = undecodedChunk.readByte(); + if (nextByte == delimiter.charAt(delimiterPos)) { + delimiterPos++; + sb.append((char) nextByte); + } else { + // delimiter not found so break here ! + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + } + // Now check if either opening delimiter or closing delimiter + if (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + // first check for opening delimiter + if (nextByte == HttpConstants.CR) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + return sb.toString(); + } else { + // error since CR must be followed by LF + // delimiter not found so break here ! + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + } else if (nextByte == HttpConstants.LF) { + return sb.toString(); + } else if (nextByte == '-') { + sb.append((char) nextByte); + // second check for closing delimiter + nextByte = undecodedChunk.readByte(); + if (nextByte == '-') { + sb.append((char) nextByte); + // now try to find if CRLF or LF there + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.CR) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + return sb.toString(); + } else { + // error CR without LF + // delimiter not found so break here ! + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + } else if (nextByte == HttpConstants.LF) { + return sb.toString(); + } else { + // No CRLF but ok however (Adobe Flash uploader) + // minus 1 since we read one char ahead but + // should not + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1); + return sb.toString(); + } + } + // FIXME what do we do here? + // either considering it is fine, either waiting for + // more data to come? + // lets try considering it is fine... + return sb.toString(); + } + // only one '-' => not enough + // whatever now => error since incomplete + } + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + + /** + * Read one line up to --delimiter or --delimiter-- and if existing the CRLF + * or LF. Note that CRLF or LF are mandatory for opening delimiter + * (--delimiter) but not for closing delimiter (--delimiter--) since some + * clients does not include CRLF in this case. + * + * @param delimiter + * of the form --string, such that '--' is already included + * @return the String from one line as the delimiter searched (opening or + * closing) + * @throws NotEnoughDataDecoderException + * Need more chunks and reset the readerInder to the previous + * value + */ + private String readDelimiter(String delimiter) throws NotEnoughDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArrayException e1) { + return readDelimiterStandard(delimiter); + } + int readerIndex = undecodedChunk.readerIndex(); + int delimiterPos = 0; + int len = delimiter.length(); + try { + StringBuilder sb = new StringBuilder(64); + // check conformity with delimiter + while (sao.pos < sao.limit && delimiterPos < len) { + byte nextByte = sao.bytes[sao.pos++]; + if (nextByte == delimiter.charAt(delimiterPos)) { + delimiterPos++; + sb.append((char) nextByte); + } else { + // delimiter not found so break here ! + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + } + // Now check if either opening delimiter or closing delimiter + if (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.CR) { + // first check for opening delimiter + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + sao.setReadPosition(0); + return sb.toString(); + } + } else { + // error since CR must be followed by LF + // delimiter not found so break here ! + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + } else if (nextByte == HttpConstants.LF) { + // same first check for opening delimiter where LF used with + // no CR + sao.setReadPosition(0); + return sb.toString(); + } else if (nextByte == '-') { + sb.append((char) nextByte); + // second check for closing delimiter + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == '-') { + sb.append((char) nextByte); + // now try to find if CRLF or LF there + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + sao.setReadPosition(0); + return sb.toString(); + } + } else { + // error CR without LF + // delimiter not found so break here ! + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + } else if (nextByte == HttpConstants.LF) { + sao.setReadPosition(0); + return sb.toString(); + } else { + // No CRLF but ok however (Adobe Flash + // uploader) + // minus 1 since we read one char ahead but + // should not + sao.setReadPosition(1); + return sb.toString(); + } + } + // FIXME what do we do here? + // either considering it is fine, either waiting for + // more data to come? + // lets try considering it is fine... + sao.setReadPosition(0); + return sb.toString(); + } + // whatever now => error since incomplete + // only one '-' => not enough or whatever not enough + // element + } + } + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + + /** + * Read a FileUpload data as Byte (Binary) and add the bytes directly to the + * FileUpload. If the delimiter is found, the FileUpload is completed. + * + * @param delimiter + * @throws NotEnoughDataDecoderException + * Need more chunks but do not reset the readerInder since some + * values will be already added to the FileOutput + * @throws ErrorDataDecoderException + * write IO error occurs with the FileUpload + */ + private void readFileUploadByteMultipartStandard(String delimiter) throws NotEnoughDataDecoderException, + ErrorDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + while (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index++; + if (delimiter.length() == index) { + found = true; + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + // save last valid position + lastPosition = undecodedChunk.readerIndex(); + } + } + } else { + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + // save last valid position + lastPosition = undecodedChunk.readerIndex(); + } + } + } + ByteBuf buffer = undecodedChunk.slice(readerIndex, lastPosition - readerIndex); + if (found) { + // found so lastPosition is correct and final + try { + currentFileUpload.addContent(buffer, true); + // just before the CRLF and delimiter + undecodedChunk.readerIndex(lastPosition); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } else { + // possibly the delimiter is partially found but still the last + // position is OK + try { + currentFileUpload.addContent(buffer, false); + // last valid char (not CR, not LF, not beginning of delimiter) + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + } + + /** + * Read a FileUpload data as Byte (Binary) and add the bytes directly to the + * FileUpload. If the delimiter is found, the FileUpload is completed. + * + * @param delimiter + * @throws NotEnoughDataDecoderException + * Need more chunks but do not reset the readerInder since some + * values will be already added to the FileOutput + * @throws ErrorDataDecoderException + * write IO error occurs with the FileUpload + */ + private void readFileUploadByteMultipart(String delimiter) throws NotEnoughDataDecoderException, + ErrorDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArrayException e1) { + readFileUploadByteMultipartStandard(delimiter); + return; + } + int readerIndex = undecodedChunk.readerIndex(); + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastrealpos = sao.pos; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + + while (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos++]; + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index++; + if (delimiter.length() == index) { + found = true; + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 1; + } else { + // save last valid position + lastrealpos = sao.pos; + } + } + } else { + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 1; + } else { + // save last valid position + lastrealpos = sao.pos; + } + } + } + lastPosition = sao.getReadPosition(lastrealpos); + ByteBuf buffer = undecodedChunk.slice(readerIndex, lastPosition - readerIndex); + if (found) { + // found so lastPosition is correct and final + try { + currentFileUpload.addContent(buffer, true); + // just before the CRLF and delimiter + undecodedChunk.readerIndex(lastPosition); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } else { + // possibly the delimiter is partially found but still the last + // position is OK + try { + currentFileUpload.addContent(buffer, false); + // last valid char (not CR, not LF, not beginning of delimiter) + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + } + + /** + * Load the field value from a Multipart request + * + * @throws NotEnoughDataDecoderException + * Need more chunks + * @throws ErrorDataDecoderException + */ + private void loadFieldMultipartStandard(String delimiter) throws NotEnoughDataDecoderException, + ErrorDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + try { + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + while (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index++; + if (delimiter.length() == index) { + found = true; + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + lastPosition = undecodedChunk.readerIndex(); + } + } + } else { + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + lastPosition = undecodedChunk.readerIndex(); + } + } + } + if (found) { + // found so lastPosition is correct + // but position is just after the delimiter (either close + // delimiter or simple one) + // so go back of delimiter size + try { + currentAttribute.addContent(undecodedChunk.slice(readerIndex, lastPosition - readerIndex), true); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + } else { + try { + currentAttribute.addContent(undecodedChunk.slice(readerIndex, lastPosition - readerIndex), false); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + } + + /** + * Load the field value from a Multipart request + * + * @throws NotEnoughDataDecoderException + * Need more chunks + * @throws ErrorDataDecoderException + */ + private void loadFieldMultipart(String delimiter) throws NotEnoughDataDecoderException, ErrorDataDecoderException { + SeekAheadOptimize sao = null; + try { + sao = new SeekAheadOptimize(undecodedChunk); + } catch (SeekAheadNoBackArrayException e1) { + loadFieldMultipartStandard(delimiter); + return; + } + int readerIndex = undecodedChunk.readerIndex(); + try { + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + int lastrealpos = sao.pos; + boolean found = false; + + while (sao.pos < sao.limit) { + byte nextByte = sao.bytes[sao.pos++]; + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index++; + if (delimiter.length() == index) { + found = true; + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 1; + } else { + lastrealpos = sao.pos; + } + } + } else { + // continue until end of line + if (nextByte == HttpConstants.CR) { + if (sao.pos < sao.limit) { + nextByte = sao.bytes[sao.pos++]; + if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 2; + } + } + } else if (nextByte == HttpConstants.LF) { + newLine = true; + index = 0; + lastrealpos = sao.pos - 1; + } else { + lastrealpos = sao.pos; + } + } + } + lastPosition = sao.getReadPosition(lastrealpos); + if (found) { + // found so lastPosition is correct + // but position is just after the delimiter (either close + // delimiter or simple one) + // so go back of delimiter size + try { + currentAttribute.addContent(undecodedChunk.slice(readerIndex, lastPosition - readerIndex), true); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + } else { + try { + currentAttribute.addContent(undecodedChunk.slice(readerIndex, lastPosition - readerIndex), false); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + } + + /** + * Clean the String from any unallowed character + * + * @return the cleaned String + */ + private static String cleanString(String field) { + StringBuilder sb = new StringBuilder(field.length()); + int i = 0; + for (i = 0; i < field.length(); i++) { + char nextChar = field.charAt(i); + if (nextChar == HttpConstants.COLON) { + sb.append(HttpConstants.SP); + } else if (nextChar == HttpConstants.COMMA) { + sb.append(HttpConstants.SP); + } else if (nextChar == HttpConstants.EQUALS) { + sb.append(HttpConstants.SP); + } else if (nextChar == HttpConstants.SEMICOLON) { + sb.append(HttpConstants.SP); + } else if (nextChar == HttpConstants.HT) { + sb.append(HttpConstants.SP); + } else if (nextChar == HttpConstants.DOUBLE_QUOTE) { + // nothing added, just removes it + } else { + sb.append(nextChar); + } + } + return sb.toString().trim(); + } + + /** + * Skip one empty line + * + * @return True if one empty line was skipped + */ + private boolean skipOneLine() { + if (!undecodedChunk.readable()) { + return false; + } + byte nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.CR) { + if (!undecodedChunk.readable()) { + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1); + return false; + } + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpConstants.LF) { + return true; + } + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2); + return false; + } else if (nextByte == HttpConstants.LF) { + return true; + } + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1); + return false; + } + + /** + * Split the very first line (Content-Type value) in 2 Strings + * + * @param sb + * @return the array of 2 Strings + */ + private static String[] splitHeaderContentType(String sb) { + int size = sb.length(); + int aStart; + int aEnd; + int bStart; + int bEnd; + aStart = HttpPostBodyUtil.findNonWhitespace(sb, 0); + aEnd = HttpPostBodyUtil.findWhitespace(sb, aStart); + if (aEnd >= size) { + return new String[] { sb, "" }; + } + if (sb.charAt(aEnd) == ';') { + aEnd--; + } + bStart = HttpPostBodyUtil.findNonWhitespace(sb, aEnd); + bEnd = HttpPostBodyUtil.findEndOfString(sb); + return new String[] { sb.substring(aStart, aEnd), sb.substring(bStart, bEnd) }; + } + + /** + * Split one header in Multipart + * + * @param sb + * @return an array of String where rank 0 is the name of the header, + * follows by several values that were separated by ';' or ',' + */ + private static String[] splitMultipartHeader(String sb) { + ArrayList headers = new ArrayList(1); + int nameStart; + int nameEnd; + int colonEnd; + int valueStart; + int valueEnd; + nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0); + for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd++) { + char ch = sb.charAt(nameEnd); + if (ch == ':' || Character.isWhitespace(ch)) { + break; + } + } + for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd++) { + if (sb.charAt(colonEnd) == ':') { + colonEnd++; + break; + } + } + valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd); + valueEnd = HttpPostBodyUtil.findEndOfString(sb); + headers.add(sb.substring(nameStart, nameEnd)); + String svalue = sb.substring(valueStart, valueEnd); + String[] values = null; + if (svalue.indexOf(';') >= 0) { + values = StringUtil.split(svalue, ';'); + } else { + values = StringUtil.split(svalue, ','); + } + for (String value : values) { + headers.add(value.trim()); + } + String[] array = new String[headers.size()]; + for (int i = 0; i < headers.size(); i++) { + array[i] = headers.get(i); + } + return array; + } + + /** + * Exception when try reading data from request in chunked format, and not + * enough data are available (need more chunks) + */ + public static class NotEnoughDataDecoderException extends Exception { + /** + */ + private static final long serialVersionUID = -7846841864603865638L; + + /** + */ + public NotEnoughDataDecoderException() { + } + + /** + * @param arg0 + */ + public NotEnoughDataDecoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public NotEnoughDataDecoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public NotEnoughDataDecoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } + + /** + * Exception when the body is fully decoded, even if there is still data + */ + public static class EndOfDataDecoderException extends Exception { + /** + */ + private static final long serialVersionUID = 1336267941020800769L; + + } + + /** + * Exception when an error occurs while decoding + */ + public static class ErrorDataDecoderException extends Exception { + /** + */ + private static final long serialVersionUID = 5020247425493164465L; + + /** + */ + public ErrorDataDecoderException() { + } + + /** + * @param arg0 + */ + public ErrorDataDecoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public ErrorDataDecoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public ErrorDataDecoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } + + /** + * Exception when an unappropriated method was called on a request + */ + public static class IncompatibleDataDecoderException extends Exception { + /** + */ + private static final long serialVersionUID = -953268047926250267L; + + /** + */ + public IncompatibleDataDecoderException() { + } + + /** + * @param arg0 + */ + public IncompatibleDataDecoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public IncompatibleDataDecoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public IncompatibleDataDecoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java new file mode 100644 index 00000000000..b379f599ffd --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java @@ -0,0 +1,1025 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.MessageBuf; +import io.netty.handler.codec.http.DefaultHttpChunk; +import io.netty.handler.codec.http.HttpChunk; +import io.netty.handler.codec.http.HttpConstants; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpTransferEncoding; +import io.netty.handler.stream.ChunkedMessageInput; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Random; + +import static io.netty.buffer.Unpooled.*; + +/** + * This encoder will help to encode Request for a FORM as POST. + */ +public class HttpPostRequestEncoder implements ChunkedMessageInput { + /** + * Factory used to create InterfaceHttpData + */ + private final HttpDataFactory factory; + + /** + * Request to encode + */ + private final HttpRequest request; + + /** + * Default charset to use + */ + private final Charset charset; + + /** + * Chunked false by default + */ + private boolean isChunked; + + /** + * InterfaceHttpData for Body (without encoding) + */ + private final List bodyListDatas; + /** + * The final Multipart List of InterfaceHttpData including encoding + */ + private final List multipartHttpDatas; + + /** + * Does this request is a Multipart request + */ + private final boolean isMultipart; + + /** + * If multipart, this is the boundary for the flobal multipart + */ + private String multipartDataBoundary; + + /** + * If multipart, there could be internal multiparts (mixed) to the global multipart. Only one level is allowed. + */ + private String multipartMixedBoundary; + /** + * To check if the header has been finalized + */ + private boolean headerFinalized; + + /** + * + * @param request + * the request to encode + * @param multipart + * True if the FORM is a ENCTYPE="multipart/form-data" + * @throws NullPointerException + * for request + * @throws ErrorDataEncoderException + * if the request is not a POST + */ + public HttpPostRequestEncoder(HttpRequest request, boolean multipart) throws ErrorDataEncoderException { + this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart, + HttpConstants.DEFAULT_CHARSET); + } + + /** + * + * @param factory + * the factory used to create InterfaceHttpData + * @param request + * the request to encode + * @param multipart + * True if the FORM is a ENCTYPE="multipart/form-data" + * @throws NullPointerException + * for request and factory + * @throws ErrorDataEncoderException + * if the request is not a POST + */ + public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart) + throws ErrorDataEncoderException { + this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET); + } + + /** + * + * @param factory + * the factory used to create InterfaceHttpData + * @param request + * the request to encode + * @param multipart + * True if the FORM is a ENCTYPE="multipart/form-data" + * @param charset + * the charset to use as default + * @throws NullPointerException + * for request or charset or factory + * @throws ErrorDataEncoderException + * if the request is not a POST + */ + public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset) + throws ErrorDataEncoderException { + if (factory == null) { + throw new NullPointerException("factory"); + } + if (request == null) { + throw new NullPointerException("request"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + if (request.getMethod() != HttpMethod.POST) { + throw new ErrorDataEncoderException("Cannot create a Encoder if not a POST"); + } + this.request = request; + this.charset = charset; + this.factory = factory; + // Fill default values + bodyListDatas = new ArrayList(); + // default mode + isLastChunk = false; + isLastChunkSent = false; + isMultipart = multipart; + multipartHttpDatas = new ArrayList(); + if (isMultipart) { + initDataMultipart(); + } + } + + /** + * Clean all HttpDatas (on Disk) for the current request. + */ + public void cleanFiles() { + factory.cleanRequestHttpDatas(request); + } + + /** + * Does the last non empty chunk already encoded so that next chunk will be empty (last chunk) + */ + private boolean isLastChunk; + /** + * Last chunk already sent + */ + private boolean isLastChunkSent; + /** + * The current FileUpload that is currently in encode process + */ + private FileUpload currentFileUpload; + /** + * While adding a FileUpload, is the multipart currently in Mixed Mode + */ + private boolean duringMixedMode; + + /** + * Global Body size + */ + private long globalBodySize; + + /** + * True if this request is a Multipart request + * + * @return True if this request is a Multipart request + */ + public boolean isMultipart() { + return isMultipart; + } + + /** + * Init the delimiter for Global Part (Data). + */ + private void initDataMultipart() { + multipartDataBoundary = getNewMultipartDelimiter(); + } + + /** + * Init the delimiter for Mixed Part (Mixed). + */ + private void initMixedMultipart() { + multipartMixedBoundary = getNewMultipartDelimiter(); + } + + /** + * + * @return a newly generated Delimiter (either for DATA or MIXED) + */ + private static String getNewMultipartDelimiter() { + // construct a generated delimiter + Random random = new Random(); + return Long.toHexString(random.nextLong()).toLowerCase(); + } + + /** + * This method returns a List of all InterfaceHttpData from body part.
+ + * @return the list of InterfaceHttpData from Body part + */ + public List getBodyListAttributes() { + return bodyListDatas; + } + + /** + * Set the Body HttpDatas list + * + * @param datas + * @throws NullPointerException + * for datas + * @throws ErrorDataEncoderException + * if the encoding is in error or if the finalize were already done + */ + public void setBodyHttpDatas(List datas) throws ErrorDataEncoderException { + if (datas == null) { + throw new NullPointerException("datas"); + } + globalBodySize = 0; + bodyListDatas.clear(); + currentFileUpload = null; + duringMixedMode = false; + multipartHttpDatas.clear(); + for (InterfaceHttpData data : datas) { + addBodyHttpData(data); + } + } + + /** + * Add a simple attribute in the body as Name=Value + * + * @param name + * name of the parameter + * @param value + * the value of the parameter + * @throws NullPointerException + * for name + * @throws ErrorDataEncoderException + * if the encoding is in error or if the finalize were already done + */ + public void addBodyAttribute(String name, String value) throws ErrorDataEncoderException { + if (name == null) { + throw new NullPointerException("name"); + } + String svalue = value; + if (value == null) { + svalue = ""; + } + Attribute data = factory.createAttribute(request, name, svalue); + addBodyHttpData(data); + } + + /** + * Add a file as a FileUpload + * + * @param name + * the name of the parameter + * @param file + * the file to be uploaded (if not Multipart mode, only the filename will be included) + * @param contentType + * the associated contentType for the File + * @param isText + * True if this file should be transmitted in Text format (else binary) + * @throws NullPointerException + * for name and file + * @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) + throws ErrorDataEncoderException { + if (name == null) { + throw new NullPointerException("name"); + } + if (file == null) { + throw new NullPointerException("file"); + } + String scontentType = contentType; + String contentTransferEncoding = null; + if (contentType == null) { + if (isText) { + scontentType = HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE; + } else { + scontentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE; + } + } + if (!isText) { + contentTransferEncoding = HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value(); + } + FileUpload fileUpload = factory.createFileUpload(request, name, file.getName(), scontentType, + contentTransferEncoding, null, file.length()); + try { + fileUpload.setContent(file); + } catch (IOException e) { + throw new ErrorDataEncoderException(e); + } + addBodyHttpData(fileUpload); + } + + /** + * Add a series of Files associated with one File parameter (implied Mixed mode in Multipart) + * + * @param name + * the name of the parameter + * @param file + * the array of files + * @param contentType + * the array of content Types associated with each file + * @param isText + * the array of isText attribute (False meaning binary mode) for each file + * @throws NullPointerException + * also throws if array have different sizes + * @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) + throws ErrorDataEncoderException { + if (file.length != contentType.length && file.length != isText.length) { + throw new NullPointerException("Different array length"); + } + for (int i = 0; i < file.length; i++) { + addBodyFileUpload(name, file[i], contentType[i], isText[i]); + } + } + + /** + * Add the InterfaceHttpData to the Body list + * + * @param data + * @throws NullPointerException + * for data + * @throws ErrorDataEncoderException + * if the encoding is in error or if the finalize were already done + */ + public void addBodyHttpData(InterfaceHttpData data) throws ErrorDataEncoderException { + if (headerFinalized) { + throw new ErrorDataEncoderException("Cannot add value once finalized"); + } + if (data == null) { + throw new NullPointerException("data"); + } + bodyListDatas.add(data); + if (!isMultipart) { + if (data instanceof Attribute) { + Attribute attribute = (Attribute) data; + try { + // name=value& with encoded name and attribute + String key = encodeAttribute(attribute.getName(), charset); + String value = encodeAttribute(attribute.getValue(), charset); + Attribute newattribute = factory.createAttribute(request, key, value); + multipartHttpDatas.add(newattribute); + globalBodySize += newattribute.getName().length() + 1 + newattribute.length() + 1; + } catch (IOException e) { + throw new ErrorDataEncoderException(e); + } + } else if (data instanceof FileUpload) { + // since not Multipart, only name=filename => Attribute + FileUpload fileUpload = (FileUpload) data; + // name=filename& with encoded name and filename + String key = encodeAttribute(fileUpload.getName(), charset); + String value = encodeAttribute(fileUpload.getFilename(), charset); + Attribute newattribute = factory.createAttribute(request, key, value); + multipartHttpDatas.add(newattribute); + globalBodySize += newattribute.getName().length() + 1 + newattribute.length() + 1; + } + return; + } + /* + * Logic: + * if not Attribute: + * add Data to body list + * if (duringMixedMode) + * add endmixedmultipart delimiter + * currentFileUpload = null + * duringMixedMode = false; + * add multipart delimiter, multipart body header and Data to multipart list + * reset currentFileUpload, duringMixedMode + * if FileUpload: take care of multiple file for one field => mixed mode + * if (duringMixeMode) + * if (currentFileUpload.name == data.name) + * add mixedmultipart delimiter, mixedmultipart body header and Data to multipart list + * else + * add endmixedmultipart delimiter, multipart body header and Data to multipart list + * currentFileUpload = data + * duringMixedMode = false; + * else + * if (currentFileUpload.name == data.name) + * change multipart body header of previous file into multipart list to + * mixedmultipart start, mixedmultipart body header + * add mixedmultipart delimiter, mixedmultipart body header and Data to multipart list + * duringMixedMode = true + * else + * add multipart delimiter, multipart body header and Data to multipart list + * currentFileUpload = data + * duringMixedMode = false; + * Do not add last delimiter! Could be: + * if duringmixedmode: endmixedmultipart + endmultipart + * else only endmultipart + */ + if (data instanceof Attribute) { + if (duringMixedMode) { + InternalAttribute internal = new InternalAttribute(); + internal.addValue("\r\n--" + multipartMixedBoundary + "--"); + multipartHttpDatas.add(internal); + multipartMixedBoundary = null; + currentFileUpload = null; + duringMixedMode = false; + } + InternalAttribute internal = new InternalAttribute(); + if (!multipartHttpDatas.isEmpty()) { + // previously a data field so CRLF + internal.addValue("\r\n"); + } + internal.addValue("--" + multipartDataBoundary + "\r\n"); + // content-disposition: form-data; name="field1" + Attribute attribute = (Attribute) data; + internal.addValue(HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + HttpPostBodyUtil.FORM_DATA + "; " + + HttpPostBodyUtil.NAME + "=\"" + encodeAttribute(attribute.getName(), charset) + "\"\r\n"); + Charset localcharset = attribute.getCharset(); + if (localcharset != null) { + // Content-Type: charset=charset + internal.addValue(HttpHeaders.Names.CONTENT_TYPE + ": " + HttpHeaders.Values.CHARSET + '=' + + localcharset + "\r\n"); + } + // CRLF between body header and data + internal.addValue("\r\n"); + multipartHttpDatas.add(internal); + multipartHttpDatas.add(data); + globalBodySize += attribute.length() + internal.size(); + } else if (data instanceof FileUpload) { + FileUpload fileUpload = (FileUpload) data; + InternalAttribute internal = new InternalAttribute(); + if (!multipartHttpDatas.isEmpty()) { + // previously a data field so CRLF + internal.addValue("\r\n"); + } + boolean localMixed = false; + if (duringMixedMode) { + if (currentFileUpload != null && currentFileUpload.getName().equals(fileUpload.getName())) { + // continue a mixed mode + + localMixed = true; + } else { + // end a mixed mode + + // add endmixedmultipart delimiter, multipart body header + // and + // Data to multipart list + internal.addValue("--" + multipartMixedBoundary + "--"); + multipartHttpDatas.add(internal); + multipartMixedBoundary = null; + // start a new one (could be replaced if mixed start again + // from here + internal = new InternalAttribute(); + internal.addValue("\r\n"); + localMixed = false; + // new currentFileUpload and no more in Mixed mode + currentFileUpload = fileUpload; + duringMixedMode = false; + } + } else { + if (currentFileUpload != null && currentFileUpload.getName().equals(fileUpload.getName())) { + // create a new mixed mode (from previous file) + + // change multipart body header of previous file into + // multipart list to + // mixedmultipart start, mixedmultipart body header + + // change Internal (size()-2 position in multipartHttpDatas) + // from (line starting with *) + // --AaB03x + // * Content-Disposition: form-data; name="files"; + // filename="file1.txt" + // Content-Type: text/plain + // to (lines starting with *) + // --AaB03x + // * Content-Disposition: form-data; name="files" + // * Content-Type: multipart/mixed; boundary=BbC04y + // * + // * --BbC04y + // * Content-Disposition: file; filename="file1.txt" + // Content-Type: text/plain + initMixedMultipart(); + InternalAttribute pastAttribute = (InternalAttribute) multipartHttpDatas.get(multipartHttpDatas + .size() - 2); + // remove past size + globalBodySize -= pastAttribute.size(); + String replacement = HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + HttpPostBodyUtil.FORM_DATA + + "; " + HttpPostBodyUtil.NAME + "=\"" + encodeAttribute(fileUpload.getName(), charset) + + "\"\r\n"; + replacement += HttpHeaders.Names.CONTENT_TYPE + ": " + HttpPostBodyUtil.MULTIPART_MIXED + "; " + + HttpHeaders.Values.BOUNDARY + '=' + multipartMixedBoundary + "\r\n\r\n"; + replacement += "--" + multipartMixedBoundary + "\r\n"; + replacement += HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + HttpPostBodyUtil.FILE + "; " + + HttpPostBodyUtil.FILENAME + "=\"" + encodeAttribute(fileUpload.getFilename(), charset) + + "\"\r\n"; + pastAttribute.setValue(replacement, 1); + // update past size + globalBodySize += pastAttribute.size(); + + // now continue + // add mixedmultipart delimiter, mixedmultipart body header + // and + // Data to multipart list + localMixed = true; + duringMixedMode = true; + } else { + // a simple new multipart + // add multipart delimiter, multipart body header and Data + // to multipart list + localMixed = false; + currentFileUpload = fileUpload; + duringMixedMode = false; + } + } + + if (localMixed) { + // add mixedmultipart delimiter, mixedmultipart body header and + // Data to multipart list + internal.addValue("--" + multipartMixedBoundary + "\r\n"); + // Content-Disposition: file; filename="file1.txt" + internal.addValue(HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + HttpPostBodyUtil.FILE + "; " + + HttpPostBodyUtil.FILENAME + "=\"" + encodeAttribute(fileUpload.getFilename(), charset) + + "\"\r\n"); + + } else { + internal.addValue("--" + multipartDataBoundary + "\r\n"); + // Content-Disposition: form-data; name="files"; + // filename="file1.txt" + internal.addValue(HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + HttpPostBodyUtil.FORM_DATA + "; " + + HttpPostBodyUtil.NAME + "=\"" + encodeAttribute(fileUpload.getName(), charset) + "\"; " + + HttpPostBodyUtil.FILENAME + "=\"" + encodeAttribute(fileUpload.getFilename(), charset) + + "\"\r\n"); + } + // Content-Type: image/gif + // Content-Type: text/plain; charset=ISO-8859-1 + // Content-Transfer-Encoding: binary + internal.addValue(HttpHeaders.Names.CONTENT_TYPE + ": " + fileUpload.getContentType()); + String contentTransferEncoding = fileUpload.getContentTransferEncoding(); + if (contentTransferEncoding != null + && contentTransferEncoding.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) { + internal.addValue("\r\n" + HttpHeaders.Names.CONTENT_TRANSFER_ENCODING + ": " + + HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value() + "\r\n\r\n"); + } else if (fileUpload.getCharset() != null) { + internal.addValue("; " + HttpHeaders.Values.CHARSET + '=' + fileUpload.getCharset() + "\r\n\r\n"); + } else { + internal.addValue("\r\n\r\n"); + } + multipartHttpDatas.add(internal); + multipartHttpDatas.add(data); + globalBodySize += fileUpload.length() + internal.size(); + } + } + + /** + * Iterator to be used when encoding will be called chunk after chunk + */ + private ListIterator iterator; + + /** + * Finalize the request by preparing the Header in the request and returns the request ready to be sent.
+ * Once finalized, no data must be added.
+ * If the request does not need chunk (isChunked() == false), this request is the only object to send to the remote + * server. + * + * @return the request object (chunked or not according to size of body) + * @throws ErrorDataEncoderException + * if the encoding is in error or if the finalize were already done + */ + public HttpRequest finalizeRequest() throws ErrorDataEncoderException { + // Finalize the multipartHttpDatas + if (!headerFinalized) { + if (isMultipart) { + InternalAttribute internal = new InternalAttribute(); + if (duringMixedMode) { + internal.addValue("\r\n--" + multipartMixedBoundary + "--"); + } + internal.addValue("\r\n--" + multipartDataBoundary + "--\r\n"); + multipartHttpDatas.add(internal); + multipartMixedBoundary = null; + currentFileUpload = null; + duringMixedMode = false; + globalBodySize += internal.size(); + } + headerFinalized = true; + } else { + throw new ErrorDataEncoderException("Header already encoded"); + } + List contentTypes = request.getHeaders(HttpHeaders.Names.CONTENT_TYPE); + List transferEncoding = request.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); + if (contentTypes != null) { + request.removeHeader(HttpHeaders.Names.CONTENT_TYPE); + for (String contentType : contentTypes) { + // "multipart/form-data; boundary=--89421926422648" + if (contentType.toLowerCase().startsWith(HttpHeaders.Values.MULTIPART_FORM_DATA)) { + // ignore + } else if (contentType.toLowerCase().startsWith(HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED)) { + // ignore + } else { + request.addHeader(HttpHeaders.Names.CONTENT_TYPE, contentType); + } + } + } + if (isMultipart) { + String value = HttpHeaders.Values.MULTIPART_FORM_DATA + "; " + HttpHeaders.Values.BOUNDARY + '=' + + multipartDataBoundary; + request.addHeader(HttpHeaders.Names.CONTENT_TYPE, value); + } else { + // Not multipart + request.addHeader(HttpHeaders.Names.CONTENT_TYPE, HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED); + } + // Now consider size for chunk or not + long realSize = globalBodySize; + if (isMultipart) { + iterator = multipartHttpDatas.listIterator(); + } else { + realSize -= 1; // last '&' removed + iterator = multipartHttpDatas.listIterator(); + } + request.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(realSize)); + if (realSize > HttpPostBodyUtil.chunkSize || isMultipart) { + isChunked = true; + if (transferEncoding != null) { + request.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + for (String v : transferEncoding) { + if (v.equalsIgnoreCase(HttpHeaders.Values.CHUNKED)) { + // ignore + } else { + request.addHeader(HttpHeaders.Names.TRANSFER_ENCODING, v); + } + } + } + request.setTransferEncoding(HttpTransferEncoding.CHUNKED); + request.setContent(EMPTY_BUFFER); + } else { + // get the only one body and set it to the request + HttpChunk chunk = nextChunk(); + request.setContent(chunk.getContent()); + } + return request; + } + + /** + * @return True if the request is by Chunk + */ + public boolean isChunked() { + return isChunked; + } + + /** + * Encode one attribute + * + * @param s + * @param charset + * @return the encoded attribute + * @throws ErrorDataEncoderException + * if the encoding is in error + */ + private static String encodeAttribute(String s, Charset charset) throws ErrorDataEncoderException { + if (s == null) { + return ""; + } + try { + return URLEncoder.encode(s, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new ErrorDataEncoderException(charset.name(), e); + } + } + + /** + * The ByteBuf currently used by the encoder + */ + private ByteBuf currentBuffer; + /** + * The current InterfaceHttpData to encode (used if more chunks are available) + */ + private InterfaceHttpData currentData; + /** + * If not multipart, does the currentBuffer stands for the Key or for the Value + */ + private boolean isKey = true; + + /** + * + * @return the next ByteBuf to send as a HttpChunk and modifying currentBuffer accordingly + */ + private ByteBuf fillByteBuf() { + int length = currentBuffer.readableBytes(); + if (length > HttpPostBodyUtil.chunkSize) { + ByteBuf slice = currentBuffer.slice(currentBuffer.readerIndex(), HttpPostBodyUtil.chunkSize); + currentBuffer.skipBytes(HttpPostBodyUtil.chunkSize); + return slice; + } else { + // to continue + ByteBuf slice = currentBuffer; + currentBuffer = null; + return slice; + } + } + + /** + * From the current context (currentBuffer and currentData), returns the next HttpChunk (if possible) trying to get + * sizeleft bytes more into the currentBuffer. This is the Multipart version. + * + * @param sizeleft + * the number of bytes to try to get from currentData + * @return the next HttpChunk or null if not enough bytes were found + * @throws ErrorDataEncoderException + * if the encoding is in error + */ + private HttpChunk encodeNextChunkMultipart(int sizeleft) throws ErrorDataEncoderException { + if (currentData == null) { + return null; + } + ByteBuf buffer; + if (currentData instanceof InternalAttribute) { + String internal = currentData.toString(); + byte[] bytes; + try { + bytes = internal.getBytes("ASCII"); + } catch (UnsupportedEncodingException e) { + throw new ErrorDataEncoderException(e); + } + buffer = wrappedBuffer(bytes); + currentData = null; + } else { + if (currentData instanceof Attribute) { + try { + buffer = ((Attribute) currentData).getChunk(sizeleft); + } catch (IOException e) { + throw new ErrorDataEncoderException(e); + } + } else { + try { + buffer = ((HttpData) currentData).getChunk(sizeleft); + } catch (IOException e) { + throw new ErrorDataEncoderException(e); + } + } + if (buffer.capacity() == 0) { + // end for current InterfaceHttpData, need more data + currentData = null; + return null; + } + } + if (currentBuffer == null) { + currentBuffer = buffer; + } else { + currentBuffer = wrappedBuffer(currentBuffer, buffer); + } + if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) { + currentData = null; + return null; + } + buffer = fillByteBuf(); + return new DefaultHttpChunk(buffer); + } + + /** + * From the current context (currentBuffer and currentData), returns the next HttpChunk (if possible) trying to get + * sizeleft bytes more into the currentBuffer. This is the UrlEncoded version. + * + * @param sizeleft + * the number of bytes to try to get from currentData + * @return the next HttpChunk or null if not enough bytes were found + * @throws ErrorDataEncoderException + * if the encoding is in error + */ + private HttpChunk encodeNextChunkUrlEncoded(int sizeleft) throws ErrorDataEncoderException { + if (currentData == null) { + return null; + } + int size = sizeleft; + ByteBuf buffer; + + // Set name= + if (isKey) { + String key = currentData.getName(); + buffer = wrappedBuffer(key.getBytes()); + isKey = false; + if (currentBuffer == null) { + currentBuffer = wrappedBuffer(buffer, wrappedBuffer("=".getBytes())); + // continue + size -= buffer.readableBytes() + 1; + } else { + currentBuffer = wrappedBuffer(currentBuffer, buffer, wrappedBuffer("=".getBytes())); + // continue + size -= buffer.readableBytes() + 1; + } + if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) { + buffer = fillByteBuf(); + return new DefaultHttpChunk(buffer); + } + } + + // Put value into buffer + try { + buffer = ((HttpData) currentData).getChunk(size); + } catch (IOException e) { + throw new ErrorDataEncoderException(e); + } + + // Figure out delimiter + ByteBuf delimiter = null; + if (buffer.readableBytes() < size) { + isKey = true; + delimiter = iterator.hasNext() ? wrappedBuffer("&".getBytes()) : null; + } + + // End for current InterfaceHttpData, need potentially more data + if (buffer.capacity() == 0) { + currentData = null; + if (currentBuffer == null) { + currentBuffer = delimiter; + } else { + if (delimiter != null) { + currentBuffer = wrappedBuffer(currentBuffer, delimiter); + } + } + if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) { + buffer = fillByteBuf(); + return new DefaultHttpChunk(buffer); + } + return null; + } + + // Put it all together: name=value& + if (currentBuffer == null) { + if (delimiter != null) { + currentBuffer = wrappedBuffer(buffer, delimiter); + } else { + currentBuffer = buffer; + } + } else { + if (delimiter != null) { + currentBuffer = wrappedBuffer(currentBuffer, buffer, delimiter); + } else { + currentBuffer = wrappedBuffer(currentBuffer, buffer); + } + } + + // end for current InterfaceHttpData, need more data + if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) { + currentData = null; + isKey = true; + return null; + } + + buffer = fillByteBuf(); + return new DefaultHttpChunk(buffer); + } + + @Override + public void close() throws Exception { + // NO since the user can want to reuse (broadcast for instance) + // cleanFiles(); + } + + /** + * Returns the next available HttpChunk. The caller is responsible to test if this chunk is the last one (isLast()), + * in order to stop calling this method. + * + * @return the next available HttpChunk + * @throws ErrorDataEncoderException + * if the encoding is in error + */ + @Override + public boolean readChunk(MessageBuf buffer) throws ErrorDataEncoderException { + if (isLastChunkSent) { + return false; + } else { + buffer.add(nextChunk()); + return true; + } + } + + /** + * Returns the next available HttpChunk. The caller is responsible to test if this chunk is the last one (isLast()), + * in order to stop calling this method. + * + * @return the next available HttpChunk + * @throws ErrorDataEncoderException + * if the encoding is in error + */ + private HttpChunk nextChunk() throws ErrorDataEncoderException { + if (isLastChunk) { + isLastChunkSent = true; + return new DefaultHttpChunk(EMPTY_BUFFER); + } + ByteBuf buffer = null; + int size = HttpPostBodyUtil.chunkSize; + // first test if previous buffer is not empty + if (currentBuffer != null) { + size -= currentBuffer.readableBytes(); + } + if (size <= 0) { + // NextChunk from buffer + buffer = fillByteBuf(); + return new DefaultHttpChunk(buffer); + } + // size > 0 + if (currentData != null) { + // continue to read data + if (isMultipart) { + HttpChunk chunk = encodeNextChunkMultipart(size); + if (chunk != null) { + return chunk; + } + } else { + HttpChunk chunk = encodeNextChunkUrlEncoded(size); + if (chunk != null) { + // NextChunk Url from currentData + return chunk; + } + } + size = HttpPostBodyUtil.chunkSize - currentBuffer.readableBytes(); + } + if (!iterator.hasNext()) { + isLastChunk = true; + // NextChunk as last non empty from buffer + buffer = currentBuffer; + currentBuffer = null; + return new DefaultHttpChunk(buffer); + } + while (size > 0 && iterator.hasNext()) { + currentData = iterator.next(); + HttpChunk chunk; + if (isMultipart) { + chunk = encodeNextChunkMultipart(size); + } else { + chunk = encodeNextChunkUrlEncoded(size); + } + if (chunk == null) { + // not enough + size = HttpPostBodyUtil.chunkSize - currentBuffer.readableBytes(); + continue; + } + // NextChunk from data + return chunk; + } + // end since no more data + isLastChunk = true; + if (currentBuffer == null) { + isLastChunkSent = true; + // LastChunk with no more data + return new DefaultHttpChunk(EMPTY_BUFFER); + } + // Previous LastChunk with no more data + buffer = currentBuffer; + currentBuffer = null; + return new DefaultHttpChunk(buffer); + } + + @Override + public boolean isEndOfInput() throws Exception { + return isLastChunkSent; + } + + /** + * Exception when an error occurs while encoding + */ + public static class ErrorDataEncoderException extends Exception { + private static final long serialVersionUID = 5020247425493164465L; + + public ErrorDataEncoderException() { + } + + /** + * @param arg0 + */ + public ErrorDataEncoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public ErrorDataEncoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public ErrorDataEncoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InterfaceHttpData.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InterfaceHttpData.java new file mode 100644 index 00000000000..12f1ab45ffa --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InterfaceHttpData.java @@ -0,0 +1,36 @@ +/* + * 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.multipart; + +/** + * Interface for all Objects that could be encoded/decoded using HttpPostRequestEncoder/Decoder + */ +public interface InterfaceHttpData extends Comparable { + enum HttpDataType { + Attribute, FileUpload, InternalAttribute + } + + /** + * Returns the name of this InterfaceHttpData. + */ + String getName(); + + /** + * + * @return The HttpDataType + */ + HttpDataType getHttpDataType(); +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java new file mode 100644 index 00000000000..4256152038c --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/InternalAttribute.java @@ -0,0 +1,105 @@ +/* + * 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.multipart; + +import java.util.ArrayList; +import java.util.List; + +/** + * This Attribute is only for Encoder use to insert special command between object if needed + * (like Multipart Mixed mode) + */ +public class InternalAttribute implements InterfaceHttpData { + protected List value = new ArrayList(); + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.InternalAttribute; + } + + public List getValue() { + return value; + } + + public void addValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value.add(value); + } + + public void addValue(String value, int rank) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value.add(rank, value); + } + + public void setValue(String value, int rank) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value.set(rank, value); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof InternalAttribute)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((InternalAttribute) arg0); + } + + public int compareTo(InternalAttribute o) { + return getName().compareToIgnoreCase(o.getName()); + } + + public int size() { + int size = 0; + for (String elt : value) { + size += elt.length(); + } + return size; + } + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for (String elt : value) { + result.append(elt); + } + return result.toString(); + } + + @Override + public String getName() { + return "InternalAttribute"; + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MemoryAttribute.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MemoryAttribute.java new file mode 100644 index 00000000000..d9da52ad679 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MemoryAttribute.java @@ -0,0 +1,109 @@ +/* + * 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.multipart; + +import java.io.IOException; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpConstants; +import static io.netty.buffer.Unpooled.*; + +/** + * Memory implementation of Attributes + */ +public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute { + + public MemoryAttribute(String name) { + super(name, HttpConstants.DEFAULT_CHARSET, 0); + } + /** + * + * @param name + * @param value + * @throws NullPointerException + * @throws IllegalArgumentException + * @throws IOException + */ + public MemoryAttribute(String name, String value) throws IOException { + super(name, HttpConstants.DEFAULT_CHARSET, 0); // Attribute have no default size + setValue(value); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.Attribute; + } + + @Override + public String getValue() { + return getByteBuf().toString(charset); + } + + @Override + public void setValue(String value) throws IOException { + if (value == null) { + throw new NullPointerException("value"); + } + byte [] bytes = value.getBytes(charset.name()); + ByteBuf buffer = wrappedBuffer(bytes); + if (definedSize > 0) { + definedSize = buffer.readableBytes(); + } + setContent(buffer); + } + + @Override + public void addContent(ByteBuf buffer, boolean last) throws IOException { + int localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + definedSize = size + localsize; + } + super.addContent(buffer, last); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof Attribute)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((Attribute) arg0); + } + + public int compareTo(Attribute o) { + return getName().compareToIgnoreCase(o.getName()); + } + + @Override + public String toString() { + return getName() + '=' + getValue(); + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MemoryFileUpload.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MemoryFileUpload.java new file mode 100644 index 00000000000..3e4f632e2f7 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MemoryFileUpload.java @@ -0,0 +1,128 @@ +/* + * 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.multipart; + +import java.nio.charset.Charset; + +import io.netty.handler.codec.http.HttpHeaders; + +/** + * Default FileUpload implementation that stores file into memory.

+ * + * Warning: be aware of the memory limitation. + */ +public class MemoryFileUpload extends AbstractMemoryHttpData implements FileUpload { + + private String filename; + + private String contentType; + + private String contentTransferEncoding; + + public MemoryFileUpload(String name, String filename, String contentType, + String contentTransferEncoding, Charset charset, long size) { + super(name, charset, size); + setFilename(filename); + setContentType(contentType); + setContentTransferEncoding(contentTransferEncoding); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.FileUpload; + } + + @Override + public String getFilename() { + return filename; + } + + @Override + public void setFilename(String filename) { + if (filename == null) { + throw new NullPointerException("filename"); + } + this.filename = filename; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof FileUpload)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((FileUpload) arg0); + } + + public int compareTo(FileUpload o) { + int v; + v = getName().compareToIgnoreCase(o.getName()); + if (v != 0) { + return v; + } + // TODO should we compare size for instance ? + return v; + } + + @Override + public void setContentType(String contentType) { + if (contentType == null) { + throw new NullPointerException("contentType"); + } + this.contentType = contentType; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + @Override + public void setContentTransferEncoding(String contentTransferEncoding) { + this.contentTransferEncoding = contentTransferEncoding; + } + + @Override + public String toString() { + return HttpPostBodyUtil.CONTENT_DISPOSITION + ": " + + HttpPostBodyUtil.FORM_DATA + "; " + HttpPostBodyUtil.NAME + "=\"" + getName() + + "\"; " + HttpPostBodyUtil.FILENAME + "=\"" + filename + "\"\r\n" + + HttpHeaders.Names.CONTENT_TYPE + ": " + contentType + + (charset != null? "; " + HttpHeaders.Values.CHARSET + '=' + charset + "\r\n" : "\r\n") + + HttpHeaders.Names.CONTENT_LENGTH + ": " + length() + "\r\n" + + "Completed: " + isCompleted() + + "\r\nIsInMemory: " + isInMemory(); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MixedAttribute.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MixedAttribute.java new file mode 100644 index 00000000000..404053fe2cb --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MixedAttribute.java @@ -0,0 +1,202 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * Mixed implementation using both in Memory and in File with a limit of size + */ +public class MixedAttribute implements Attribute { + private Attribute attribute; + + private final long limitSize; + + public MixedAttribute(String name, long limitSize) { + this.limitSize = limitSize; + attribute = new MemoryAttribute(name); + } + + public MixedAttribute(String name, String value, long limitSize) { + this.limitSize = limitSize; + if (value.length() > this.limitSize) { + try { + attribute = new DiskAttribute(name, value); + } catch (IOException e) { + // revert to Memory mode + try { + attribute = new MemoryAttribute(name, value); + } catch (IOException e1) { + throw new IllegalArgumentException(e); + } + } + } else { + try { + attribute = new MemoryAttribute(name, value); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + } + + @Override + public void addContent(ByteBuf buffer, boolean last) throws IOException { + if (attribute instanceof MemoryAttribute) { + if (attribute.length() + buffer.readableBytes() > limitSize) { + DiskAttribute diskAttribute = new DiskAttribute(attribute + .getName()); + if (((MemoryAttribute) attribute).getByteBuf() != null) { + diskAttribute.addContent(((MemoryAttribute) attribute) + .getByteBuf(), false); + } + attribute = diskAttribute; + } + } + attribute.addContent(buffer, last); + } + + @Override + public void delete() { + attribute.delete(); + } + + @Override + public byte[] get() throws IOException { + return attribute.get(); + } + + @Override + public ByteBuf getByteBuf() throws IOException { + return attribute.getByteBuf(); + } + + @Override + public Charset getCharset() { + return attribute.getCharset(); + } + + @Override + public String getString() throws IOException { + return attribute.getString(); + } + + @Override + public String getString(Charset encoding) throws IOException { + return attribute.getString(encoding); + } + + @Override + public boolean isCompleted() { + return attribute.isCompleted(); + } + + @Override + public boolean isInMemory() { + return attribute.isInMemory(); + } + + @Override + public long length() { + return attribute.length(); + } + + @Override + public boolean renameTo(File dest) throws IOException { + return attribute.renameTo(dest); + } + + @Override + public void setCharset(Charset charset) { + attribute.setCharset(charset); + } + + @Override + public void setContent(ByteBuf buffer) throws IOException { + if (buffer.readableBytes() > limitSize) { + if (attribute instanceof MemoryAttribute) { + // change to Disk + attribute = new DiskAttribute(attribute.getName()); + } + } + attribute.setContent(buffer); + } + + @Override + public void setContent(File file) throws IOException { + if (file.length() > limitSize) { + if (attribute instanceof MemoryAttribute) { + // change to Disk + attribute = new DiskAttribute(attribute.getName()); + } + } + attribute.setContent(file); + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (attribute instanceof MemoryAttribute) { + // change to Disk even if we don't know the size + attribute = new DiskAttribute(attribute.getName()); + } + attribute.setContent(inputStream); + } + + @Override + public HttpDataType getHttpDataType() { + return attribute.getHttpDataType(); + } + + @Override + public String getName() { + return attribute.getName(); + } + + @Override + public int compareTo(InterfaceHttpData o) { + return attribute.compareTo(o); + } + + @Override + public String toString() { + return "Mixed: " + attribute.toString(); + } + + @Override + public String getValue() throws IOException { + return attribute.getValue(); + } + + @Override + public void setValue(String value) throws IOException { + attribute.setValue(value); + } + + @Override + public ByteBuf getChunk(int length) throws IOException { + return attribute.getChunk(length); + } + + @Override + public File getFile() throws IOException { + return attribute.getFile(); + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MixedFileUpload.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MixedFileUpload.java new file mode 100644 index 00000000000..73f94caa78f --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/MixedFileUpload.java @@ -0,0 +1,227 @@ +/* + * 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.multipart; + +import io.netty.buffer.ByteBuf; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * Mixed implementation using both in Memory and in File with a limit of size + */ +public class MixedFileUpload implements FileUpload { + private FileUpload fileUpload; + + private final long limitSize; + + private final long definedSize; + + public MixedFileUpload(String name, String filename, String contentType, + String contentTransferEncoding, Charset charset, long size, + long limitSize) { + this.limitSize = limitSize; + if (size > this.limitSize) { + fileUpload = new DiskFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + } else { + fileUpload = new MemoryFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + } + definedSize = size; + } + + @Override + public void addContent(ByteBuf buffer, boolean last) + throws IOException { + if (fileUpload instanceof MemoryFileUpload) { + if (fileUpload.length() + buffer.readableBytes() > limitSize) { + DiskFileUpload diskFileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + if (((MemoryFileUpload) fileUpload).getByteBuf() != null) { + diskFileUpload.addContent(((MemoryFileUpload) fileUpload) + .getByteBuf(), false); + } + fileUpload = diskFileUpload; + } + } + fileUpload.addContent(buffer, last); + } + + @Override + public void delete() { + fileUpload.delete(); + } + + @Override + public byte[] get() throws IOException { + return fileUpload.get(); + } + + @Override + public ByteBuf getByteBuf() throws IOException { + return fileUpload.getByteBuf(); + } + + @Override + public Charset getCharset() { + return fileUpload.getCharset(); + } + + @Override + public String getContentType() { + return fileUpload.getContentType(); + } + + @Override + public String getContentTransferEncoding() { + return fileUpload.getContentTransferEncoding(); + } + + @Override + public String getFilename() { + return fileUpload.getFilename(); + } + + @Override + public String getString() throws IOException { + return fileUpload.getString(); + } + + @Override + public String getString(Charset encoding) throws IOException { + return fileUpload.getString(encoding); + } + + @Override + public boolean isCompleted() { + return fileUpload.isCompleted(); + } + + @Override + public boolean isInMemory() { + return fileUpload.isInMemory(); + } + + @Override + public long length() { + return fileUpload.length(); + } + + @Override + public boolean renameTo(File dest) throws IOException { + return fileUpload.renameTo(dest); + } + + @Override + public void setCharset(Charset charset) { + fileUpload.setCharset(charset); + } + + @Override + public void setContent(ByteBuf buffer) throws IOException { + if (buffer.readableBytes() > limitSize) { + if (fileUpload instanceof MemoryFileUpload) { + // change to Disk + fileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + } + } + fileUpload.setContent(buffer); + } + + @Override + public void setContent(File file) throws IOException { + if (file.length() > limitSize) { + if (fileUpload instanceof MemoryFileUpload) { + // change to Disk + fileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + } + } + fileUpload.setContent(file); + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (fileUpload instanceof MemoryFileUpload) { + // change to Disk + fileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + } + fileUpload.setContent(inputStream); + } + + @Override + public void setContentType(String contentType) { + fileUpload.setContentType(contentType); + } + + @Override + public void setContentTransferEncoding(String contentTransferEncoding) { + fileUpload.setContentTransferEncoding(contentTransferEncoding); + } + + @Override + public void setFilename(String filename) { + fileUpload.setFilename(filename); + } + + @Override + public HttpDataType getHttpDataType() { + return fileUpload.getHttpDataType(); + } + + @Override + public String getName() { + return fileUpload.getName(); + } + + @Override + public int compareTo(InterfaceHttpData o) { + return fileUpload.compareTo(o); + } + + @Override + public String toString() { + return "Mixed: " + fileUpload.toString(); + } + + @Override + public ByteBuf getChunk(int length) throws IOException { + return fileUpload.getChunk(length); + } + + @Override + public File getFile() throws IOException { + return fileUpload.getFile(); + } + +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/package-info.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/package-info.java new file mode 100644 index 00000000000..bd53ddbb7a8 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * HTTP multipart support. + */ +package io.netty.handler.codec.http.multipart; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java index b18b43e3bfc..99414babf59 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket00FrameDecoder.java @@ -70,7 +70,7 @@ public WebSocketFrame decode(ChannelHandlerContext ctx, ByteBuf in) throws Excep } } - private WebSocketFrame decodeBinaryFrame(byte type, ByteBuf buffer) throws TooLongFrameException { + private WebSocketFrame decodeBinaryFrame(byte type, ByteBuf buffer) { long frameSize = 0; int lengthFieldSize = 0; byte b; @@ -96,7 +96,7 @@ private WebSocketFrame decodeBinaryFrame(byte type, ByteBuf buffer) throws TooLo return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize)); } - private WebSocketFrame decodeTextFrame(ByteBuf buffer) throws TooLongFrameException { + private WebSocketFrame decodeTextFrame(ByteBuf buffer) { int ridx = buffer.readerIndex(); int rbytes = actualReadableBytes(); int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java index 0d0e7e64ac2..acbd53a22a8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java @@ -366,7 +366,7 @@ private void unmask(ByteBuf frame) { } } - private void protocolViolation(ChannelHandlerContext ctx, String reason) throws CorruptedFrameException { + private void protocolViolation(ChannelHandlerContext ctx, String reason) { checkpoint(State.CORRUPT); if (ctx.channel().isActive()) { ctx.flush().addListener(ChannelFutureListener.CLOSE); @@ -374,7 +374,7 @@ private void protocolViolation(ChannelHandlerContext ctx, String reason) throws throw new CorruptedFrameException(reason); } - private static int toFrameLength(long l) throws TooLongFrameException { + private static int toFrameLength(long l) { if (l > Integer.MAX_VALUE) { throw new TooLongFrameException("Length:" + l); } else { @@ -382,7 +382,7 @@ private static int toFrameLength(long l) throws TooLongFrameException { } } - private void checkUTF8String(ChannelHandlerContext ctx, byte[] bytes) throws CorruptedFrameException { + private void checkUTF8String(ChannelHandlerContext ctx, byte[] bytes) { try { if (fragmentedFramesText == null) { fragmentedFramesText = new UTF8Output(bytes); @@ -394,8 +394,9 @@ private void checkUTF8String(ChannelHandlerContext ctx, byte[] bytes) throws Cor } } + /** */ protected void checkCloseFrameBody( - ChannelHandlerContext ctx, ByteBuf buffer) throws CorruptedFrameException { + ChannelHandlerContext ctx, ByteBuf buffer) { if (buffer == null || buffer.capacity() == 0) { return; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java index f0788656604..78ee6bf6508 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker.java @@ -56,8 +56,8 @@ public abstract class WebSocketClientHandshaker { * @param maxFramePayloadLength * Maximum length of a frame's payload */ - public WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol, - Map customHeaders, int maxFramePayloadLength) { + protected WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol, + Map customHeaders, int maxFramePayloadLength) { this.webSocketUrl = webSocketUrl; this.version = version; expectedSubprotocol = subprotocol; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index 53db1c8d1ef..32de9c1b597 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -133,8 +133,8 @@ public ChannelFuture handshake(Channel channel) { // Get path URI wsURL = getWebSocketUrl(); String path = wsURL.getPath(); - if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { - path = wsURL.getPath() + "?" + wsURL.getQuery(); + if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { + path = wsURL.getPath() + '?' + wsURL.getQuery(); } // Format request @@ -148,21 +148,21 @@ public ChannelFuture handshake(Channel channel) { if (wsPort != 80 && wsPort != 443) { // if the port is not standard (80/443) its needed to add the port to the header. // See http://tools.ietf.org/html/rfc6454#section-6.2 - originValue = originValue + ":" + wsPort; + originValue = originValue + ':' + wsPort; } request.addHeader(Names.ORIGIN, originValue); request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); String expectedSubprotocol = getExpectedSubprotocol(); - if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) { + if (expectedSubprotocol != null && !expectedSubprotocol.isEmpty()) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol); } if (customHeaders != null) { - for (String header : customHeaders.keySet()) { - request.addHeader(header, customHeaders.get(header)); + for (Map.Entry e : customHeaders.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); } } @@ -206,7 +206,7 @@ public void operationComplete(ChannelFuture future) { * @throws WebSocketHandshakeException */ @Override - public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { + public void finishHandshake(Channel channel, HttpResponse response) { final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake"); if (!response.getStatus().equals(status)) { @@ -214,13 +214,13 @@ public void finishHandshake(Channel channel, HttpResponse response) throws WebSo } String upgrade = response.getHeader(Names.UPGRADE); - if (upgrade == null || !upgrade.equalsIgnoreCase(Values.WEBSOCKET)) { + if (Values.WEBSOCKET.equalsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE)); } String connection = response.getHeader(Names.CONNECTION); - if (connection == null || !connection.equalsIgnoreCase(Values.UPGRADE)) { + if (Values.UPGRADE.equalsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION)); } @@ -269,7 +269,7 @@ private static String insertSpaces(String key, int spaces) { int split = WebSocketUtil.randomNumber(1, key.length() - 1); String part1 = key.substring(0, split); String part2 = key.substring(split); - key = part1 + " " + part2; + key = part1 + ' ' + part2; } return key; diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 6f040806289..3c651951735 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -101,8 +101,8 @@ public ChannelFuture handshake(Channel channel) { // Get path URI wsURL = getWebSocketUrl(); String path = wsURL.getPath(); - if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { - path = wsURL.getPath() + "?" + wsURL.getQuery(); + if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { + path = wsURL.getPath() + '?' + wsURL.getQuery(); } // Get 16 bit nonce and base 64 encode it @@ -130,20 +130,20 @@ public ChannelFuture handshake(Channel channel) { if (wsPort != 80 && wsPort != 443) { // if the port is not standard (80/443) its needed to add the port to the header. // See http://tools.ietf.org/html/rfc6454#section-6.2 - originValue = originValue + ":" + wsPort; + originValue = originValue + ':' + wsPort; } request.addHeader(Names.SEC_WEBSOCKET_ORIGIN, originValue); String expectedSubprotocol = getExpectedSubprotocol(); - if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) { + if (expectedSubprotocol != null && !expectedSubprotocol.isEmpty()) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol); } request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8"); if (customHeaders != null) { - for (String header : customHeaders.keySet()) { - request.addHeader(header, customHeaders.get(header)); + for (Map.Entry e : customHeaders.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); } } @@ -190,13 +190,13 @@ public void finishHandshake(Channel channel, HttpResponse response) { } String upgrade = response.getHeader(Names.UPGRADE); - if (upgrade == null || !upgrade.equalsIgnoreCase(Values.WEBSOCKET)) { + if (Values.WEBSOCKET.equalsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE)); } String connection = response.getHeader(Names.CONNECTION); - if (connection == null || !connection.equalsIgnoreCase(Values.UPGRADE)) { + if (Values.UPGRADE.equalsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION)); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 90b05893fd4..ea61f0e2a55 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -101,8 +101,8 @@ public ChannelFuture handshake(Channel channel) { // Get path URI wsURL = getWebSocketUrl(); String path = wsURL.getPath(); - if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { - path = wsURL.getPath() + "?" + wsURL.getQuery(); + if (wsURL.getQuery() != null && !wsURL.getQuery().isEmpty()) { + path = wsURL.getPath() + '?' + wsURL.getQuery(); } // Get 16 bit nonce and base 64 encode it @@ -130,20 +130,20 @@ public ChannelFuture handshake(Channel channel) { if (wsPort != 80 && wsPort != 443) { // if the port is not standard (80/443) its needed to add the port to the header. // See http://tools.ietf.org/html/rfc6454#section-6.2 - originValue = originValue + ":" + wsPort; + originValue = originValue + ':' + wsPort; } request.addHeader(Names.SEC_WEBSOCKET_ORIGIN, originValue); String expectedSubprotocol = getExpectedSubprotocol(); - if (expectedSubprotocol != null && !expectedSubprotocol.equals("")) { + if (expectedSubprotocol != null && !expectedSubprotocol.isEmpty()) { request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, expectedSubprotocol); } request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13"); if (customHeaders != null) { - for (String header : customHeaders.keySet()) { - request.addHeader(header, customHeaders.get(header)); + for (Map.Entry e: customHeaders.entrySet()) { + request.addHeader(e.getKey(), e.getValue()); } } @@ -182,7 +182,7 @@ public void operationComplete(ChannelFuture future) { * @throws WebSocketHandshakeException */ @Override - public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { + public void finishHandshake(Channel channel, HttpResponse response) { final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS; if (!response.getStatus().equals(status)) { @@ -190,13 +190,13 @@ public void finishHandshake(Channel channel, HttpResponse response) throws WebSo } String upgrade = response.getHeader(Names.UPGRADE); - if (upgrade == null || !upgrade.equalsIgnoreCase(Values.WEBSOCKET)) { + if (Values.WEBSOCKET.equalsIgnoreCase(upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE)); } String connection = response.getHeader(Names.CONNECTION); - if (connection == null || !connection.equalsIgnoreCase(Values.UPGRADE)) { + if (Values.UPGRADE.equalsIgnoreCase(connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION)); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java index dbc2aea64df..4fbb5848ea9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker.java @@ -18,6 +18,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.internal.StringUtil; import java.util.Collections; import java.util.LinkedHashSet; @@ -57,7 +58,7 @@ protected WebSocketServerHandshaker( this.version = version; this.webSocketUrl = webSocketUrl; if (subprotocols != null) { - String[] subprotocolArray = subprotocols.split(","); + String[] subprotocolArray = StringUtil.split(subprotocols, ','); for (int i = 0; i < subprotocolArray.length; i++) { subprotocolArray[i] = subprotocolArray[i].trim(); } @@ -132,7 +133,7 @@ protected String selectSubprotocol(String requestedSubprotocols) { return null; } - String[] requestedSubprotocolArray = requestedSubprotocols.split(","); + String[] requestedSubprotocolArray = StringUtil.split(requestedSubprotocols, ','); for (String p: requestedSubprotocolArray) { String requestedSubprotocol = p.trim(); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java index d677e4fed30..1f7301f223d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker00.java @@ -138,13 +138,13 @@ public ChannelFuture handshake(Channel channel, HttpRequest req) { // New handshake method with a challenge: res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketUrl()); - String subprotocols = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); + String subprotocols = req.getHeader(SEC_WEBSOCKET_PROTOCOL); if (subprotocols != null) { String selectedSubprotocol = selectSubprotocol(subprotocols); if (selectedSubprotocol == null) { throw new WebSocketHandshakeException("Requested subprotocol(s) not supported: " + subprotocols); } else { - res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol); + res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectedSubprotocol); setSelectedSubprotocol(selectedSubprotocol); } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java index 4e8b48f25d8..fe8a0794d83 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java @@ -108,7 +108,7 @@ public WebSocketServerHandshaker newHandshaker(HttpRequest req) { * @param channel * Channel */ - public void sendUnsupportedWebSocketVersionResponse(Channel channel) { + public static void sendUnsupportedWebSocketVersionResponse(Channel channel) { HttpResponse res = new DefaultHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java index e1bdc801987..22fba7cbf41 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java @@ -66,7 +66,7 @@ public void afterAdd(ChannelHandlerContext ctx) { @Override public void messageReceived(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { if (frame instanceof CloseWebSocketFrame) { - WebSocketServerHandshaker handshaker = WebSocketServerProtocolHandler.getHandshaker(ctx); + WebSocketServerHandshaker handshaker = getHandshaker(ctx); handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame); return; } else if (frame instanceof PingWebSocketFrame) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java index 7954749daab..e5a97f4995b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java +++ b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java @@ -123,7 +123,7 @@ public static HttpMethod valueOf(String name) { } name = name.trim().toUpperCase(); - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException("empty name"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java index 116c5098a87..4f75c0382a1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyNoOpFrame.java @@ -20,12 +20,6 @@ */ public class DefaultSpdyNoOpFrame implements SpdyNoOpFrame { - /** - * Creates a new instance. - */ - public DefaultSpdyNoOpFrame() { - } - @Override public String toString() { return getClass().getSimpleName(); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java index a3d9f709bf4..fd9022e6c16 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdySettingsFrame.java @@ -29,12 +29,6 @@ public class DefaultSpdySettingsFrame implements SpdySettingsFrame { private boolean clear; private final Map settingsMap = new TreeMap(); - /** - * Creates a new instance. - */ - public DefaultSpdySettingsFrame() { - } - @Override public Set getIds() { return settingsMap.keySet(); @@ -140,7 +134,7 @@ private void appendSettings(StringBuilder buf) { Setting setting = e.getValue(); buf.append("--> "); buf.append(e.getKey().toString()); - buf.append(":"); + buf.append(':'); buf.append(setting.getValue()); buf.append(" (persist value: "); buf.append(setting.isPersist()); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java index 5458a9f77f2..746e5b66288 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyCodecUtil.java @@ -319,7 +319,7 @@ static void validateHeaderName(String name) { if (name == null) { throw new NullPointerException("name"); } - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException( "name cannot be length zero"); } @@ -349,7 +349,7 @@ static void validateHeaderValue(String value) { if (value == null) { throw new NullPointerException("value"); } - if (value.length() == 0) { + if (value.isEmpty()) { throw new IllegalArgumentException( "value cannot be length zero"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java index 0bc9c54bb0f..c46007a09bc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java @@ -15,7 +15,6 @@ */ package io.netty.handler.codec.spdy; -import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; @@ -27,6 +26,8 @@ import java.util.Set; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; + /** * Encodes a SPDY Data or Control Frame into a {@link ByteBuf}. */ @@ -192,7 +193,7 @@ public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Ex // Chromium Issue 79156 // SPDY setting ids are not written in network byte order // Write id assuming the architecture is little endian - out.writeByte(id >> 0 & 0xFF); + out.writeByte(id & 0xFF); out.writeByte(id >> 8 & 0xFF); out.writeByte(id >> 16 & 0xFF); out.writeByte(ID_flags); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java index 336df21017d..bcea411c95a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java @@ -760,7 +760,7 @@ public String setValue(String value) { @Override public String toString() { - return key + "=" + value; + return key + '=' + value; } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java index 20c53f54d86..321625c454f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpResponseStreamIdHandler.java @@ -21,7 +21,6 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; /** diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java index 56212f720a2..4d7002a02e1 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyOrHttpChooser.java @@ -39,7 +39,7 @@ */ public abstract class SpdyOrHttpChooser extends ChannelHandlerAdapter implements ChannelInboundByteHandler { - public static enum SelectedProtocol { + public enum SelectedProtocol { SpdyVersion2, SpdyVersion3, HttpVersion1_1, @@ -50,7 +50,7 @@ public static enum SelectedProtocol { private final int maxSpdyContentLength; private final int maxHttpContentLength; - public SpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) { + protected SpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) { this.maxSpdyContentLength = maxSpdyContentLength; this.maxHttpContentLength = maxHttpContentLength; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java index 727b2a9ec0e..5c3b674cecf 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdySession.java @@ -29,9 +29,6 @@ final class SpdySession { private final Map activeStreams = new ConcurrentHashMap(); - SpdySession() { - } - int numActiveStreams() { return activeStreams.size(); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpInvalidMessageTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpInvalidMessageTest.java index 3d67783227f..28ca9c9dd78 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpInvalidMessageTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpInvalidMessageTest.java @@ -20,12 +20,11 @@ import io.netty.channel.embedded.EmbeddedByteChannel; import io.netty.handler.codec.DecoderResult; import io.netty.util.CharsetUtil; - -import java.util.Random; - import org.junit.Assert; import org.junit.Test; +import java.util.Random; + public class HttpInvalidMessageTest { private final Random rnd = new Random(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java new file mode 100644 index 00000000000..775c793119d --- /dev/null +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpRequestEncoderTest.java @@ -0,0 +1,50 @@ +/* + * 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 static org.junit.Assert.*; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.Test; + +import java.nio.charset.Charset; + +/** + */ +public class HttpRequestEncoderTest { + + @Test + public void testUriWithoutPath() throws Exception { + HttpRequestEncoder encoder = new HttpRequestEncoder(); + ByteBuf buffer = Unpooled.buffer(64); + encoder.encodeInitialLine(buffer, new DefaultHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.GET, "http://localhost")); + String req = buffer.toString(Charset.forName("US-ASCII")); + assertEquals("GET http://localhost/ HTTP/1.1\r\n", req); + } + + + @Test + public void testUriWithPath() throws Exception { + HttpRequestEncoder encoder = new HttpRequestEncoder(); + ByteBuf buffer = Unpooled.buffer(64); + encoder.encodeInitialLine(buffer, new DefaultHttpRequest(HttpVersion.HTTP_1_1, + HttpMethod.GET, "http://localhost/")); + String req = buffer.toString(Charset.forName("US-ASCII")); + assertEquals("GET http://localhost/ HTTP/1.1\r\n", req); + } +} \ No newline at end of file diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java index b3aa3677536..489560b45af 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java @@ -65,7 +65,7 @@ public void testUnfinishedChunkedHttpRequestIsLastFlag() throws Exception { private static ByteBuf prepareDataChunk(int size) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; ++i) { - sb.append("a"); + sb.append('a'); } return Unpooled.copiedBuffer(sb.toString(), CharsetUtil.UTF_8); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java index 0bec4da3b5e..f24030c53de 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java @@ -76,17 +76,17 @@ public WebSocketRequestBuilder origin(String origin) { } public WebSocketRequestBuilder version13() { - this.version = WebSocketVersion.V13; + version = WebSocketVersion.V13; return this; } public WebSocketRequestBuilder version8() { - this.version = WebSocketVersion.V08; + version = WebSocketVersion.V08; return this; } public WebSocketRequestBuilder version00() { - this.version = null; + version = null; return this; } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java index d75ae96f5eb..d0010a99f7a 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java @@ -15,12 +15,6 @@ */ package io.netty.handler.codec.http.websocketx; -import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; -import static io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS; -import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import io.netty.buffer.MessageBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; @@ -34,11 +28,13 @@ import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseEncoder; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpVersion; - import org.junit.Test; +import static io.netty.handler.codec.http.HttpHeaders.Values.*; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.*; +import static org.junit.Assert.*; + public class WebSocketServerProtocolHandlerTest { @Test @@ -59,7 +55,7 @@ public void testSubsequentHttpRequestsAfterUpgradeShouldReturn403() throws Excep writeUpgradeRequest(ch); assertEquals(SWITCHING_PROTOCOLS, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus()); - ch.writeInbound(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/test")); + ch.writeInbound(new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/test")); assertEquals(FORBIDDEN, ((HttpResponse) ch.outboundMessageBuffer().poll()).getStatus()); } @@ -77,7 +73,7 @@ public void testHttpUpgradeRequestInvalidUpgradeHeader() { ch.writeInbound(httpRequest); HttpResponse response = getHttpResponse(ch); - assertEquals(HttpResponseStatus.BAD_REQUEST, response.getStatus()); + assertEquals(BAD_REQUEST, response.getStatus()); assertEquals("not a WebSocket handshake request: missing upgrade", getResponseMessage(response)); } @@ -97,7 +93,7 @@ public void testHttpUpgradeRequestMissingWSKeyHeader() { ch.writeInbound(httpRequest); HttpResponse response = getHttpResponse(ch); - assertEquals(HttpResponseStatus.BAD_REQUEST, response.getStatus()); + assertEquals(BAD_REQUEST, response.getStatus()); assertEquals("not a WebSocket request: missing key", getResponseMessage(response)); } @@ -114,11 +110,11 @@ public void testHandleTextFrame() { assertEquals("processed: payload", customTextFrameHandler.getContent()); } - private EmbeddedMessageChannel createChannel() { + private static EmbeddedMessageChannel createChannel() { return createChannel(null); } - private EmbeddedMessageChannel createChannel(ChannelHandler handler) { + private static EmbeddedMessageChannel createChannel(ChannelHandler handler) { return new EmbeddedMessageChannel( new WebSocketServerProtocolHandler("/test", null, false), new HttpRequestDecoder(), @@ -127,15 +123,15 @@ private EmbeddedMessageChannel createChannel(ChannelHandler handler) { handler); } - private void writeUpgradeRequest(EmbeddedMessageChannel ch) { + private static void writeUpgradeRequest(EmbeddedMessageChannel ch) { ch.writeInbound(WebSocketRequestBuilder.sucessful()); } - private String getResponseMessage(HttpResponse response) { + private static String getResponseMessage(HttpResponse response) { return new String(response.getContent().array()); } - private HttpResponse getHttpResponse(EmbeddedMessageChannel ch) { + private static HttpResponse getHttpResponse(EmbeddedMessageChannel ch) { MessageBuf outbound = ch.pipeline().context(MockOutboundHandler.class).outboundMessageBuffer(); return (HttpResponse) outbound.poll(); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java index 7bf94fb3b95..5d4b6472f8f 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java @@ -273,7 +273,7 @@ public void testSpdyServerSessionHandler() { // Echo Handler opens 4 half-closed streams on session connection // and then sets the number of concurrent streams to 3 - private class EchoHandler extends ChannelInboundMessageHandlerAdapter { + private static class EchoHandler extends ChannelInboundMessageHandlerAdapter { private final int closeSignal; private final boolean server; diff --git a/codec/pom.xml b/codec/pom.xml index 61d5816c5c2..a939e803ee9 100644 --- a/codec/pom.xml +++ b/codec/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-codec diff --git a/codec/src/main/java/io/netty/handler/codec/ReplayingDecoder.java b/codec/src/main/java/io/netty/handler/codec/ReplayingDecoder.java index 6347ba8d982..e3111f9e7a8 100644 --- a/codec/src/main/java/io/netty/handler/codec/ReplayingDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/ReplayingDecoder.java @@ -429,7 +429,7 @@ protected void callDecode(ChannelHandlerContext ctx) { throw new IllegalStateException( "decode() method must consume at least one byte " + "if it returned a decoded message (caused by: " + - getClass() + ")"); + getClass() + ')'); } // A successful decode diff --git a/codec/src/main/java/io/netty/handler/codec/compression/ZlibUtil.java b/codec/src/main/java/io/netty/handler/codec/compression/ZlibUtil.java index 0eaebd356cd..bcb9af3c8af 100644 --- a/codec/src/main/java/io/netty/handler/codec/compression/ZlibUtil.java +++ b/codec/src/main/java/io/netty/handler/codec/compression/ZlibUtil.java @@ -28,7 +28,7 @@ static void fail(ZStream z, String message, int resultCode) { } static CompressionException exception(ZStream z, String message, int resultCode) { - return new CompressionException(message + " (" + resultCode + ")" + + return new CompressionException(message + " (" + resultCode + ')' + (z.msg != null? ": " + z.msg : "")); } diff --git a/codec/src/main/java/io/netty/handler/codec/protobuf/ProtobufVarint32FrameDecoder.java b/codec/src/main/java/io/netty/handler/codec/protobuf/ProtobufVarint32FrameDecoder.java index 6e0e0d5ea47..a71d9af88f4 100644 --- a/codec/src/main/java/io/netty/handler/codec/protobuf/ProtobufVarint32FrameDecoder.java +++ b/codec/src/main/java/io/netty/handler/codec/protobuf/ProtobufVarint32FrameDecoder.java @@ -42,12 +42,6 @@ public class ProtobufVarint32FrameDecoder extends ByteToMessageDecoder { // TODO maxFrameLength + safe skip + fail-fast option // (just like LengthFieldBasedFrameDecoder) - /** - * Creates a new instance. - */ - public ProtobufVarint32FrameDecoder() { - } - @Override public Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { in.markReaderIndex(); diff --git a/codec/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java b/codec/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java index afdc8905678..0b438e5f811 100644 --- a/codec/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java +++ b/codec/src/main/java/io/netty/handler/codec/sctp/SctpMessageCompletionHandler.java @@ -26,12 +26,7 @@ import java.util.Map; public class SctpMessageCompletionHandler extends ChannelInboundMessageHandlerAdapter { - private Map fragments = new HashMap(); - - /** - */ - public SctpMessageCompletionHandler() { - } + private final Map fragments = new HashMap(); @Override public void messageReceived(ChannelHandlerContext ctx, SctpMessage msg) throws Exception { diff --git a/codec/src/test/java/io/netty/handler/codec/frame/DelimiterBasedFrameDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/frame/DelimiterBasedFrameDecoderTest.java index 6903d402332..f9f7e5388a6 100644 --- a/codec/src/test/java/io/netty/handler/codec/frame/DelimiterBasedFrameDecoderTest.java +++ b/codec/src/test/java/io/netty/handler/codec/frame/DelimiterBasedFrameDecoderTest.java @@ -25,7 +25,6 @@ import io.netty.handler.codec.TooLongFrameException; import io.netty.util.CharsetUtil; -import org.junit.Assert; import org.junit.Test; public class DelimiterBasedFrameDecoderTest { @@ -39,14 +38,14 @@ public void testFailSlowTooLongFrameRecovery() throws Exception { ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 1, 2 })); try { assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0 }))); - Assert.fail(DecoderException.class.getSimpleName() + " must be raised."); + fail(DecoderException.class.getSimpleName() + " must be raised."); } catch (TooLongFrameException e) { // Expected } ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 'A', 0 })); ByteBuf buf = (ByteBuf) ch.readInbound(); - Assert.assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); + assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); } } @@ -58,14 +57,14 @@ public void testFailFastTooLongFrameRecovery() throws Exception { for (int i = 0; i < 2; i ++) { try { assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 1, 2 }))); - Assert.fail(DecoderException.class.getSimpleName() + " must be raised."); + fail(DecoderException.class.getSimpleName() + " must be raised."); } catch (TooLongFrameException e) { // Expected } ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0, 'A', 0 })); ByteBuf buf = (ByteBuf) ch.readInbound(); - Assert.assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); + assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); } } } diff --git a/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldBasedFrameDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldBasedFrameDecoderTest.java index aebb1149df3..80ceb6a0097 100644 --- a/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldBasedFrameDecoderTest.java +++ b/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldBasedFrameDecoderTest.java @@ -24,7 +24,6 @@ import io.netty.handler.codec.TooLongFrameException; import io.netty.util.CharsetUtil; -import org.junit.Assert; import org.junit.Test; public class LengthFieldBasedFrameDecoderTest { @@ -37,14 +36,14 @@ public void testFailSlowTooLongFrameRecovery() throws Exception { assertFalse(ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0, 0, 0, 2 }))); try { assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0, 0 }))); - Assert.fail(DecoderException.class.getSimpleName() + " must be raised."); + fail(DecoderException.class.getSimpleName() + " must be raised."); } catch (TooLongFrameException e) { // Expected } ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0, 0, 0, 1, 'A' })); ByteBuf buf = (ByteBuf) ch.readInbound(); - Assert.assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); + assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); } } @@ -56,14 +55,14 @@ public void testFailFastTooLongFrameRecovery() throws Exception { for (int i = 0; i < 2; i ++) { try { assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0, 0, 0, 2 }))); - Assert.fail(DecoderException.class.getSimpleName() + " must be raised."); + fail(DecoderException.class.getSimpleName() + " must be raised."); } catch (TooLongFrameException e) { // Expected } ch.writeInbound(Unpooled.wrappedBuffer(new byte[] { 0, 0, 0, 0, 0, 1, 'A' })); ByteBuf buf = (ByteBuf) ch.readInbound(); - Assert.assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); + assertEquals("A", buf.toString(CharsetUtil.ISO_8859_1)); } } } diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingDecoderTest.java index 196eeec75c4..e3a47d25566 100644 --- a/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingDecoderTest.java +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingDecoderTest.java @@ -15,26 +15,26 @@ */ package io.netty.handler.codec.marshalling; -import static org.junit.Assert.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.embedded.EmbeddedByteChannel; import io.netty.handler.codec.CodecException; import io.netty.handler.codec.TooLongFrameException; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - import junit.framework.Assert; - import org.jboss.marshalling.Marshaller; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; import org.junit.Test; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static org.junit.Assert.*; + public abstract class AbstractCompatibleMarshallingDecoderTest { + @SuppressWarnings("RedundantStringConstructorCall") private final String testObject = new String("test"); @Test diff --git a/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingEncoderTest.java b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingEncoderTest.java index 98675b345f7..03b2579c5f0 100644 --- a/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingEncoderTest.java +++ b/codec/src/test/java/io/netty/handler/codec/marshalling/AbstractCompatibleMarshallingEncoderTest.java @@ -18,21 +18,20 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.embedded.EmbeddedByteChannel; - -import java.io.IOException; - import junit.framework.Assert; - import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; import org.jboss.marshalling.Unmarshaller; import org.junit.Test; +import java.io.IOException; + public abstract class AbstractCompatibleMarshallingEncoderTest { @Test public void testMarshalling() throws IOException, ClassNotFoundException { + @SuppressWarnings("RedundantStringConstructorCall") String testObject = new String("test"); final MarshallerFactory marshallerFactory = createMarshallerFactory(); diff --git a/common/pom.xml b/common/pom.xml index 19ea89b13bb..f6660175227 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-common diff --git a/common/src/main/java/io/netty/logging/OsgiLogger.java b/common/src/main/java/io/netty/logging/OsgiLogger.java index ddf1c2b2821..0320268a4df 100644 --- a/common/src/main/java/io/netty/logging/OsgiLogger.java +++ b/common/src/main/java/io/netty/logging/OsgiLogger.java @@ -31,7 +31,7 @@ class OsgiLogger extends AbstractInternalLogger { this.parent = parent; this.name = name; this.fallback = fallback; - prefix = "[" + name + "] "; + prefix = '[' + name + "] "; } @Override diff --git a/common/src/main/java/io/netty/logging/OsgiLoggerFactory.java b/common/src/main/java/io/netty/logging/OsgiLoggerFactory.java index 33d5db6726d..fae5da758e6 100644 --- a/common/src/main/java/io/netty/logging/OsgiLoggerFactory.java +++ b/common/src/main/java/io/netty/logging/OsgiLoggerFactory.java @@ -24,6 +24,7 @@ * Logger factory which creates an OSGi * {@link LogService} logger. */ +@SuppressWarnings("all") public class OsgiLoggerFactory extends InternalLoggerFactory { private final ServiceTracker logServiceTracker; @@ -56,8 +57,7 @@ public Object addingService(ServiceReference reference) { } @Override - public void removedService(ServiceReference reference, - Object service) { + public void removedService(ServiceReference reference, Object service) { logService = null; } }; diff --git a/common/src/main/java/io/netty/monitor/MonitorName.java b/common/src/main/java/io/netty/monitor/MonitorName.java index 1f7a5d00ef5..f73ce8f7f8a 100644 --- a/common/src/main/java/io/netty/monitor/MonitorName.java +++ b/common/src/main/java/io/netty/monitor/MonitorName.java @@ -139,7 +139,7 @@ public MonitorName ofInstance(final String instance) { if (instance.equals(this.instance)) { return this; } - return new MonitorName(this.group, this.type, this.name, instance); + return new MonitorName(group, type, name, instance); } /** @@ -193,10 +193,10 @@ public String getInstance() { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((group == null) ? 0 : group.hashCode()); - result = prime * result + ((instance == null) ? 0 : instance.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + (group == null ? 0 : group.hashCode()); + result = prime * result + (instance == null ? 0 : instance.hashCode()); + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + (type == null ? 0 : type.hashCode()); return result; } @@ -251,7 +251,7 @@ public boolean equals(final Object obj) { */ @Override public String toString() { - return this.instance != null ? "Monitor(" + group + "/" + type + "/" + name + "/" + instance + ")" : "Monitor(" - + group + "/" + type + "/" + name + ")"; + return instance != null ? "Monitor(" + group + '/' + type + '/' + name + '/' + instance + ')' : "Monitor(" + + group + '/' + type + '/' + name + ')'; } } diff --git a/common/src/main/java/io/netty/monitor/MonitorRegistries.java b/common/src/main/java/io/netty/monitor/MonitorRegistries.java index c172b44e864..19dbb33c828 100644 --- a/common/src/main/java/io/netty/monitor/MonitorRegistries.java +++ b/common/src/main/java/io/netty/monitor/MonitorRegistries.java @@ -47,8 +47,8 @@ public static MonitorRegistries instance() { return Holder.INSTANCE; } - private static final class Holder { - private static final MonitorRegistries INSTANCE = new MonitorRegistries(); + private interface Holder { + MonitorRegistries INSTANCE = new MonitorRegistries(); } private static final ServiceLoader FACTORIES = ServiceLoader @@ -117,7 +117,7 @@ public Iterator iterator() { return new MonitorRegistryIterator(FACTORIES.iterator()); } - private final class MonitorRegistryIterator implements Iterator { + private static final class MonitorRegistryIterator implements Iterator { private final Iterator factories; @@ -127,12 +127,12 @@ private MonitorRegistryIterator(final Iterator factories @Override public boolean hasNext() { - return this.factories.hasNext(); + return factories.hasNext(); } @Override public MonitorRegistry next() { - return this.factories.next().newMonitorRegistry(); + return factories.next().newMonitorRegistry(); } @Override diff --git a/common/src/main/java/io/netty/monitor/spi/MonitorProvider.java b/common/src/main/java/io/netty/monitor/spi/MonitorProvider.java index 64c7489282e..81164e1b6ac 100644 --- a/common/src/main/java/io/netty/monitor/spi/MonitorProvider.java +++ b/common/src/main/java/io/netty/monitor/spi/MonitorProvider.java @@ -63,7 +63,7 @@ public String getName() { */ @Override public int compareTo(final MonitorProvider o) { - return this.name.compareTo(o.name); + return name.compareTo(o.name); } /** @@ -73,7 +73,7 @@ public int compareTo(final MonitorProvider o) { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + (name == null ? 0 : name.hashCode()); return result; } @@ -107,6 +107,6 @@ public boolean equals(final Object obj) { */ @Override public String toString() { - return "MonitorProvider(" + name + ")"; + return "MonitorProvider(" + name + ')'; } } diff --git a/common/src/main/java/io/netty/util/HashedWheelTimer.java b/common/src/main/java/io/netty/util/HashedWheelTimer.java index 550ee320ccb..d06f10d1166 100644 --- a/common/src/main/java/io/netty/util/HashedWheelTimer.java +++ b/common/src/main/java/io/netty/util/HashedWheelTimer.java @@ -631,7 +631,7 @@ public void expire() { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by " + - TimerTask.class.getSimpleName() + ".", t); + TimerTask.class.getSimpleName() + '.', t); } } diff --git a/common/src/main/java/io/netty/util/NetworkConstants.java b/common/src/main/java/io/netty/util/NetworkConstants.java index a4280e44345..ccb2d5129f3 100644 --- a/common/src/main/java/io/netty/util/NetworkConstants.java +++ b/common/src/main/java/io/netty/util/NetworkConstants.java @@ -20,10 +20,13 @@ import java.io.BufferedReader; import java.io.FileReader; +import java.io.IOException; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.Socket; import java.net.SocketException; -import java.net.UnknownHostException; import java.util.Enumeration; /** @@ -60,30 +63,26 @@ public final class NetworkConstants { static { //Start the process of discovering localhost - InetAddress localhost = null; - + InetAddress localhost; try { - //Let's start by getting localhost automatically localhost = InetAddress.getLocalHost(); - } catch (UnknownHostException e) { - //No? That's okay. + validateHost(localhost); + } catch (IOException e) { + // The default local host names did not work. Try hard-coded IPv4 address. try { - //Try to force an IPv4 localhost address localhost = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); - } catch (UnknownHostException e1) { - //No? Okay. You must be using IPv6 + validateHost(localhost); + } catch (IOException e1) { + // The hard-coded IPv4 address did not work. Try hard coded IPv6 address. try { - //Try to force an IPv6 localhost address - localhost = InetAddress.getByAddress( - new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }); - } catch (UnknownHostException e2) { - //No? Okay. - logger.error("Failed to resolve localhost - Incorrect network configuration?", e2); + localhost = InetAddress.getByAddress(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }); + validateHost(localhost); + } catch (IOException e2) { + throw new Error("Failed to resolve localhost - incorrect network configuration?", e2); } } } - //Set the localhost constant LOCALHOST = localhost; //Prepare to get the local NetworkInterface @@ -143,6 +142,41 @@ public final class NetworkConstants { SOMAXCONN = somaxconn; } + private static void validateHost(InetAddress host) throws IOException { + ServerSocket ss = null; + Socket s1 = null; + Socket s2 = null; + try { + ss = new ServerSocket(); + ss.setReuseAddress(false); + ss.bind(new InetSocketAddress(host, 0)); + s1 = new Socket(host, ss.getLocalPort()); + s2 = ss.accept(); + } finally { + if (s2 != null) { + try { + s2.close(); + } catch (IOException e) { + // Ignore + } + } + if (s1 != null) { + try { + s1.close(); + } catch (IOException e) { + // Ignore + } + } + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + /** * A constructor to stop this class being constructed. */ diff --git a/common/src/main/java/io/netty/util/internal/DetectionUtil.java b/common/src/main/java/io/netty/util/internal/DetectionUtil.java index 21a06a3c9f9..a5e40a293b6 100644 --- a/common/src/main/java/io/netty/util/internal/DetectionUtil.java +++ b/common/src/main/java/io/netty/util/internal/DetectionUtil.java @@ -23,6 +23,7 @@ import java.util.Locale; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; /** @@ -41,6 +42,8 @@ public final class DetectionUtil { private static final boolean IS_ROOT; static { + + Pattern PERMISSION_DENIED = Pattern.compile(".*permission.*denied.*"); String os = SystemPropertyUtil.get("os.name", "").toLowerCase(Locale.UK); // windows IS_WINDOWS = os.contains("win"); @@ -63,7 +66,7 @@ public final class DetectionUtil { message = ""; } message = message.toLowerCase(); - if (message.matches(".*permission.*denied.*")) { + if (PERMISSION_DENIED.matcher(message).matches()) { break; } } finally { diff --git a/common/src/main/java/io/netty/util/internal/StringUtil.java b/common/src/main/java/io/netty/util/internal/StringUtil.java index ecdd72b8e2b..3c5f94bb5e4 100644 --- a/common/src/main/java/io/netty/util/internal/StringUtil.java +++ b/common/src/main/java/io/netty/util/internal/StringUtil.java @@ -15,7 +15,9 @@ */ package io.netty.util.internal; +import java.util.ArrayList; import java.util.Formatter; +import java.util.List; /** * String utility class. @@ -40,4 +42,47 @@ private StringUtil() { NEWLINE = newLine; } + + private static final String EMPTY_STRING = ""; + + /** + * Splits the specified {@link String} with the specified delimiter. This operation is a simplified and optimized + * version of {@link String#split(String)}. + */ + public static String[] split(String value, char delim) { + final int end = value.length(); + final List res = new ArrayList(); + + int start = 0; + for (int i = 0; i < end; i ++) { + if (value.charAt(i) == delim) { + if (start == i) { + res.add(EMPTY_STRING); + } else { + res.add(value.substring(start, i)); + } + start = i + 1; + } + } + + if (start == 0) { // If no delimiter was found in the value + res.add(value); + } else { + if (start != end) { + // Add the last element if it's not empty. + res.add(value.substring(start, end)); + } else { + // Truncate trailing empty elements. + for (int i = res.size() - 1; i >= 0; i --) { + if (res.get(i).isEmpty()) { + res.remove(i); + } else { + break; + } + } + } + } + + return res.toArray(new String[res.size()]); + } } diff --git a/common/src/main/java/io/netty/util/internal/SystemPropertyUtil.java b/common/src/main/java/io/netty/util/internal/SystemPropertyUtil.java index 929838912c7..0974cdf11a1 100644 --- a/common/src/main/java/io/netty/util/internal/SystemPropertyUtil.java +++ b/common/src/main/java/io/netty/util/internal/SystemPropertyUtil.java @@ -20,12 +20,14 @@ import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; /** * A collection of utility methods to retrieve and parse the values of the Java system properties. */ public final class SystemPropertyUtil { + @SuppressWarnings("all") private static boolean initializedLogger; private static final InternalLogger logger; private static boolean loggedException; @@ -66,7 +68,7 @@ public static String get(String key, String def) { if (key == null) { throw new NullPointerException("key"); } - if (key.length() == 0) { + if (key.isEmpty()) { throw new IllegalArgumentException("key must not be empty."); } @@ -103,7 +105,7 @@ public static boolean getBoolean(String key, boolean def) { } value = value.trim().toLowerCase(); - if (value.length() == 0) { + if (value.isEmpty()) { return true; } @@ -122,6 +124,8 @@ public static boolean getBoolean(String key, boolean def) { return def; } + private static final Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); + /** * Returns the value of the Java system property with the specified * {@code key}, while falling back to the specified default value if @@ -138,7 +142,7 @@ public static int getInt(String key, int def) { } value = value.trim().toLowerCase(); - if (value.matches("-?[0-9]+")) { + if (INTEGER_PATTERN.matcher(value).matches()) { try { return Integer.parseInt(value); } catch (Exception e) { @@ -169,7 +173,7 @@ public static long getLong(String key, long def) { } value = value.trim().toLowerCase(); - if (value.matches("-?[0-9]+")) { + if (INTEGER_PATTERN.matcher(value).matches()) { try { return Long.parseLong(value); } catch (Exception e) { diff --git a/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java b/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java index efd5a44a70f..433067168ce 100644 --- a/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java +++ b/common/src/main/java/io/netty/util/internal/jzlib/Deflate.java @@ -256,10 +256,10 @@ private void lm_init() { window_size = 2 * w_size; // Set the default configuration parameters: - max_lazy_match = Deflate.config_table[level].max_lazy; - good_match = Deflate.config_table[level].good_length; - nice_match = Deflate.config_table[level].nice_length; - max_chain_length = Deflate.config_table[level].max_chain; + max_lazy_match = config_table[level].max_lazy; + good_match = config_table[level].good_length; + nice_match = config_table[level].nice_length; + max_chain_length = config_table[level].max_chain; strstart = 0; block_start = 0; diff --git a/common/src/test/java/io/netty/util/HashedWheelTimerMonitorTest.java b/common/src/test/java/io/netty/util/HashedWheelTimerMonitorTest.java index 359e0830ce5..d900226cf37 100644 --- a/common/src/test/java/io/netty/util/HashedWheelTimerMonitorTest.java +++ b/common/src/test/java/io/netty/util/HashedWheelTimerMonitorTest.java @@ -63,12 +63,12 @@ private static class RecordingMonitorRegistry implements MonitorRegistry { @Override public ValueDistributionMonitor newValueDistributionMonitor(final MonitorName monitorName) { - return this.eventDistributionMonitor; + return eventDistributionMonitor; } @Override public EventRateMonitor newEventRateMonitor(final MonitorName monitorName, final TimeUnit rateUnit) { - return this.eventRateMonitor; + return eventRateMonitor; } @Override diff --git a/common/src/test/java/io/netty/util/NetworkConstantsTest.java b/common/src/test/java/io/netty/util/NetworkConstantsTest.java index f9f75ffda7a..aba727dc52f 100644 --- a/common/src/test/java/io/netty/util/NetworkConstantsTest.java +++ b/common/src/test/java/io/netty/util/NetworkConstantsTest.java @@ -15,19 +15,15 @@ */ package io.netty.util; -import static org.junit.Assert.*; - -import java.net.InetAddress; -import java.net.UnknownHostException; - import org.junit.Test; +import static org.junit.Assert.*; + public class NetworkConstantsTest { @Test - public void testLocalhost() throws UnknownHostException { + public void testLocalhost() { assertNotNull(NetworkConstants.LOCALHOST); - assertEquals(NetworkConstants.LOCALHOST, InetAddress.getLocalHost()); } @Test diff --git a/common/src/test/java/io/netty/util/internal/StringUtilTest.java b/common/src/test/java/io/netty/util/internal/StringUtilTest.java index 82afd97d8f1..4ff03c28bd3 100644 --- a/common/src/test/java/io/netty/util/internal/StringUtilTest.java +++ b/common/src/test/java/io/netty/util/internal/StringUtilTest.java @@ -15,14 +15,39 @@ */ package io.netty.util.internal; -import static org.junit.Assert.assertNotNull; import org.junit.Test; +import static org.junit.Assert.*; + public class StringUtilTest { @Test public void ensureNewlineExists() { assertNotNull(StringUtil.NEWLINE); } - + + @Test + public void splitSimple() { + assertArrayEquals(new String[] { "foo", "bar" }, StringUtil.split("foo:bar", ':')); + } + + @Test + public void splitWithTrailingDelimiter() { + assertArrayEquals(new String[] { "foo", "bar" }, StringUtil.split("foo,bar,", ',')); + } + + @Test + public void splitWithTrailingDelimiters() { + assertArrayEquals(new String[] { "foo", "bar" }, StringUtil.split("foo!bar!!", '!')); + } + + @Test + public void splitWithConsecutiveDelimiters() { + assertArrayEquals(new String[] { "foo", "", "bar" }, StringUtil.split("foo$$bar", '$')); + } + + @Test + public void splitWithDelimiterAtBeginning() { + assertArrayEquals(new String[] { "", "foo", "bar" }, StringUtil.split("#foo#bar", '#')); + } } diff --git a/example/pom.xml b/example/pom.xml index 875bffc0680..b0543b6a3ee 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-example diff --git a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java index ab55d0ad778..ff92a806aba 100644 --- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java +++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java @@ -15,11 +15,6 @@ */ package io.netty.example.http.file; -import static io.netty.handler.codec.http.HttpHeaders.*; -import static io.netty.handler.codec.http.HttpHeaders.Names.*; -import static io.netty.handler.codec.http.HttpMethod.*; -import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static io.netty.handler.codec.http.HttpVersion.*; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -33,6 +28,7 @@ import io.netty.handler.stream.ChunkedFile; import io.netty.util.CharsetUtil; +import javax.activation.MimetypesFileTypeMap; import java.io.File; import java.io.FileNotFoundException; import java.io.RandomAccessFile; @@ -44,8 +40,13 @@ import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; +import java.util.regex.Pattern; -import javax.activation.MimetypesFileTypeMap; +import static io.netty.handler.codec.http.HttpHeaders.Names.*; +import static io.netty.handler.codec.http.HttpHeaders.*; +import static io.netty.handler.codec.http.HttpMethod.*; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.*; /** * A simple handler that serves incoming HTTP requests to send their respective @@ -141,8 +142,8 @@ public void messageReceived( } // Cache Validation - String ifModifiedSince = request.getHeader(HttpHeaders.Names.IF_MODIFIED_SINCE); - if (ifModifiedSince != null && !ifModifiedSince.equals("")) { + String ifModifiedSince = request.getHeader(IF_MODIFIED_SINCE); + if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince); @@ -194,6 +195,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } } + private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); + private static String sanitizeUri(String uri) { // Decode the path. try { @@ -215,10 +218,10 @@ private static String sanitizeUri(String uri) { // Simplistic dumb security check. // You will have to do something serious in the production environment. - if (uri.contains(File.separator + ".") || - uri.contains("." + File.separator) || + if (uri.contains(File.separator + '.') || + uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || - uri.matches(".*[<>&\"].*")) { + INSECURE_URI.matcher(uri).matches()) { return null; } @@ -226,8 +229,10 @@ private static String sanitizeUri(String uri) { return System.getProperty("user.dir") + File.separator + uri; } + private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*"); + private static void sendListing(ChannelHandlerContext ctx, File dir) { - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK); + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); response.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); StringBuilder buf = new StringBuilder(); @@ -252,7 +257,7 @@ private static void sendListing(ChannelHandlerContext ctx, File dir) { } String name = f.getName(); - if (!name.matches("[A-Za-z0-9][-_A-Za-z0-9\\.]*")) { + if (!ALLOWED_FILE_NAME.matcher(name).matches()) { continue; } @@ -297,7 +302,7 @@ private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus stat * Context */ private static void sendNotModified(ChannelHandlerContext ctx) { - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_MODIFIED); + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, NOT_MODIFIED); setDateHeader(response); // Close the connection as soon as the error message is sent. @@ -315,7 +320,7 @@ private static void setDateHeader(HttpResponse response) { dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); Calendar time = new GregorianCalendar(); - response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime())); + response.setHeader(DATE, dateFormatter.format(time.getTime())); } /** @@ -332,14 +337,14 @@ private static void setDateAndCacheHeaders(HttpResponse response, File fileToCac // Date header Calendar time = new GregorianCalendar(); - response.setHeader(HttpHeaders.Names.DATE, dateFormatter.format(time.getTime())); + response.setHeader(DATE, dateFormatter.format(time.getTime())); // Add cache headers time.add(Calendar.SECOND, HTTP_CACHE_SECONDS); - response.setHeader(HttpHeaders.Names.EXPIRES, dateFormatter.format(time.getTime())); - response.setHeader(HttpHeaders.Names.CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS); + response.setHeader(EXPIRES, dateFormatter.format(time.getTime())); + response.setHeader(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS); response.setHeader( - HttpHeaders.Names.LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified()))); + LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified()))); } /** @@ -352,7 +357,7 @@ private static void setDateAndCacheHeaders(HttpResponse response, File fileToCac */ private static void setContentTypeHeader(HttpResponse response, File file) { MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap(); - response.setHeader(HttpHeaders.Names.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath())); + response.setHeader(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath())); } } diff --git a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java index 1b13a0ecccf..fe9d5944075 100644 --- a/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java +++ b/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClientInitializer.java @@ -23,6 +23,7 @@ import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.ssl.SslHandler; import javax.net.ssl.SSLEngine; @@ -46,8 +47,7 @@ public void initChannel(SocketChannel ch) throws Exception { SecureChatSslContextFactory.getClientContext().createSSLEngine(); engine.setUseClientMode(true); - // FIXME: Port SslHandler to the new API - //p.addLast("ssl", new SslHandler(engine)); + p.addLast("ssl", new SslHandler(engine)); } p.addLast("codec", new HttpClientCodec()); diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java new file mode 100644 index 00000000000..2261c7cccce --- /dev/null +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java @@ -0,0 +1,974 @@ +/* + * 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.example.http.upload; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.socket.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.ClientCookieEncoder; +import io.netty.handler.codec.http.DefaultCookie; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.QueryStringEncoder; +import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.DiskAttribute; +import io.netty.handler.codec.http.multipart.DiskFileUpload; +import io.netty.handler.codec.http.multipart.HttpDataFactory; +import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder; +import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder.ErrorDataEncoderException; +import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map.Entry; + +/** + * This class is meant to be run against {@link HttpUploadServer}. + */ +public class HttpUploadClient { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpUploadClient.class); + + private final String baseUri; + private final String filePath; + + public HttpUploadClient(String baseUri, String filePath) { + this.baseUri = baseUri; + this.filePath = filePath; + } + + public void run() throws Exception { + String postSimple, postFile, get; + if (baseUri.endsWith("/")) { + postSimple = baseUri + "formpost"; + postFile = baseUri + "formpostmultipart"; + get = baseUri + "formget"; + } else { + postSimple = baseUri + "/formpost"; + postFile = baseUri + "/formpostmultipart"; + get = baseUri + "/formget"; + } + URI uriSimple; + try { + uriSimple = new URI(postSimple); + } catch (URISyntaxException e) { + logger.error("Invalid URI syntax" + e.getCause()); + return; + } + String scheme = uriSimple.getScheme() == null ? "http" : uriSimple.getScheme(); + String host = uriSimple.getHost() == null ? "localhost" : uriSimple.getHost(); + int port = uriSimple.getPort(); + if (port == -1) { + if (scheme.equalsIgnoreCase("http")) { + port = 80; + } else if (scheme.equalsIgnoreCase("https")) { + port = 443; + } + } + + if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) { + logger.error("Only HTTP(S) is supported."); + return; + } + + boolean ssl = scheme.equalsIgnoreCase("https"); + + URI uriFile; + try { + uriFile = new URI(postFile); + } catch (URISyntaxException e) { + logger.error("Error: " + e.getMessage()); + return; + } + File file = new File(filePath); + if (!file.canRead()) { + logger.error("A correct path is needed"); + return; + } + + // Configure the client. + Bootstrap b = new Bootstrap(); + b.group(new NioEventLoopGroup()).channel(NioSocketChannel.class).handler(new HttpUploadClientIntializer(ssl)); + + // setup the factory: here using a mixed memory/disk based on size threshold + HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if MINSIZE exceed + + DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file on exit (in normal exit) + DiskFileUpload.baseDirectory = null; // system temp directory + DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on exit (in normal exit) + DiskAttribute.baseDirectory = null; // system temp directory + + // Simple Get form: no factory used (not usable) + List> headers = formGet(b, host, port, get, uriSimple); + if (headers == null) { + factory.cleanAllHttpDatas(); + return; + } + + // Simple Post form: factory used for big attributes + List bodylist = formPost(b, host, port, uriSimple, file, factory, headers); + if (bodylist == null) { + factory.cleanAllHttpDatas(); + return; + } + + // Multipart Post form: factory used + formPostMultipart(b, host, port, uriFile, factory, headers, bodylist); + + // Shut down executor threads to exit. + b.shutdown(); + + // Really clean all temporary files if they still exist + factory.cleanAllHttpDatas(); + } + + /** + * Standard usage of HTTP API in Netty without file Upload (get is not able to achieve File upload due to limitation + * on request size). + * + * @return the list of headers that will be used in every example after + **/ + private static List> formGet(Bootstrap bootstrap, String host, int port, String get, + URI uriSimple) throws Exception { + // Start the connection attempt. + // No use of HttpPostRequestEncoder since not a POST + bootstrap.remoteAddress(new InetSocketAddress(host, port)); + Channel channel = bootstrap.connect().sync().channel(); + + // Prepare the HTTP request. + QueryStringEncoder encoder = new QueryStringEncoder(get); + // add Form attribute + encoder.addParam("getform", "GET"); + encoder.addParam("info", "first value"); + encoder.addParam("secondinfo", "secondvalue ���&"); + // not the big one since it is not compatible with GET size + // encoder.addParam("thirdinfo", textArea); + encoder.addParam("thirdinfo", "third value\r\ntest second line\r\n\r\nnew line\r\n"); + encoder.addParam("Send", "Send"); + + URI uriGet; + try { + uriGet = new URI(encoder.toString()); + } catch (URISyntaxException e) { + logger.error("Error: " + e.getMessage()); + return null; + } + + HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uriGet.toASCIIString()); + request.setHeader(HttpHeaders.Names.HOST, host); + request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); + request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP + ',' + + HttpHeaders.Values.DEFLATE); + + request.setHeader(HttpHeaders.Names.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + request.setHeader(HttpHeaders.Names.ACCEPT_LANGUAGE, "fr"); + request.setHeader(HttpHeaders.Names.REFERER, uriSimple.toString()); + request.setHeader(HttpHeaders.Names.USER_AGENT, "Netty Simple Http Client side"); + request.setHeader(HttpHeaders.Names.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + + // connection will not close but needed + // request.setHeader("Connection","keep-alive"); + // request.setHeader("Keep-Alive","300"); + + request.setHeader(HttpHeaders.Names.COOKIE, ClientCookieEncoder.encode(new DefaultCookie("my-cookie", "foo"), + new DefaultCookie("another-cookie", "bar"))); + + // send request + List> headers = request.getHeaders(); + channel.write(request); + + // Wait for the server to close the connection. + channel.closeFuture().sync(); + + return headers; + } + + /** + * Standard post without multipart but already support on Factory (memory management) + * + * @return the list of HttpData object (attribute and file) to be reused on next post + */ + private static List formPost(Bootstrap bootstrap, String host, int port, URI uriSimple, + File file, HttpDataFactory factory, List> headers) throws Exception { + + // Start the connection attempt + bootstrap.remoteAddress(new InetSocketAddress(host, port)); + Channel channel = bootstrap.connect().sync().channel(); + + // Prepare the HTTP request. + HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriSimple.toASCIIString()); + + // Use the PostBody encoder + HttpPostRequestEncoder bodyRequestEncoder = null; + try { + bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, false); // false not multipart + } catch (NullPointerException e) { + // should not be since args are not null + e.printStackTrace(); + } catch (ErrorDataEncoderException e) { + // test if method is a POST method + e.printStackTrace(); + } + + // it is legal to add directly header or cookie into the request until finalize + for (Entry entry : headers) { + request.setHeader(entry.getKey(), entry.getValue()); + } + + // add Form attribute + try { + bodyRequestEncoder.addBodyAttribute("getform", "POST"); + bodyRequestEncoder.addBodyAttribute("info", "first value"); + bodyRequestEncoder.addBodyAttribute("secondinfo", "secondvalue ���&"); + bodyRequestEncoder.addBodyAttribute("thirdinfo", textArea); + bodyRequestEncoder.addBodyFileUpload("myfile", file, "application/x-zip-compressed", false); + bodyRequestEncoder.addBodyAttribute("Send", "Send"); + } catch (NullPointerException e) { + // should not be since not null args + e.printStackTrace(); + } catch (ErrorDataEncoderException e) { + // if an encoding error occurs + e.printStackTrace(); + } + + // finalize request + try { + request = bodyRequestEncoder.finalizeRequest(); + } catch (ErrorDataEncoderException e) { + // if an encoding error occurs + e.printStackTrace(); + } + + // Create the bodylist to be reused on the last version with Multipart support + List bodylist = bodyRequestEncoder.getBodyListAttributes(); + + // send request + channel.write(request); + + // test if request was chunked and if so, finish the write + if (bodyRequestEncoder.isChunked()) { + // could do either request.isChunked() + // either do it through ChunkedWriteHandler + channel.write(bodyRequestEncoder).awaitUninterruptibly(); + } + + // Do not clear here since we will reuse the InterfaceHttpData on the + // next request + // for the example (limit action on client side). Take this as a + // broadcast of the same + // request on both Post actions. + // + // On standard program, it is clearly recommended to clean all files + // after each request + // bodyRequestEncoder.cleanFiles(); + + // Wait for the server to close the connection. + channel.closeFuture().sync(); + + return bodylist; + } + + /** + * Multipart example + */ + private static void formPostMultipart(Bootstrap bootstrap, String host, int port, URI uriFile, + HttpDataFactory factory, List> headers, List bodylist) + throws Exception { + + // Start the connection attempt + bootstrap.remoteAddress(new InetSocketAddress(host, port)); + Channel channel = bootstrap.connect().sync().channel(); + + // Prepare the HTTP request. + HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uriFile.toASCIIString()); + + // Use the PostBody encoder + HttpPostRequestEncoder bodyRequestEncoder = null; + try { + bodyRequestEncoder = new HttpPostRequestEncoder(factory, request, true); // true => multipart + } catch (NullPointerException e) { + // should not be since no null args + e.printStackTrace(); + } catch (ErrorDataEncoderException e) { + // test if method is a POST method + e.printStackTrace(); + } + + // it is legal to add directly header or cookie into the request until finalize + for (Entry entry : headers) { + request.setHeader(entry.getKey(), entry.getValue()); + } + + // add Form attribute from previous request in formpost() + try { + bodyRequestEncoder.setBodyHttpDatas(bodylist); + } catch (NullPointerException e1) { + // should not be since previously created + e1.printStackTrace(); + } catch (ErrorDataEncoderException e1) { + // again should not be since previously encoded (except if an error + // occurs previously) + e1.printStackTrace(); + } + + // finalize request + try { + bodyRequestEncoder.finalizeRequest(); + } catch (ErrorDataEncoderException e) { + // if an encoding error occurs + e.printStackTrace(); + } + + // send request + channel.write(request); + + // test if request was chunked and if so, finish the write + if (bodyRequestEncoder.isChunked()) { + channel.write(bodyRequestEncoder).awaitUninterruptibly(); + } + + // Now no more use of file representation (and list of HttpData) + bodyRequestEncoder.cleanFiles(); + + // Wait for the server to close the connection. + channel.closeFuture().sync(); + } + + public static void main(String[] args) throws Exception { + String baseUri; + String filePath; + if (args.length == 2) { + baseUri = args[0]; + filePath = args[1]; + } else { + baseUri = "http://localhost:8080"; + + File f = File.createTempFile("upload", ".txt"); + BufferedWriter bw = new BufferedWriter(new FileWriter(f)); + bw.write("Some text data in a file to be posted"); + bw.close(); + filePath = f.getPath(); + f.deleteOnExit(); + } + + logger.info("Posting to " + baseUri + ". Using file " + filePath); + new HttpUploadClient(baseUri, filePath).run(); + } + + // use to simulate a small TEXTAREA field in a form + private static final String textArea = "short text"; + // use to simulate a big TEXTAREA field in a form + /* + private static final String textAreaLong = + "lkjlkjlKJLKJLKJLKJLJlkj lklkj\r\n\r\nLKJJJJJJJJKKKKKKKKKKKKKKK ����&\r\n\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n" + + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\r\n"; + */ +} diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java new file mode 100644 index 00000000000..082da90628e --- /dev/null +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java @@ -0,0 +1,79 @@ +/* + * 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.example.http.upload; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import io.netty.handler.codec.http.HttpChunk; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; +import io.netty.util.CharsetUtil; + +/** + * Handler that just dumps the contents of the response from the server + */ +public class HttpUploadClientHandler extends ChannelInboundMessageHandlerAdapter { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpUploadClientHandler.class); + + private boolean readingChunks; + + @Override + public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!readingChunks) { + HttpResponse response = (HttpResponse) msg; + + logger.info("STATUS: " + response.getStatus()); + logger.info("VERSION: " + response.getProtocolVersion()); + + if (!response.getHeaderNames().isEmpty()) { + for (String name : response.getHeaderNames()) { + for (String value : response.getHeaders(name)) { + logger.info("HEADER: " + name + " = " + value); + } + } + } + + if (response.getStatus().getCode() == 200 && response.getTransferEncoding().isMultiple()) { + readingChunks = true; + logger.info("CHUNKED CONTENT {"); + } else { + ByteBuf content = response.getContent(); + if (content.readable()) { + logger.info("CONTENT {"); + logger.info(content.toString(CharsetUtil.UTF_8)); + logger.info("} END OF CONTENT"); + } + } + } else { + HttpChunk chunk = (HttpChunk) msg; + if (chunk.isLast()) { + readingChunks = false; + logger.info("} END OF CHUNKED CONTENT"); + } else { + logger.info(chunk.getContent().toString(CharsetUtil.UTF_8)); + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + cause.printStackTrace(); + ctx.channel().close(); + } +} diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java new file mode 100644 index 00000000000..1e7eaa6b5c6 --- /dev/null +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientIntializer.java @@ -0,0 +1,57 @@ +/* + * 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.example.http.upload; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.example.securechat.SecureChatSslContextFactory; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpContentDecompressor; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.stream.ChunkedWriteHandler; + +import javax.net.ssl.SSLEngine; + +public class HttpUploadClientIntializer extends ChannelInitializer { + private final boolean ssl; + + public HttpUploadClientIntializer(boolean ssl) { + this.ssl = ssl; + } + + @Override + public void initChannel(SocketChannel ch) throws Exception { + // Create a default pipeline implementation. + ChannelPipeline pipeline = ch.pipeline(); + + if (ssl) { + SSLEngine engine = SecureChatSslContextFactory.getClientContext().createSSLEngine(); + engine.setUseClientMode(true); + pipeline.addLast("ssl", new SslHandler(engine)); + } + + pipeline.addLast("codec", new HttpClientCodec()); + + // Remove the following line if you don't want automatic content decompression. + pipeline.addLast("inflater", new HttpContentDecompressor()); + + // to be used since huge file transfer + pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); + + pipeline.addLast("handler", new HttpUploadClientHandler()); + } +} diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServer.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServer.java new file mode 100644 index 00000000000..d5bfa007af6 --- /dev/null +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServer.java @@ -0,0 +1,63 @@ +/* + * 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.example.http.upload; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.socket.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; + +/** + * A HTTP server showing how to use the HTTP multipart package for file uploads and decoding post data. + */ +public class HttpUploadServer { + + private final int port; + public static boolean isSSL; + + public HttpUploadServer(int port) { + this.port = port; + } + + public void run() throws Exception { + ServerBootstrap b = new ServerBootstrap(); + try { + b.group(new NioEventLoopGroup(), new NioEventLoopGroup()).channel(NioServerSocketChannel.class) + .localAddress(port).childHandler(new HttpUploadServerInitializer()); + + Channel ch = b.bind().sync().channel(); + System.out.println("HTTP Upload Server at port " + port + '.'); + System.out.println("Open your browser and navigate to http://localhost:" + port + '/'); + + ch.closeFuture().sync(); + } finally { + b.shutdown(); + } + } + + public static void main(String[] args) throws Exception { + int port; + if (args.length > 0) { + port = Integer.parseInt(args[0]); + } else { + port = 8080; + } + if (args.length > 1) { + isSSL = true; + } + new HttpUploadServer(port).run(); + } +} diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java new file mode 100644 index 00000000000..219328dfdc8 --- /dev/null +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerHandler.java @@ -0,0 +1,428 @@ +/* + * 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.example.http.upload; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundMessageHandlerAdapter; +import io.netty.handler.codec.http.Cookie; +import io.netty.handler.codec.http.CookieDecoder; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpChunk; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.ServerCookieEncoder; +import io.netty.handler.codec.http.multipart.Attribute; +import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; +import io.netty.handler.codec.http.multipart.DiskAttribute; +import io.netty.handler.codec.http.multipart.DiskFileUpload; +import io.netty.handler.codec.http.multipart.FileUpload; +import io.netty.handler.codec.http.multipart.HttpDataFactory; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException; +import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; +import io.netty.util.CharsetUtil; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import static io.netty.buffer.Unpooled.*; +import static io.netty.handler.codec.http.HttpHeaders.Names.*; + +public class HttpUploadServerHandler extends ChannelInboundMessageHandlerAdapter { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpUploadServerHandler.class); + + private HttpRequest request; + + private boolean readingChunks; + + private final StringBuilder responseContent = new StringBuilder(); + + private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); //Disk + // if + // size + // exceed + + private HttpPostRequestDecoder decoder; + static { + DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file + // on exit (in normal + // exit) + DiskFileUpload.baseDirectory = null; // system temp directory + DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on + // exit (in normal exit) + DiskAttribute.baseDirectory = null; // system temp directory + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + if (decoder != null) { + decoder.cleanFiles(); + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!readingChunks) { + // clean previous FileUpload if Any + if (decoder != null) { + decoder.cleanFiles(); + decoder = null; + } + + HttpRequest request = this.request = (HttpRequest) msg; + URI uri = new URI(request.getUri()); + if (!uri.getPath().startsWith("/form")) { + // Write Menu + writeMenu(ctx); + return; + } + responseContent.setLength(0); + responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n"); + responseContent.append("===================================\r\n"); + + responseContent.append("VERSION: " + request.getProtocolVersion().getText() + "\r\n"); + + responseContent.append("REQUEST_URI: " + request.getUri() + "\r\n\r\n"); + responseContent.append("\r\n\r\n"); + + // new method + List> headers = request.getHeaders(); + for (Entry entry : headers) { + responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n"); + } + responseContent.append("\r\n\r\n"); + + // new method + Set cookies; + String value = request.getHeader(COOKIE); + if (value == null) { + cookies = Collections.emptySet(); + } else { + cookies = CookieDecoder.decode(value); + } + for (Cookie cookie : cookies) { + responseContent.append("COOKIE: " + cookie.toString() + "\r\n"); + } + responseContent.append("\r\n\r\n"); + + QueryStringDecoder decoderQuery = new QueryStringDecoder(request.getUri()); + Map> uriAttributes = decoderQuery.getParameters(); + for (Entry> attr: uriAttributes.entrySet()) { + for (String attrVal: attr.getValue()) { + responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n"); + } + } + responseContent.append("\r\n\r\n"); + + // if GET Method: should not try to create a HttpPostRequestDecoder + try { + decoder = new HttpPostRequestDecoder(factory, request); + } catch (ErrorDataDecoderException e1) { + e1.printStackTrace(); + responseContent.append(e1.getMessage()); + writeResponse(ctx.channel()); + ctx.channel().close(); + return; + } catch (IncompatibleDataDecoderException e1) { + // GET Method: should not try to create a HttpPostRequestDecoder + // So OK but stop here + responseContent.append(e1.getMessage()); + responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n"); + writeResponse(ctx.channel()); + return; + } + + responseContent.append("Is Chunked: " + request.getTransferEncoding().isMultiple() + "\r\n"); + responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n"); + if (request.getTransferEncoding().isMultiple()) { + // Chunk version + responseContent.append("Chunks: "); + readingChunks = true; + } else { + // Not chunk version + readHttpDataAllReceive(ctx.channel()); + responseContent.append("\r\n\r\nEND OF NOT CHUNKED CONTENT\r\n"); + writeResponse(ctx.channel()); + } + } else { + // New chunk is received + HttpChunk chunk = (HttpChunk) msg; + try { + decoder.offer(chunk); + } catch (ErrorDataDecoderException e1) { + e1.printStackTrace(); + responseContent.append(e1.getMessage()); + writeResponse(ctx.channel()); + ctx.channel().close(); + return; + } + responseContent.append('o'); + // example of reading chunk by chunk (minimize memory usage due to + // Factory) + readHttpDataChunkByChunk(ctx.channel()); + // example of reading only if at the end + if (chunk.isLast()) { + readHttpDataAllReceive(ctx.channel()); + writeResponse(ctx.channel()); + readingChunks = false; + } + } + } + + /** + * Example of reading all InterfaceHttpData from finished transfer + * + * @param channel + */ + private void readHttpDataAllReceive(Channel channel) { + List datas = null; + try { + datas = decoder.getBodyHttpDatas(); + } catch (NotEnoughDataDecoderException e1) { + // Should not be! + e1.printStackTrace(); + responseContent.append(e1.getMessage()); + writeResponse(channel); + channel.close(); + return; + } + for (InterfaceHttpData data : datas) { + writeHttpData(data); + } + responseContent.append("\r\n\r\nEND OF CONTENT AT FINAL END\r\n"); + } + + /** + * Example of reading request by chunk and getting values from chunk to chunk + * + * @param channel + */ + private void readHttpDataChunkByChunk(Channel channel) { + try { + while (decoder.hasNext()) { + InterfaceHttpData data = decoder.next(); + if (data != null) { + // new value + writeHttpData(data); + } + } + } catch (EndOfDataDecoderException e1) { + // end + responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n"); + } + } + + private void writeHttpData(InterfaceHttpData data) { + if (data.getHttpDataType() == HttpDataType.Attribute) { + Attribute attribute = (Attribute) data; + String value; + try { + value = attribute.getValue(); + } catch (IOException e1) { + // Error while reading data from File, only print name and error + e1.printStackTrace(); + responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + + attribute.getName() + " Error while reading value: " + e1.getMessage() + "\r\n"); + return; + } + if (value.length() > 100) { + responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + + attribute.getName() + " data too long\r\n"); + } else { + responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + + attribute.toString() + "\r\n"); + } + } else { + responseContent.append("\r\nBODY FileUpload: " + data.getHttpDataType().name() + ": " + data.toString() + + "\r\n"); + if (data.getHttpDataType() == HttpDataType.FileUpload) { + FileUpload fileUpload = (FileUpload) data; + if (fileUpload.isCompleted()) { + if (fileUpload.length() < 10000) { + responseContent.append("\tContent of file\r\n"); + try { + responseContent.append(fileUpload.getString(fileUpload.getCharset())); + } catch (IOException e1) { + // do nothing for the example + e1.printStackTrace(); + } + responseContent.append("\r\n"); + } else { + responseContent.append("\tFile too long to be printed out:" + fileUpload.length() + "\r\n"); + } + // fileUpload.isInMemory();// tells if the file is in Memory + // or on File + // fileUpload.renameTo(dest); // enable to move into another + // File dest + // decoder.removeFileUploadFromClean(fileUpload); //remove + // the File of to delete file + } else { + responseContent.append("\tFile to be continued but should not!\r\n"); + } + } + } + } + + private void writeResponse(Channel channel) { + // Convert the response content to a ChannelBuffer. + ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8); + responseContent.setLength(0); + + // Decide whether to close the connection or not. + boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request.getHeader(CONNECTION)) + || request.getProtocolVersion().equals(HttpVersion.HTTP_1_0) + && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request.getHeader(CONNECTION)); + + // Build the response object. + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + response.setContent(buf); + response.setHeader(CONTENT_TYPE, "text/plain; charset=UTF-8"); + + if (!close) { + // There's no need to add 'Content-Length' header + // if this is the last response. + response.setHeader(CONTENT_LENGTH, String.valueOf(buf.readableBytes())); + } + + Set cookies; + String value = request.getHeader(COOKIE); + if (value == null) { + cookies = Collections.emptySet(); + } else { + cookies = CookieDecoder.decode(value); + } + if (!cookies.isEmpty()) { + // Reset the cookies if necessary. + for (Cookie cookie : cookies) { + response.addHeader(SET_COOKIE, ServerCookieEncoder.encode(cookie)); + } + } + // Write the response. + ChannelFuture future = channel.write(response); + // Close the connection after the write operation is done if necessary. + if (close) { + future.addListener(ChannelFutureListener.CLOSE); + } + } + + private void writeMenu(ChannelHandlerContext ctx) { + // print several HTML forms + // Convert the response content to a ChannelBuffer. + responseContent.setLength(0); + + // create Pseudo Menu + responseContent.append(""); + responseContent.append(""); + responseContent.append("Netty Test Form\r\n"); + responseContent.append("\r\n"); + responseContent.append(""); + + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append("
"); + responseContent.append("

Netty Test Form

"); + responseContent.append("Choose one FORM"); + responseContent.append("
\r\n"); + + // GET + responseContent.append("
GET FORM
"); + responseContent.append("
"); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append("
Fill with value:
Fill with value:
"); + responseContent + .append("
Fill with value:
"); + responseContent.append("
\r\n"); + responseContent.append("

"); + + // POST + responseContent.append("
POST FORM
"); + responseContent.append("
"); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append("
Fill with value:
Fill with value:
"); + responseContent + .append("
Fill with value:
"); + responseContent.append("
Fill with file (only file name will be transmitted):
" + + ""); + responseContent.append("
\r\n"); + responseContent.append("

"); + + // POST with enctype="multipart/form-data" + responseContent.append("
POST MULTIPART FORM
"); + responseContent.append("
"); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append(""); + responseContent.append("
Fill with value:
Fill with value:
"); + responseContent + .append("
Fill with value:
"); + responseContent.append("
Fill with file:
"); + responseContent.append("
\r\n"); + responseContent.append("

"); + + responseContent.append(""); + responseContent.append(""); + + ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8); + // Build the response object. + HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); + response.setContent(buf); + response.setHeader(CONTENT_TYPE, "text/html; charset=UTF-8"); + response.setHeader(CONTENT_LENGTH, String.valueOf(buf.readableBytes())); + // Write the response. + ctx.channel().write(response); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + logger.error(responseContent.toString(), cause); + ctx.channel().close(); + } +} diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java new file mode 100644 index 00000000000..1ef05adbbea --- /dev/null +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadServerInitializer.java @@ -0,0 +1,52 @@ +/* + * 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.example.http.upload; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.example.securechat.SecureChatSslContextFactory; +import io.netty.handler.codec.http.HttpContentCompressor; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.ssl.SslHandler; + +import javax.net.ssl.SSLEngine; + +public class HttpUploadServerInitializer extends ChannelInitializer { + @Override + public void initChannel(SocketChannel ch) throws Exception { + // Create a default pipeline implementation. + ChannelPipeline pipeline = ch.pipeline(); + + if (HttpUploadServer.isSSL) { + SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine(); + engine.setUseClientMode(false); + pipeline.addLast("ssl", new SslHandler(engine)); + } + + pipeline.addLast("decoder", new HttpRequestDecoder()); + pipeline.addLast("encoder", new HttpResponseEncoder()); + + // Remove the following line if you don't want automatic content + // compression. + pipeline.addLast("deflater", new HttpContentCompressor()); + + pipeline.addLast("handler", new HttpUploadServerHandler()); + + return; + } +} diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java index b852d3941f9..8902029a6ce 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClientHandler.java @@ -90,7 +90,7 @@ public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Except if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; throw new Exception("Unexpected HttpResponse (status=" + response.getStatus() + ", content=" - + response.getContent().toString(CharsetUtil.UTF_8) + ")"); + + response.getContent().toString(CharsetUtil.UTF_8) + ')'); } WebSocketFrame frame = (WebSocketFrame) msg; diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java index 91b4a3a655f..a51a0501536 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java @@ -27,7 +27,6 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundMessageHandlerAdapter; import io.netty.handler.codec.http.DefaultHttpResponse; -import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; @@ -145,6 +144,6 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } private static String getWebSocketLocation(HttpRequest req) { - return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH; + return "ws://" + req.getHeader(HOST) + WEBSOCKET_PATH; } } diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerIndexPage.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerIndexPage.java index 13958442ece..8431af67a97 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerIndexPage.java +++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerIndexPage.java @@ -34,7 +34,7 @@ public static ByteBuf getContent(String webSocketLocation) { "var socket;" + NEWLINE + "if (!window.WebSocket) {" + NEWLINE + " window.WebSocket = window.MozWebSocket;" + NEWLINE + - "}" + NEWLINE + + '}' + NEWLINE + "if (window.WebSocket) {" + NEWLINE + " socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE + " socket.onmessage = function(event) {" + NEWLINE + @@ -51,7 +51,7 @@ public static ByteBuf getContent(String webSocketLocation) { " };" + NEWLINE + "} else {" + NEWLINE + " alert(\"Your browser does not support Web Socket.\");" + NEWLINE + - "}" + NEWLINE + + '}' + NEWLINE + NEWLINE + "function send(message) {" + NEWLINE + " if (!window.WebSocket) { return; }" + NEWLINE + @@ -60,7 +60,7 @@ public static ByteBuf getContent(String webSocketLocation) { " } else {" + NEWLINE + " alert(\"The socket is not open.\");" + NEWLINE + " }" + NEWLINE + - "}" + NEWLINE + + '}' + NEWLINE + "" + NEWLINE + "
" + NEWLINE + "" + diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java index 71306591285..5968845172a 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerHandler.java @@ -28,7 +28,6 @@ import io.netty.channel.ChannelInboundMessageHandlerAdapter; import io.netty.example.http.websocketx.server.WebSocketServerIndexPage; import io.netty.handler.codec.http.DefaultHttpResponse; -import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; @@ -146,6 +145,6 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } private static String getWebSocketLocation(HttpRequest req) { - return "wss://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_PATH; + return "wss://" + req.getHeader(HOST) + WEBSOCKET_PATH; } } diff --git a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java index 34302920e43..902aba441ae 100644 --- a/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java +++ b/example/src/main/java/io/netty/example/http/websocketx/sslserver/WebSocketSslServerSslContext.java @@ -18,13 +18,12 @@ import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; import java.io.FileInputStream; import java.security.KeyStore; import java.security.Security; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; - /** * Creates a {@link SSLContext} for just server certificates. */ @@ -32,7 +31,7 @@ public final class WebSocketSslServerSslContext { private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketSslServerSslContext.class); private static final String PROTOCOL = "TLS"; - private SSLContext _serverContext; + private final SSLContext _serverContext; /** * Returns the singleton instance for this class @@ -47,14 +46,15 @@ public static WebSocketSslServerSslContext getInstance() { * * See http://en.wikipedia.org/wiki/Singleton_pattern */ - private static class SingletonHolder { - public static final WebSocketSslServerSslContext INSTANCE = new WebSocketSslServerSslContext(); + private interface SingletonHolder { + WebSocketSslServerSslContext INSTANCE = new WebSocketSslServerSslContext(); } /** * Constructor for singleton */ private WebSocketSslServerSslContext() { + SSLContext serverContext = null; try { // Key store (Server side certificate) String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); @@ -62,7 +62,6 @@ private WebSocketSslServerSslContext() { algorithm = "SunX509"; } - SSLContext serverContext; try { String keyStoreFilePath = System.getProperty("keystore.file.path"); String keyStoreFilePassword = System.getProperty("keystore.file.password"); @@ -83,13 +82,14 @@ private WebSocketSslServerSslContext() { } catch (Exception e) { throw new Error("Failed to initialize the server-side SSLContext", e); } - _serverContext = serverContext; } catch (Exception ex) { if (logger.isErrorEnabled()) { logger.error("Error initializing SslContextManager. " + ex.getMessage(), ex); } System.exit(1); + } finally { + _serverContext = serverContext; } } diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java index 18590c147ab..2cc0fc52de2 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeClient.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.regex.Pattern; /** * Sends a list of continent/city pairs to a {@link LocalTimeServer} to @@ -101,11 +102,13 @@ private static void printUsage() { " localhost 8080 America/New_York Asia/Seoul"); } + private static final Pattern CITY_PATTERN = Pattern.compile("^[_A-Za-z]+/[_A-Za-z]+$"); + private static List parseCities(String[] args, int offset) { List cities = new ArrayList(); for (int i = offset; i < args.length; i ++) { - if (!args[i].matches("^[_A-Za-z]+/[_A-Za-z]+$")) { - System.err.println("Syntax error: '" + args[i] + "'"); + if (!CITY_PATTERN.matcher(args[i]).matches()) { + System.err.println("Syntax error: '" + args[i] + '\''); printUsage(); return null; } diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeClientHandler.java b/example/src/main/java/io/netty/example/localtime/LocalTimeClientHandler.java index 6fa7eb5bb2a..9af7e0027ff 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeClientHandler.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeClientHandler.java @@ -32,12 +32,15 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; public class LocalTimeClientHandler extends ChannelInboundMessageHandlerAdapter { private static final Logger logger = Logger.getLogger( LocalTimeClientHandler.class.getName()); + private static final Pattern DELIM = Pattern.compile("/"); + // Stateful properties private volatile Channel channel; private final BlockingQueue answer = new LinkedBlockingQueue(); @@ -46,7 +49,7 @@ public List getLocalTimes(Collection cities) { Locations.Builder builder = Locations.newBuilder(); for (String c: cities) { - String[] components = c.split("/"); + String[] components = DELIM.split(c); builder.addLocation(Location.newBuilder(). setContinent(Continent.valueOf(components[0].toUpperCase())). setCity(components[1]).build()); diff --git a/example/src/main/java/io/netty/example/localtime/LocalTimeServerHandler.java b/example/src/main/java/io/netty/example/localtime/LocalTimeServerHandler.java index eca9d1b1aa2..b1145db62e8 100644 --- a/example/src/main/java/io/netty/example/localtime/LocalTimeServerHandler.java +++ b/example/src/main/java/io/netty/example/localtime/LocalTimeServerHandler.java @@ -43,7 +43,7 @@ public void messageReceived(ChannelHandlerContext ctx, Locations locations) thro for (Location l: locations.getLocationList()) { TimeZone tz = TimeZone.getTimeZone( toString(l.getContinent()) + '/' + l.getCity()); - Calendar calendar = Calendar.getInstance(tz); + Calendar calendar = getInstance(tz); calendar.setTimeInMillis(currentTime); builder.addLocalTime(LocalTime.newBuilder(). diff --git a/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java b/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java index f7fc649d54e..90a9f8e7403 100644 --- a/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java +++ b/example/src/main/java/io/netty/example/sctp/NioSctpEchoClient.java @@ -15,7 +15,6 @@ */ package io.netty.example.sctp; -import io.netty.bootstrap.AbstractBootstrap; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; diff --git a/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java b/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java index cfa8a37262f..28dafea70f6 100644 --- a/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java +++ b/example/src/main/java/io/netty/example/securechat/SecureChatTrustManagerFactory.java @@ -15,16 +15,14 @@ */ package io.netty.example.securechat; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactorySpi; import javax.net.ssl.X509TrustManager; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.X509Certificate; /** * Bogus {@link TrustManagerFactorySpi} which accepts any certificate @@ -39,8 +37,7 @@ public X509Certificate[] getAcceptedIssuers() { } @Override - public void checkClientTrusted( - X509Certificate[] chain, String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) { // Always trust - it is an example. // You should do something in the real world. // You will reach here only if you enabled client certificate auth, @@ -50,8 +47,7 @@ public void checkClientTrusted( } @Override - public void checkServerTrusted( - X509Certificate[] chain, String authType) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType) { // Always trust - it is an example. // You should do something in the real world. System.err.println( diff --git a/example/src/main/java/io/netty/example/telnet/TelnetServerHandler.java b/example/src/main/java/io/netty/example/telnet/TelnetServerHandler.java index 2073750a6d0..153516376b2 100644 --- a/example/src/main/java/io/netty/example/telnet/TelnetServerHandler.java +++ b/example/src/main/java/io/netty/example/telnet/TelnetServerHandler.java @@ -48,7 +48,7 @@ public void messageReceived(ChannelHandlerContext ctx, String request) throws Ex // Generate and write a response. String response; boolean close = false; - if (request.length() == 0) { + if (request.isEmpty()) { response = "Please type something.\r\n"; } else if (request.toLowerCase().equals("bye")) { response = "Have a good day!\r\n"; diff --git a/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java b/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java index 4cf69426a5f..d456342d889 100644 --- a/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java +++ b/example/src/main/java/io/netty/example/uptime/UptimeClientHandler.java @@ -77,7 +77,7 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { @Override public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { - println("Sleeping for: " + UptimeClient.RECONNECT_DELAY + "s"); + println("Sleeping for: " + UptimeClient.RECONNECT_DELAY + 's'); final EventLoop loop = ctx.channel().eventLoop(); loop.schedule(new Runnable() { diff --git a/handler/pom.xml b/handler/pom.xml index ae41f031037..eafa68ae6cf 100644 --- a/handler/pom.xml +++ b/handler/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-handler diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java index 2462ef01004..303673dd19a 100644 --- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java +++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java @@ -802,6 +802,7 @@ public void inboundBufferUpdated(final ChannelHandlerContext ctx) throws Excepti break; case FINISHED: setHandshakeSuccess(); + wrapLater = true; continue; case NOT_HANDSHAKING: break; diff --git a/metrics-yammer/pom.xml b/metrics-yammer/pom.xml index a9abd1876a8..ff69bb392b3 100644 --- a/metrics-yammer/pom.xml +++ b/metrics-yammer/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-metrics-yammer diff --git a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerCounterMonitor.java b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerCounterMonitor.java index 5c78af932cf..37b5efe2cc6 100644 --- a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerCounterMonitor.java +++ b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerCounterMonitor.java @@ -44,7 +44,7 @@ class YammerCounterMonitor implements CounterMonitor { */ @Override public void inc() { - this.delegate.inc(); + delegate.inc(); } /** @@ -52,7 +52,7 @@ public void inc() { */ @Override public void inc(final long delta) { - this.delegate.inc(delta); + delegate.inc(delta); } /** @@ -60,7 +60,7 @@ public void inc(final long delta) { */ @Override public void decr() { - this.delegate.dec(); + delegate.dec(); } /** @@ -68,7 +68,7 @@ public void decr() { */ @Override public void decr(final long delta) { - this.delegate.dec(delta); + delegate.dec(delta); } /** @@ -76,7 +76,7 @@ public void decr(final long delta) { */ @Override public void reset() { - this.delegate.clear(); + delegate.clear(); } /** @@ -84,6 +84,6 @@ public void reset() { */ @Override public String toString() { - return "YammerCounterMonitor(delegate=" + delegate + ")"; + return "YammerCounterMonitor(delegate=" + delegate + ')'; } } diff --git a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerEventRateMonitor.java b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerEventRateMonitor.java index 2e3f0b51e51..e8aee470beb 100644 --- a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerEventRateMonitor.java +++ b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerEventRateMonitor.java @@ -45,7 +45,7 @@ class YammerEventRateMonitor implements EventRateMonitor { */ @Override public void event() { - this.delegate.mark(); + delegate.mark(); } /** @@ -54,7 +54,7 @@ public void event() { */ @Override public void events(final long count) { - this.delegate.mark(count); + delegate.mark(count); } /** @@ -62,6 +62,6 @@ public void events(final long count) { */ @Override public String toString() { - return "YammerEventRateMonitor(delegate=" + delegate + ")"; + return "YammerEventRateMonitor(delegate=" + delegate + ')'; } } diff --git a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerMonitorRegistry.java b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerMonitorRegistry.java index b257d82596e..558778efec1 100644 --- a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerMonitorRegistry.java +++ b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerMonitorRegistry.java @@ -68,7 +68,7 @@ public YammerMonitorRegistry(final MetricsRegistry delegate) { */ @Override public ValueDistributionMonitor newValueDistributionMonitor(final MonitorName monitorName) { - final Histogram histogram = this.delegate.newHistogram(Utils.toMetricName(monitorName), true); + final Histogram histogram = delegate.newHistogram(Utils.toMetricName(monitorName), true); return new YammerValueDistributionMonitor(histogram); } @@ -80,7 +80,7 @@ public ValueDistributionMonitor newValueDistributionMonitor(final MonitorName mo */ @Override public EventRateMonitor newEventRateMonitor(final MonitorName monitorName, final TimeUnit rateUnit) { - final Meter meter = this.delegate.newMeter(Utils.toMetricName(monitorName), monitorName.getName(), rateUnit); + final Meter meter = delegate.newMeter(Utils.toMetricName(monitorName), monitorName.getName(), rateUnit); return new YammerEventRateMonitor(meter); } @@ -92,7 +92,7 @@ public EventRateMonitor newEventRateMonitor(final MonitorName monitorName, final */ @Override public ValueMonitor registerValueMonitor(final MonitorName monitorName, final ValueMonitor valueMonitor) { - this.delegate.newGauge(Utils.toMetricName(monitorName), new Gauge() { + delegate.newGauge(Utils.toMetricName(monitorName), new Gauge() { @Override public T value() { return valueMonitor.currentValue(); @@ -108,7 +108,7 @@ public T value() { */ @Override public CounterMonitor newCounterMonitor(MonitorName monitorName) { - final Counter counter = this.delegate.newCounter(Utils.toMetricName(monitorName)); + final Counter counter = delegate.newCounter(Utils.toMetricName(monitorName)); return new YammerCounterMonitor(counter); } @@ -117,6 +117,6 @@ public CounterMonitor newCounterMonitor(MonitorName monitorName) { */ @Override public String toString() { - return "YammerMonitorRegistry(delegate=" + delegate + ")"; + return "YammerMonitorRegistry(delegate=" + delegate + ')'; } } diff --git a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerValueDistributionMonitor.java b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerValueDistributionMonitor.java index f66ea8dac57..7c8f01ab663 100644 --- a/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerValueDistributionMonitor.java +++ b/metrics-yammer/src/main/java/io/netty/monitor/yammer/YammerValueDistributionMonitor.java @@ -44,7 +44,7 @@ final class YammerValueDistributionMonitor implements ValueDistributionMonitor { */ @Override public void reset() { - this.delegate.clear(); + delegate.clear(); } /** @@ -52,7 +52,7 @@ public void reset() { */ @Override public void update(final long value) { - this.delegate.update(value); + delegate.update(value); } /** @@ -60,6 +60,6 @@ public void update(final long value) { */ @Override public String toString() { - return "YammerEventDistributionMonitor(delegate=" + delegate + ")"; + return "YammerEventDistributionMonitor(delegate=" + delegate + ')'; } } diff --git a/metrics-yammer/src/main/java/io/netty/monitor/yammer/spi/YammerMonitorRegistryFactory.java b/metrics-yammer/src/main/java/io/netty/monitor/yammer/spi/YammerMonitorRegistryFactory.java index 7bdabf091f9..ac3e25b08a2 100644 --- a/metrics-yammer/src/main/java/io/netty/monitor/yammer/spi/YammerMonitorRegistryFactory.java +++ b/metrics-yammer/src/main/java/io/netty/monitor/yammer/spi/YammerMonitorRegistryFactory.java @@ -71,6 +71,6 @@ public MonitorRegistry newMonitorRegistry() { */ @Override public String toString() { - return "YammerMonitorRegistryFactory(metricsRegistry=" + metricsRegistry + ")"; + return "YammerMonitorRegistryFactory(metricsRegistry=" + metricsRegistry + ')'; } } diff --git a/pom.xml b/pom.xml index 1ca69458986..a97a31a0b28 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ io.netty netty-parent pom - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT Netty http://netty.io/ @@ -328,7 +328,7 @@ ${project.groupId} netty-build - 10 + 11 diff --git a/tarball/pom.xml b/tarball/pom.xml index c8be028cf99..b65334a3f03 100644 --- a/tarball/pom.xml +++ b/tarball/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-tarball diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 42cb7e15306..111fa7db198 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-testsuite diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java index 59069ee3ad6..203891e3c2e 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramMulticastTest.java @@ -15,7 +15,6 @@ */ package io.netty.testsuite.transport.socket; -import static org.junit.Assert.*; import io.netty.bootstrap.AbstractBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; @@ -26,14 +25,14 @@ import io.netty.channel.socket.DatagramPacket; import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.util.NetworkConstants; +import org.junit.Ignore; +import org.junit.Test; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; +import static org.junit.Assert.*; public class DatagramMulticastTest extends AbstractDatagramTest { @@ -43,7 +42,7 @@ public void testMulticast() throws Throwable { run(); } - public void testMulticast(AbstractBootstrap sb, AbstractBootstrap cb) throws Throwable { + public void testMulticast(AbstractBootstrap sb, AbstractBootstrap cb) throws Throwable { MulticastTestHandler mhandler = new MulticastTestHandler(); sb.handler(new ChannelInboundMessageHandlerAdapter() { @@ -96,7 +95,7 @@ public void messageReceived( } - private final class MulticastTestHandler extends ChannelInboundMessageHandlerAdapter { + private static final class MulticastTestHandler extends ChannelInboundMessageHandlerAdapter { private final CountDownLatch latch = new CountDownLatch(1); private boolean done; @@ -110,7 +109,7 @@ public void messageReceived( fail = true; } - Assert.assertEquals(1, msg.data().readInt()); + assertEquals(1, msg.data().readInt()); latch.countDown(); // mark the handler as done as we only are supposed to receive one message @@ -121,7 +120,7 @@ public boolean await() throws Exception { boolean success = latch.await(10, TimeUnit.SECONDS); if (fail) { // fail if we receive an message after we are done - Assert.fail(); + fail(); } return success; } diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java index e8603816aaa..8b8cd721ff4 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/DatagramUnicastTest.java @@ -15,19 +15,18 @@ */ package io.netty.testsuite.transport.socket; -import static org.junit.Assert.*; import io.netty.bootstrap.AbstractBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundMessageHandlerAdapter; import io.netty.channel.socket.DatagramPacket; +import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.junit.Assert; -import org.junit.Test; +import static org.junit.Assert.*; public class DatagramUnicastTest extends AbstractDatagramTest { @@ -36,7 +35,7 @@ public void testSimpleSend() throws Throwable { run(); } - public void testSimpleSend(AbstractBootstrap sb, AbstractBootstrap cb) throws Throwable { + public void testSimpleSend(AbstractBootstrap sb, AbstractBootstrap cb) throws Throwable { final CountDownLatch latch = new CountDownLatch(1); sb.handler(new ChannelInboundMessageHandlerAdapter() { @@ -44,7 +43,7 @@ public void testSimpleSend(AbstractBootstrap sb, AbstractBootstrap cb) throws Th public void messageReceived( ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { - Assert.assertEquals(1, msg.data().readInt()); + assertEquals(1, msg.data().readInt()); latch.countDown(); } }); diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java index 74a65b3bbd1..d9524bb5128 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/ServerSocketSuspendTest.java @@ -94,7 +94,7 @@ public void testSuspendAndResumeAccept(ServerBootstrap sb) throws Throwable { } @ChannelHandler.Sharable - private final class AcceptedChannelCounter extends ChannelInboundByteHandlerAdapter { + private static final class AcceptedChannelCounter extends ChannelInboundByteHandlerAdapter { final CountDownLatch latch; diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java index 6cecdb098cc..77f0b568168 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSpdyEchoTest.java @@ -229,7 +229,7 @@ public void initChannel(SocketChannel channel) throws Exception { } } - private class SpdyEchoTestServerHandler extends ChannelInboundMessageHandlerAdapter { + private static class SpdyEchoTestServerHandler extends ChannelInboundMessageHandlerAdapter { final AtomicReference exception = new AtomicReference(); @Override @@ -245,7 +245,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } } - private class SpdyEchoTestClientHandler extends ChannelInboundByteHandlerAdapter { + private static class SpdyEchoTestClientHandler extends ChannelInboundByteHandlerAdapter { final AtomicReference exception = new AtomicReference(); final ByteBuf frames; volatile int counter; diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java index f521916a588..733da9666b2 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketSslEchoTest.java @@ -15,7 +15,6 @@ */ package io.netty.testsuite.transport.socket; -import static org.junit.Assert.*; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; @@ -28,7 +27,15 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.SslHandler; +import org.junit.Test; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactorySpi; +import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -36,21 +43,12 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Security; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.ManagerFactoryParameters; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactorySpi; -import javax.net.ssl.X509TrustManager; - -import org.junit.Test; +import static org.junit.Assert.*; public class SocketSslEchoTest extends AbstractSocketTest { @@ -211,7 +209,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, } } - private static class BogusSslContextFactory { + private static final class BogusSslContextFactory { private static final String PROTOCOL = "TLS"; private static final SSLContext SERVER_CONTEXT; @@ -254,6 +252,9 @@ private static class BogusSslContextFactory { CLIENT_CONTEXT = clientContext; } + @SuppressWarnings("all") + private BogusSslContextFactory() { } + public static SSLContext getServerContext() { return SERVER_CONTEXT; } @@ -276,14 +277,12 @@ public X509Certificate[] getAcceptedIssuers() { } @Override - public void checkClientTrusted( - X509Certificate[] chain, String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) { // NOOP } @Override - public void checkServerTrusted( - X509Certificate[] chain, String authType) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType) { // NOOP } }; @@ -598,8 +597,7 @@ public static char[] getKeyStorePassword() { return "secret".toCharArray(); } - private BogusKeyStore() { - // Unused - } + @SuppressWarnings("all") + private BogusKeyStore() { } } } diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java index af6cd01976b..4ed151b7860 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java @@ -32,9 +32,7 @@ import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioEventLoopGroup; import io.netty.channel.socket.oio.OioServerSocketChannel; -import io.netty.channel.socket.oio.OioSctpServerChannel; import io.netty.channel.socket.oio.OioSocketChannel; -import io.netty.channel.socket.oio.OioSctpChannel; import io.netty.testsuite.util.TestUtils; import java.util.ArrayList; diff --git a/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java b/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java index 0d3338a95ab..fc40aed8b50 100644 --- a/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java +++ b/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java @@ -23,14 +23,14 @@ import java.nio.channels.Channel; import java.util.*; -public class TestUtils { +public final class TestUtils { private static final int START_PORT = 32768; private static final int END_PORT = 65536; private static final int NUM_CANDIDATES = END_PORT - START_PORT; private static final List PORTS = new ArrayList(); - private static Iterator PORTS_ITERATOR; + private static Iterator portIterator; static { for (int i = START_PORT; i < END_PORT; i ++) { @@ -40,10 +40,10 @@ public class TestUtils { } private static int nextCandidatePort() { - if (PORTS_ITERATOR == null || !PORTS_ITERATOR.hasNext()) { - PORTS_ITERATOR = PORTS.iterator(); + if (portIterator == null || !portIterator.hasNext()) { + portIterator = PORTS.iterator(); } - return PORTS_ITERATOR.next(); + return portIterator.next(); } /** @@ -109,4 +109,4 @@ public static boolean isSctpSupported() { } private TestUtils() { } -} \ No newline at end of file +} diff --git a/transport/pom.xml b/transport/pom.xml index 782bd6248c5..6aa99d2dc18 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ io.netty netty-parent - 4.0.0.Alpha7-SNAPSHOT + 4.0.0.Alpha8-SNAPSHOT netty-transport diff --git a/transport/src/main/java/com/sun/nio/sctp/AbstractNotificationHandler.java b/transport/src/main/java/com/sun/nio/sctp/AbstractNotificationHandler.java index bade95b2646..09d01f254a6 100644 --- a/transport/src/main/java/com/sun/nio/sctp/AbstractNotificationHandler.java +++ b/transport/src/main/java/com/sun/nio/sctp/AbstractNotificationHandler.java @@ -15,6 +15,7 @@ */ package com.sun.nio.sctp; +@SuppressWarnings("all") public class AbstractNotificationHandler implements NotificationHandler { static { UnsupportedOperatingSystemException.raise(); diff --git a/transport/src/main/java/com/sun/nio/sctp/HandlerResult.java b/transport/src/main/java/com/sun/nio/sctp/HandlerResult.java index f75088140b8..5993a898941 100644 --- a/transport/src/main/java/com/sun/nio/sctp/HandlerResult.java +++ b/transport/src/main/java/com/sun/nio/sctp/HandlerResult.java @@ -21,5 +21,5 @@ * @author Trustin Lee */ public enum HandlerResult { - CONTINUE, RETURN; + CONTINUE, RETURN } diff --git a/transport/src/main/java/com/sun/nio/sctp/SctpChannel.java b/transport/src/main/java/com/sun/nio/sctp/SctpChannel.java index 4d0c906d0ec..8545ecd7ef6 100644 --- a/transport/src/main/java/com/sun/nio/sctp/SctpChannel.java +++ b/transport/src/main/java/com/sun/nio/sctp/SctpChannel.java @@ -23,6 +23,7 @@ import java.nio.channels.spi.SelectorProvider; import java.util.Set; +@SuppressWarnings("all") public abstract class SctpChannel extends AbstractSelectableChannel { static { UnsupportedOperatingSystemException.raise(); diff --git a/transport/src/main/java/com/sun/nio/sctp/SctpServerChannel.java b/transport/src/main/java/com/sun/nio/sctp/SctpServerChannel.java index 2b47d1daf30..4c2da8ee9ec 100644 --- a/transport/src/main/java/com/sun/nio/sctp/SctpServerChannel.java +++ b/transport/src/main/java/com/sun/nio/sctp/SctpServerChannel.java @@ -22,6 +22,7 @@ import java.nio.channels.spi.SelectorProvider; import java.util.Set; +@SuppressWarnings("all") public abstract class SctpServerChannel extends AbstractSelectableChannel { static { UnsupportedOperatingSystemException.raise(); diff --git a/transport/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java b/transport/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java index 0cbca3061f5..234eb9f58eb 100644 --- a/transport/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java +++ b/transport/src/main/java/com/sun/nio/sctp/SctpStandardSocketOptions.java @@ -17,6 +17,7 @@ import java.net.SocketAddress; +@SuppressWarnings("all") public class SctpStandardSocketOptions { static { UnsupportedOperatingSystemException.raise(); diff --git a/transport/src/main/java/com/sun/nio/sctp/UnsupportedOperatingSystemException.java b/transport/src/main/java/com/sun/nio/sctp/UnsupportedOperatingSystemException.java index 8cd99f1783a..16fb81295f6 100644 --- a/transport/src/main/java/com/sun/nio/sctp/UnsupportedOperatingSystemException.java +++ b/transport/src/main/java/com/sun/nio/sctp/UnsupportedOperatingSystemException.java @@ -24,7 +24,6 @@ public static void raise() { } public UnsupportedOperatingSystemException() { - super(); } public UnsupportedOperatingSystemException(String message) { diff --git a/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java index 2fbf0fb8c1f..2102f0c64ea 100644 --- a/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/AbstractBootstrap.java @@ -247,7 +247,7 @@ protected final Map, Object> attrs() { return attrs; } - private final class BootstrapChannelFactory implements ChannelFactory { + private static final class BootstrapChannelFactory implements ChannelFactory { private final Class clazz; BootstrapChannelFactory(Class clazz) { diff --git a/transport/src/main/java/io/netty/bootstrap/Bootstrap.java b/transport/src/main/java/io/netty/bootstrap/Bootstrap.java index c4ccae4657f..fe1a0b52b60 100644 --- a/transport/src/main/java/io/netty/bootstrap/Bootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/Bootstrap.java @@ -20,6 +20,8 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.aio.AioEventLoopGroup; +import io.netty.channel.socket.aio.AioSocketChannel; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import io.netty.util.AttributeKey; @@ -177,4 +179,22 @@ public Bootstrap duplicate() { b.attrs().putAll(attrs()); return b; } + + @Override + public Bootstrap channel(Class channelClass) { + if (channelClass == null) { + throw new NullPointerException("channelClass"); + } + if (channelClass == AioSocketChannel.class) { + return channelFactory(new AioSocketChannelFactory()); + } + return super.channel(channelClass); + } + + private final class AioSocketChannelFactory implements ChannelFactory { + @Override + public Channel newChannel() { + return new AioSocketChannel((AioEventLoopGroup) group()); + } + } } diff --git a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java index d06d25c56ee..8299f4e4bef 100644 --- a/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java +++ b/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java @@ -30,6 +30,8 @@ import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.aio.AioEventLoopGroup; +import io.netty.channel.socket.aio.AioServerSocketChannel; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; import io.netty.util.AttributeKey; @@ -98,6 +100,9 @@ public ServerBootstrap channel(Class channelClass) { if (!ServerChannel.class.isAssignableFrom(channelClass)) { throw new IllegalArgumentException(); } + if (channelClass == AioServerSocketChannel.class) { + channelFactory(new AioServerSocketChannelFactory()); + } return super.channel(channelClass); } @@ -258,4 +263,12 @@ public void inboundBufferUpdated(ChannelHandlerContext ctx) { } } } + + private final class AioServerSocketChannelFactory implements ChannelFactory { + @Override + public Channel newChannel() { + return new AioServerSocketChannel((AioEventLoopGroup) group(), (AioEventLoopGroup) childGroup); + } + } } + diff --git a/transport/src/main/java/io/netty/channel/AbstractChannel.java b/transport/src/main/java/io/netty/channel/AbstractChannel.java index 49d854f82ce..39c4521eea4 100644 --- a/transport/src/main/java/io/netty/channel/AbstractChannel.java +++ b/transport/src/main/java/io/netty/channel/AbstractChannel.java @@ -399,7 +399,7 @@ public final SocketAddress remoteAddress() { } @Override - public final void register(EventLoop eventLoop, ChannelFuture future) { + public final void register(EventLoop eventLoop, final ChannelFuture future) { if (eventLoop == null) { throw new NullPointerException("eventLoop"); } @@ -418,6 +418,25 @@ public final void register(EventLoop eventLoop, ChannelFuture future) { return; } + // check if the eventLoop which was given is currently in the eventloop. + // if that is the case we are safe to call register, if not we need to + // schedule the execution as otherwise we may say some race-conditions. + // + // See https://github.com/netty/netty/issues/654 + if (eventLoop.inEventLoop()) { + register0(future); + } else { + eventLoop.execute(new Runnable() { + @Override + public void run() { + register0(future); + } + }); + } + + } + + private void register0(ChannelFuture future) { try { Runnable postRegisterTask = doRegister(); registered = true; diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java b/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java index b8e8d388b3f..ea6369ca0df 100644 --- a/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelFuture.java @@ -15,7 +15,6 @@ */ package io.netty.channel; -import static java.util.concurrent.TimeUnit.*; import io.netty.channel.ChannelFlushFutureNotifier.FlushCheckpoint; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; @@ -28,6 +27,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static java.util.concurrent.TimeUnit.*; + /** * The default {@link ChannelFuture} implementation. It is recommended to * use {@link Channels#future(Channel)} and {@link Channels#future(Channel, boolean)} @@ -492,7 +493,7 @@ private static void notifyListener0(ChannelFuture f, ChannelFutureListener l) { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by " + - ChannelFutureListener.class.getSimpleName() + ".", t); + ChannelFutureListener.class.getSimpleName() + '.', t); } } } @@ -534,7 +535,7 @@ private void notifyProgressListener( if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by " + - ChannelFutureProgressListener.class.getSimpleName() + ".", t); + ChannelFutureProgressListener.class.getSimpleName() + '.', t); } } } diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java index 6231ac77967..8c0fc883f40 100755 --- a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java +++ b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java @@ -15,13 +15,10 @@ */ package io.netty.channel; -import static io.netty.channel.DefaultChannelHandlerContext.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.ChannelBuf; import io.netty.buffer.MessageBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.DefaultChannelHandlerContext.ByteBridge; -import io.netty.channel.DefaultChannelHandlerContext.MessageBridge; import io.netty.logging.InternalLogger; import io.netty.logging.InternalLoggerFactory; @@ -36,6 +33,8 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import static io.netty.channel.DefaultChannelHandlerContext.*; + /** * The default {@link ChannelPipeline} implementation. It is usually created * by a {@link Channel} implementation when the {@link Channel} is created. @@ -922,7 +921,7 @@ public ByteBuf outboundByteBuffer() { return nextOutboundByteBuffer(tail); } - boolean hasNextOutboundByteBuffer(DefaultChannelHandlerContext ctx) { + static boolean hasNextOutboundByteBuffer(DefaultChannelHandlerContext ctx) { for (;;) { if (ctx == null) { return false; @@ -935,7 +934,7 @@ boolean hasNextOutboundByteBuffer(DefaultChannelHandlerContext ctx) { } } - boolean hasNextOutboundMessageBuffer(DefaultChannelHandlerContext ctx) { + static boolean hasNextOutboundMessageBuffer(DefaultChannelHandlerContext ctx) { for (;;) { if (ctx == null) { return false; @@ -948,7 +947,7 @@ boolean hasNextOutboundMessageBuffer(DefaultChannelHandlerContext ctx) { } } - ByteBuf nextOutboundByteBuffer(DefaultChannelHandlerContext ctx) { + static ByteBuf nextOutboundByteBuffer(DefaultChannelHandlerContext ctx) { final DefaultChannelHandlerContext initialCtx = ctx; final Thread currentThread = Thread.currentThread(); for (;;) { @@ -984,7 +983,7 @@ ByteBuf nextOutboundByteBuffer(DefaultChannelHandlerContext ctx) { } } - MessageBuf nextOutboundMessageBuffer(DefaultChannelHandlerContext ctx) { + static MessageBuf nextOutboundMessageBuffer(DefaultChannelHandlerContext ctx) { final DefaultChannelHandlerContext initialCtx = ctx; final Thread currentThread = Thread.currentThread(); for (;;) { diff --git a/transport/src/main/java/io/netty/channel/MultithreadEventExecutorGroup.java b/transport/src/main/java/io/netty/channel/MultithreadEventExecutorGroup.java index d36dbb6a1b6..87ebd3684df 100644 --- a/transport/src/main/java/io/netty/channel/MultithreadEventExecutorGroup.java +++ b/transport/src/main/java/io/netty/channel/MultithreadEventExecutorGroup.java @@ -136,7 +136,7 @@ private final class DefaultThreadFactory implements ThreadFactory { DefaultThreadFactory() { String typeName = MultithreadEventExecutorGroup.this.getClass().getSimpleName(); - typeName = "" + Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1); + typeName = Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1); prefix = typeName + '-' + poolId.incrementAndGet() + '-'; } diff --git a/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java b/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java index 8f0b1ab409d..149e766868d 100644 --- a/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java +++ b/transport/src/main/java/io/netty/channel/group/DefaultChannelGroupFuture.java @@ -370,7 +370,7 @@ private void notifyListener(ChannelGroupFutureListener l) { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by " + - ChannelFutureListener.class.getSimpleName() + ".", t); + ChannelFutureListener.class.getSimpleName() + '.', t); } } } diff --git a/transport/src/main/java/io/netty/channel/local/LocalAddress.java b/transport/src/main/java/io/netty/channel/local/LocalAddress.java index c299adb68c8..d523d6462be 100644 --- a/transport/src/main/java/io/netty/channel/local/LocalAddress.java +++ b/transport/src/main/java/io/netty/channel/local/LocalAddress.java @@ -56,7 +56,7 @@ public LocalAddress(String id) { throw new NullPointerException("id"); } id = id.trim().toLowerCase(); - if (id.length() == 0) { + if (id.isEmpty()) { throw new IllegalArgumentException("empty id"); } this.id = id; diff --git a/transport/src/main/java/io/netty/channel/socket/DefaultDatagramChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/DefaultDatagramChannelConfig.java index f1f9a1c2b56..73da9e6f26c 100644 --- a/transport/src/main/java/io/netty/channel/socket/DefaultDatagramChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/DefaultDatagramChannelConfig.java @@ -15,7 +15,6 @@ */ package io.netty.channel.socket; -import static io.netty.channel.ChannelOption.*; import io.netty.channel.ChannelException; import io.netty.channel.ChannelOption; import io.netty.channel.DefaultChannelConfig; @@ -31,6 +30,8 @@ import java.net.SocketException; import java.util.Map; +import static io.netty.channel.ChannelOption.*; + /** * The default {@link DatagramChannelConfig} implementation. */ @@ -151,7 +152,7 @@ public void setBroadcast(boolean broadcast) { "A non-root user can't receive a broadcast packet if the socket " + "is not bound to a wildcard address; setting the SO_BROADCAST flag " + "anyway as requested on the socket which is bound to " + - socket.getLocalSocketAddress() + "."); + socket.getLocalSocketAddress() + '.'); } socket.setBroadcast(broadcast); diff --git a/transport/src/main/java/io/netty/channel/socket/DefaultSctpChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/DefaultSctpChannelConfig.java index fa0dbae9877..b374d35b9e3 100644 --- a/transport/src/main/java/io/netty/channel/socket/DefaultSctpChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/DefaultSctpChannelConfig.java @@ -31,7 +31,7 @@ */ public class DefaultSctpChannelConfig extends DefaultChannelConfig implements SctpChannelConfig { - private SctpChannel channel; + private final SctpChannel channel; public DefaultSctpChannelConfig(SctpChannel channel) { if (channel == null) { diff --git a/transport/src/main/java/io/netty/channel/socket/SctpChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/SctpChannelConfig.java index d75670721cd..4201b43230a 100644 --- a/transport/src/main/java/io/netty/channel/socket/SctpChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/SctpChannelConfig.java @@ -38,7 +38,7 @@ * {@code "sendBufferSize"}{@link #setSendBufferSize(int)} * * {@code "sctpInitMaxStreams"} - * {@link #setInitMaxStreams(com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams)} + * {@link #setInitMaxStreams(InitMaxStreams)} * * */ diff --git a/transport/src/main/java/io/netty/channel/socket/SctpMessage.java b/transport/src/main/java/io/netty/channel/socket/SctpMessage.java index 139e34f447e..1a921d2346f 100644 --- a/transport/src/main/java/io/netty/channel/socket/SctpMessage.java +++ b/transport/src/main/java/io/netty/channel/socket/SctpMessage.java @@ -45,8 +45,8 @@ public SctpMessage(int protocolIdentifier, int streamIdentifier, ByteBuf payload public SctpMessage(MessageInfo msgInfo, ByteBuf payloadBuffer) { this.msgInfo = msgInfo; - this.streamIdentifier = msgInfo.streamNumber(); - this.protocolIdentifier = msgInfo.payloadProtocolID(); + streamIdentifier = msgInfo.streamNumber(); + protocolIdentifier = msgInfo.payloadProtocolID(); this.payloadBuffer = payloadBuffer; } diff --git a/transport/src/main/java/io/netty/channel/socket/SctpNotificationEvent.java b/transport/src/main/java/io/netty/channel/socket/SctpNotificationEvent.java index b9a97416f33..1adc069a30e 100644 --- a/transport/src/main/java/io/netty/channel/socket/SctpNotificationEvent.java +++ b/transport/src/main/java/io/netty/channel/socket/SctpNotificationEvent.java @@ -18,8 +18,8 @@ import com.sun.nio.sctp.Notification; public final class SctpNotificationEvent { - private Notification notification; - private Object attachment; + private final Notification notification; + private final Object attachment; public SctpNotificationEvent(Notification notification, Object attachment) { diff --git a/transport/src/main/java/io/netty/channel/socket/SctpServerChannelConfig.java b/transport/src/main/java/io/netty/channel/socket/SctpServerChannelConfig.java index dfef7b7c2c9..95a38a46423 100644 --- a/transport/src/main/java/io/netty/channel/socket/SctpServerChannelConfig.java +++ b/transport/src/main/java/io/netty/channel/socket/SctpServerChannelConfig.java @@ -39,7 +39,7 @@ * {@code "sendBufferSize"}{@link #setSendBufferSize(int)} * * {@code "sctpInitMaxStreams"} - * {@link #setInitMaxStreams(com.sun.nio.sctp.SctpStandardSocketOptions.InitMaxStreams)} (int)}} + * {@link #setInitMaxStreams(InitMaxStreams)} (int)}} * * */ diff --git a/transport/src/main/java/io/netty/channel/socket/aio/AioEventLoopGroup.java b/transport/src/main/java/io/netty/channel/socket/aio/AioEventLoopGroup.java index 847cd7e8a57..6b9f8556240 100644 --- a/transport/src/main/java/io/netty/channel/socket/aio/AioEventLoopGroup.java +++ b/transport/src/main/java/io/netty/channel/socket/aio/AioEventLoopGroup.java @@ -110,28 +110,6 @@ protected EventExecutor newChild( return new AioEventLoop(this, threadFactory, scheduler); } - private void executeAioTask(Runnable command) { - AbstractAioChannel ch = null; - try { - ch = CHANNEL_FINDER.findChannel(command); - } catch (Throwable t) { - // Ignore - } - - EventExecutor l; - if (ch != null) { - l = ch.eventLoop(); - } else { - l = next(); - } - - if (l.isShutdown()) { - command.run(); - } else { - l.execute(command); - } - } - private final class AioExecutorService extends AbstractExecutorService { // It does not shut down the underlying EventExecutor - it merely pretends to be shut down. @@ -173,5 +151,27 @@ public void execute(Runnable command) { next().execute(command); } } + + private void executeAioTask(Runnable command) { + AbstractAioChannel ch = null; + try { + ch = CHANNEL_FINDER.findChannel(command); + } catch (Throwable t) { + // Ignore + } + + EventExecutor l; + if (ch != null) { + l = ch.eventLoop(); + } else { + l = next(); + } + + if (l.isShutdown()) { + command.run(); + } else { + l.execute(command); + } + } } } diff --git a/transport/src/main/java/io/netty/channel/socket/aio/AioSocketChannel.java b/transport/src/main/java/io/netty/channel/socket/aio/AioSocketChannel.java index 5a726cf1cd1..1c216ece62f 100755 --- a/transport/src/main/java/io/netty/channel/socket/aio/AioSocketChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/aio/AioSocketChannel.java @@ -68,7 +68,7 @@ private static AsynchronousSocketChannel newSocket(AsynchronousChannelGroup grou private final Runnable readTask = new Runnable() { @Override public void run() { - AioSocketChannel.this.beginRead(); + beginRead(); } }; @@ -245,7 +245,7 @@ protected void doFlushByteBuffer(ByteBuf buf) throws Exception { if (buf.hasNioBuffers()) { ByteBuffer[] buffers = buf.nioBuffers(buf.readerIndex(), buf.readableBytes()); javaChannel().write(buffers, 0, buffers.length, config.getReadTimeout(), - TimeUnit.MILLISECONDS, AioSocketChannel.this, GATHERING_WRITE_HANDLER); + TimeUnit.MILLISECONDS, this, GATHERING_WRITE_HANDLER); } else { javaChannel().write(buf.nioBuffer(), config.getReadTimeout(), TimeUnit.MILLISECONDS, this, WRITE_HANDLER); @@ -276,12 +276,12 @@ private void beginRead() { if (byteBuf.hasNioBuffers()) { ByteBuffer[] buffers = byteBuf.nioBuffers(byteBuf.writerIndex(), byteBuf.writableBytes()); javaChannel().read(buffers, 0, buffers.length, config.getWriteTimeout(), - TimeUnit.MILLISECONDS, AioSocketChannel.this, SCATTERING_READ_HANDLER); + TimeUnit.MILLISECONDS, this, SCATTERING_READ_HANDLER); } else { // Get a ByteBuffer view on the ByteBuf ByteBuffer buffer = byteBuf.nioBuffer(byteBuf.writerIndex(), byteBuf.writableBytes()); javaChannel().read(buffer, config.getWriteTimeout(), TimeUnit.MILLISECONDS, - AioSocketChannel.this, READ_HANDLER); + this, READ_HANDLER); } } diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java b/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java index 43b88085fc2..83dadcbdebe 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioEventLoop.java @@ -111,7 +111,7 @@ public void register(final SelectableChannel ch, final int interestOps, final Ni } if ((interestOps & ~ch.validOps()) != 0) { throw new IllegalArgumentException( - "invalid interestOps: " + interestOps + "(validOps: " + ch.validOps() + ")"); + "invalid interestOps: " + interestOps + "(validOps: " + ch.validOps() + ')'); } if (task == null) { throw new NullPointerException("task"); @@ -132,7 +132,7 @@ public void register(final SelectableChannel ch, final int interestOps, final Ni // selector to the new one private Selector recreateSelector() { final Selector newSelector = openSelector(); - final Selector oldSelector = this.selector; + final Selector oldSelector = selector; // Register all channels to the new Selector. boolean success = false; @@ -166,7 +166,7 @@ private Selector recreateSelector() { } logger.info("Selector migration complete."); - return this.selector = newSelector; + return selector = newSelector; } @Override diff --git a/transport/src/main/java/io/netty/channel/socket/nio/NioSctpServerChannel.java b/transport/src/main/java/io/netty/channel/socket/nio/NioSctpServerChannel.java index aaa87e670da..05c62f4cbb8 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/NioSctpServerChannel.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/NioSctpServerChannel.java @@ -49,7 +49,7 @@ private static SctpServerChannel newSocket() { public NioSctpServerChannel() { super(null, null, newSocket(), SelectionKey.OP_ACCEPT); - config = new DefaultSctpServerChannelConfig(this.javaChannel()); + config = new DefaultSctpServerChannelConfig(javaChannel()); } @Override diff --git a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java index e47802ac62a..f9320b64c9b 100644 --- a/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java +++ b/transport/src/main/java/io/netty/channel/socket/nio/SelectorUtil.java @@ -48,7 +48,7 @@ final class SelectorUtil { } } catch (SecurityException e) { if (logger.isDebugEnabled()) { - logger.debug("Unable to get/set System Property '" + key + "'", e); + logger.debug("Unable to get/set System Property '" + key + '\'', e); } } if (logger.isDebugEnabled()) { diff --git a/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java b/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java index 84ebb499c93..49ff2b87a58 100644 --- a/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java +++ b/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest.java @@ -32,6 +32,10 @@ import io.netty.channel.DefaultEventExecutorGroup; import io.netty.channel.EventExecutorGroup; import io.netty.channel.EventLoopGroup; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import java.util.HashSet; import java.util.Queue; @@ -43,11 +47,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - public class LocalTransportThreadModelTest { private static ServerBootstrap sb; @@ -369,7 +368,7 @@ public void flush(ChannelHandlerContext ctx, @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { exception.compareAndSet(null, cause); - System.err.print("[" + Thread.currentThread().getName() + "] "); + System.err.print('[' + Thread.currentThread().getName() + "] "); cause.printStackTrace(); super.exceptionCaught(ctx, cause); } @@ -604,7 +603,7 @@ public void flush(ChannelHandlerContext ctx, @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { exception.compareAndSet(null, cause); - System.err.print("[" + Thread.currentThread().getName() + "] "); + System.err.print('[' + Thread.currentThread().getName() + "] "); cause.printStackTrace(); super.exceptionCaught(ctx, cause); } diff --git a/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest2.java b/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest2.java index 87fdfe41acb..3e296ae5936 100644 --- a/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest2.java +++ b/transport/src/test/java/io/netty/channel/local/LocalTransportThreadModelTest2.java @@ -126,10 +126,10 @@ public void run() { } @Sharable - class LocalHander extends ChannelInboundMessageHandlerAdapter { + static class LocalHander extends ChannelInboundMessageHandlerAdapter { private final String name; - public volatile ChannelFuture lastWriteFuture = null; + public volatile ChannelFuture lastWriteFuture; public AtomicInteger count = new AtomicInteger(0); @@ -141,7 +141,7 @@ public LocalHander(String name) { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < messageCountPerRun; i ++) { - lastWriteFuture = ctx.channel().write(name + " " + i); + lastWriteFuture = ctx.channel().write(name + ' ' + i); } } @@ -150,4 +150,4 @@ public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Except count.incrementAndGet(); } } -} \ No newline at end of file +}