| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.spdy.parser; |
| |
| import static org.hamcrest.Matchers.is; |
| import static org.junit.Assert.assertThat; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.zip.ZipException; |
| |
| import org.eclipse.jetty.io.MappedByteBufferPool; |
| import org.eclipse.jetty.spdy.CompressionFactory; |
| import org.eclipse.jetty.spdy.StreamException; |
| import org.eclipse.jetty.spdy.api.SPDY; |
| import org.eclipse.jetty.spdy.api.SynInfo; |
| import org.eclipse.jetty.spdy.frames.ControlFrame; |
| import org.eclipse.jetty.spdy.frames.SynStreamFrame; |
| import org.eclipse.jetty.spdy.generator.Generator; |
| import org.eclipse.jetty.util.BufferUtil; |
| import org.eclipse.jetty.util.Fields; |
| import org.junit.Test; |
| |
| public class BrokenFrameTest |
| { |
| |
| @Test |
| public void testInvalidHeaderNameLength() throws Exception |
| { |
| Fields headers = new Fields(); |
| headers.add("broken", "header"); |
| SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers); |
| Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor()); |
| |
| ByteBuffer bufferWithBrokenHeaderNameLength = generator.control(frame); |
| // Break the header name length to provoke the Parser to throw a StreamException |
| bufferWithBrokenHeaderNameLength.put(21, (byte)0); |
| |
| ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame); |
| |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| outputStream.write(BufferUtil.toArray(bufferWithBrokenHeaderNameLength)); |
| outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame)); |
| |
| byte concatenatedFramesByteArray[] = outputStream.toByteArray(); |
| ByteBuffer concatenatedBuffer = BufferUtil.toBuffer(concatenatedFramesByteArray); |
| |
| final CountDownLatch latch = new CountDownLatch(2); |
| Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor()); |
| parser.addListener(new Parser.Listener.Adapter() |
| { |
| @Override |
| public void onControlFrame(ControlFrame frame) |
| { |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onStreamException(StreamException x) |
| { |
| latch.countDown(); |
| } |
| }); |
| parser.parse(concatenatedBuffer); |
| |
| assertThat(latch.await(5, TimeUnit.SECONDS), is(true)); |
| } |
| |
| @Test |
| public void testInvalidVersion() throws Exception |
| { |
| Fields headers = new Fields(); |
| headers.add("good", "header"); |
| headers.add("another","header"); |
| SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers); |
| Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor()); |
| |
| ByteBuffer bufferWithBrokenVersion = generator.control(frame); |
| // Break the header name length to provoke the Parser to throw a StreamException |
| bufferWithBrokenVersion.put(1, (byte)4); |
| |
| ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame); |
| |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion)); |
| outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame)); |
| |
| byte concatenatedFramesByteArray[] = outputStream.toByteArray(); |
| ByteBuffer concatenatedBuffer = BufferUtil.toBuffer(concatenatedFramesByteArray); |
| |
| final CountDownLatch latch = new CountDownLatch(2); |
| Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor()); |
| parser.addListener(new Parser.Listener.Adapter() |
| { |
| @Override |
| public void onControlFrame(ControlFrame frame) |
| { |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onStreamException(StreamException x) |
| { |
| latch.countDown(); |
| } |
| }); |
| parser.parse(concatenatedBuffer); |
| |
| assertThat(latch.await(5, TimeUnit.SECONDS), is(true)); |
| } |
| |
| @Test |
| public void testInvalidVersionWithSplitBuffer() throws Exception |
| { |
| Fields headers = new Fields(); |
| headers.add("good", "header"); |
| headers.add("another","header"); |
| SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers); |
| Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor()); |
| |
| ByteBuffer bufferWithBrokenVersion = generator.control(frame); |
| // Break the header name length to provoke the Parser to throw a StreamException |
| bufferWithBrokenVersion.put(1, (byte)4); |
| |
| ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame); |
| |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion)); |
| outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame)); |
| |
| byte concatenatedFramesByteArray[] = outputStream.toByteArray(); |
| ByteBuffer concatenatedBuffer1 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,0,20)); |
| ByteBuffer concatenatedBuffer2 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,20, |
| concatenatedFramesByteArray.length)); |
| |
| final CountDownLatch latch = new CountDownLatch(2); |
| Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor()); |
| parser.addListener(new Parser.Listener.Adapter() |
| { |
| @Override |
| public void onControlFrame(ControlFrame frame) |
| { |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onStreamException(StreamException x) |
| { |
| latch.countDown(); |
| } |
| }); |
| parser.parse(concatenatedBuffer1); |
| parser.parse(concatenatedBuffer2); |
| |
| assertThat(latch.await(5, TimeUnit.SECONDS), is(true)); |
| } |
| |
| @Test |
| public void testInvalidVersionAndGoodFrameSplitInThreeBuffers() throws Exception |
| { |
| Fields headers = new Fields(); |
| headers.add("good", "header"); |
| headers.add("another","header"); |
| SynStreamFrame frame = new SynStreamFrame(SPDY.V2, SynInfo.FLAG_CLOSE, 1, 0, (byte)0, (short)0, headers); |
| Generator generator = new Generator(new MappedByteBufferPool(), new NoCompressionCompressionFactory.NoCompressionCompressor()); |
| |
| ByteBuffer bufferWithBrokenVersion = generator.control(frame); |
| // Break the header name length to provoke the Parser to throw a StreamException |
| bufferWithBrokenVersion.put(1, (byte)4); |
| |
| ByteBuffer bufferWithValidSynStreamFrame = generator.control(frame); |
| |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| outputStream.write(BufferUtil.toArray(bufferWithBrokenVersion)); |
| outputStream.write(BufferUtil.toArray(bufferWithValidSynStreamFrame)); |
| |
| byte concatenatedFramesByteArray[] = outputStream.toByteArray(); |
| ByteBuffer concatenatedBuffer1 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,0,20)); |
| ByteBuffer concatenatedBuffer2 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,20, 30)); |
| ByteBuffer concatenatedBuffer3 = BufferUtil.toBuffer(Arrays.copyOfRange(concatenatedFramesByteArray,30, |
| concatenatedFramesByteArray.length)); |
| |
| final CountDownLatch latch = new CountDownLatch(2); |
| Parser parser = new Parser(new NoCompressionCompressionFactory.NoCompressionDecompressor()); |
| parser.addListener(new Parser.Listener.Adapter() |
| { |
| @Override |
| public void onControlFrame(ControlFrame frame) |
| { |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onStreamException(StreamException x) |
| { |
| latch.countDown(); |
| } |
| }); |
| parser.parse(concatenatedBuffer1); |
| parser.parse(concatenatedBuffer2); |
| parser.parse(concatenatedBuffer3); |
| |
| assertThat(latch.await(5, TimeUnit.SECONDS), is(true)); |
| } |
| |
| private static class NoCompressionCompressionFactory implements CompressionFactory |
| { |
| |
| @Override |
| public Compressor newCompressor() |
| { |
| return null; |
| } |
| |
| @Override |
| public Decompressor newDecompressor() |
| { |
| return null; |
| } |
| |
| public static class NoCompressionCompressor implements Compressor |
| { |
| |
| private byte[] input; |
| |
| @Override |
| public void setInput(byte[] input) |
| { |
| this.input = input; |
| } |
| |
| @Override |
| public void setDictionary(byte[] dictionary) |
| { |
| } |
| |
| @Override |
| public int compress(byte[] output) |
| { |
| System.arraycopy(input, 0, output, 0, input.length); |
| return input.length; |
| } |
| } |
| |
| public static class NoCompressionDecompressor implements Decompressor |
| { |
| private byte[] input; |
| |
| @Override |
| public void setDictionary(byte[] dictionary) |
| { |
| } |
| |
| @Override |
| public void setInput(byte[] input) |
| { |
| this.input = input; |
| } |
| |
| @Override |
| public int decompress(byte[] output) throws ZipException |
| { |
| System.arraycopy(input, 0, output, 0, input.length); |
| return input.length; |
| } |
| } |
| } |
| } |