From a2727d84d9c8467e04cf6e73029b715673a30088 Mon Sep 17 00:00:00 2001 From: Holger Wanke Date: Mon, 6 Mar 2017 20:42:14 +0100 Subject: [PATCH 1/2] STS-912 bugfix + test --- .../stripes/tag/layout/LayoutContext.java | 115 ------------- .../tag/layout/MyServletOutputStream.java | 124 ++++++++++++++ .../stripes/tag/layout/DummyJspWriter.java | 157 ++++++++++++++++++ .../tag/layout/MyServletOutputStreamTest.java | 108 ++++++++++++ 4 files changed, 389 insertions(+), 115 deletions(-) create mode 100644 stripes/src/main/java/net/sourceforge/stripes/tag/layout/MyServletOutputStream.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/tag/layout/DummyJspWriter.java create mode 100644 stripes/src/test/java/net/sourceforge/stripes/tag/layout/MyServletOutputStreamTest.java diff --git a/stripes/src/main/java/net/sourceforge/stripes/tag/layout/LayoutContext.java b/stripes/src/main/java/net/sourceforge/stripes/tag/layout/LayoutContext.java index 6790da8dc..9e2f5ca32 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/tag/layout/LayoutContext.java +++ b/stripes/src/main/java/net/sourceforge/stripes/tag/layout/LayoutContext.java @@ -232,121 +232,6 @@ protected boolean isIncludeBroken(PageContext pageContext) { protected void doIncludeHack(PageContext pageContext, String relativeUrlPath) throws ServletException, IOException { - /** - * A servlet output stream implementation that decodes bytes to characters and writes the - * characters to an underlying writer. - */ - class MyServletOutputStream extends ServletOutputStream { - static final String DEFAULT_CHARSET = "UTF-8"; - static final int BUFFER_SIZE = 1024; - - Writer out; - String charset = DEFAULT_CHARSET; - CharsetDecoder decoder; - ByteBuffer bbuf; - CharBuffer cbuf; - - /** Construct a new instance that sends output to the specified writer. */ - MyServletOutputStream(Writer out) { - this.out = out; - } - - /** Get the character set to which bytes will be decoded. */ - String getCharset() { - return charset; - } - - /** Set the character set to which bytes will be decoded. */ - void setCharset(String charset) { - if (charset == null) - charset = DEFAULT_CHARSET; - - // Create a new decoder only if the charset has changed - if (!charset.equals(this.charset)) - decoder = null; - - this.charset = charset; - } - - /** Initialize the character decoder, byte buffer and character buffer. */ - void initDecoder() { - if (decoder == null) { - decoder = Charset.forName(getCharset()).newDecoder(); - - if (bbuf == null) - bbuf = ByteBuffer.allocate(BUFFER_SIZE); - - int size = (int) Math.ceil(BUFFER_SIZE * decoder.maxCharsPerByte()); - if (cbuf == null || cbuf.capacity() != size) - cbuf = CharBuffer.allocate(size); - } - } - - /** - * Clear the byte buffer. If the byte buffer has any data remaining to be read, then - * those bytes are shifted to the front of the buffer and the buffer's position is - * updated accordingly. - */ - void resetBuffer() { - if (bbuf.hasRemaining()) { - ByteBuffer slice = bbuf.slice(); - bbuf.clear(); - bbuf.put(slice); - } - else { - bbuf.clear(); - } - } - - /** - * Decode the contents of the byte buffer to the character buffer and then write the - * contents of the character buffer to the underlying writer. - */ - void decodeBuffer() throws IOException { - bbuf.flip(); - cbuf.clear(); - decoder.decode(bbuf, cbuf, false); - cbuf.flip(); - out.write(cbuf.array(), cbuf.position(), cbuf.remaining()); - resetBuffer(); - } - - @Override - public void print(char c) throws IOException { - out.write(c); - } - - @Override - public void print(String s) throws IOException { - out.write(s); - } - - @Override - public void write(int b) throws IOException { - initDecoder(); - bbuf.put((byte) b); - decodeBuffer(); - } - - @Override - public void write(byte[] buf, int off, int len) throws IOException { - initDecoder(); - - for (int i = 0; i < len; i += bbuf.remaining()) { - int n = len - i; - if (n > bbuf.remaining()) - n = bbuf.remaining(); - - bbuf.put(buf, i, n); - decodeBuffer(); - } - } - - @Override - public void write(byte[] buf) throws IOException { - write(buf, 0, buf.length); - } - } final MyServletOutputStream os = new MyServletOutputStream(pageContext.getOut()); final PrintWriter writer = new PrintWriter(pageContext.getOut()); diff --git a/stripes/src/main/java/net/sourceforge/stripes/tag/layout/MyServletOutputStream.java b/stripes/src/main/java/net/sourceforge/stripes/tag/layout/MyServletOutputStream.java new file mode 100644 index 000000000..f9c95c348 --- /dev/null +++ b/stripes/src/main/java/net/sourceforge/stripes/tag/layout/MyServletOutputStream.java @@ -0,0 +1,124 @@ +package net.sourceforge.stripes.tag.layout; + +import javax.servlet.ServletOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +/** + * A servlet output stream implementation that decodes bytes to characters and writes the + * characters to an underlying writer. + */ +class MyServletOutputStream extends ServletOutputStream { + static final String DEFAULT_CHARSET = "UTF-8"; + static final int BUFFER_SIZE = 1024; + + Writer out; + String charset = DEFAULT_CHARSET; + CharsetDecoder decoder; + ByteBuffer bbuf; + CharBuffer cbuf; + + /** Construct a new instance that sends output to the specified writer. */ + MyServletOutputStream(Writer out) { + this.out = out; + } + + /** Get the character set to which bytes will be decoded. */ + String getCharset() { + return charset; + } + + /** Set the character set to which bytes will be decoded. */ + void setCharset(String charset) { + if (charset == null) + charset = DEFAULT_CHARSET; + + // Create a new decoder only if the charset has changed + if (!charset.equals(this.charset)) + decoder = null; + + this.charset = charset; + } + + /** Initialize the character decoder, byte buffer and character buffer. */ + void initDecoder() { + if (decoder == null) { + decoder = Charset.forName(getCharset()).newDecoder(); + + if (bbuf == null) + bbuf = ByteBuffer.allocate(BUFFER_SIZE); + + int size = (int) Math.ceil(BUFFER_SIZE * decoder.maxCharsPerByte()); + if (cbuf == null || cbuf.capacity() != size) + cbuf = CharBuffer.allocate(size); + } + } + + /** + * Clear the byte buffer. If the byte buffer has any data remaining to be read, then + * those bytes are shifted to the front of the buffer and the buffer's position is + * updated accordingly. + */ + void resetBuffer() { + if (bbuf.hasRemaining()) { + ByteBuffer slice = bbuf.slice(); + bbuf.clear(); + bbuf.put(slice); + } + else { + bbuf.clear(); + } + } + + /** + * Decode the contents of the byte buffer to the character buffer and then write the + * contents of the character buffer to the underlying writer. + */ + void decodeBuffer() throws IOException { + bbuf.flip(); + cbuf.clear(); + decoder.decode(bbuf, cbuf, false); + cbuf.flip(); + out.write(cbuf.array(), cbuf.position(), cbuf.remaining()); + resetBuffer(); + } + + @Override + public void print(char c) throws IOException { + out.write(c); + } + + @Override + public void print(String s) throws IOException { + out.write(s); + } + + @Override + public void write(int b) throws IOException { + initDecoder(); + bbuf.put((byte) b); + decodeBuffer(); + } + + @Override + public void write(byte[] buf, int off, int len) throws IOException { + initDecoder(); + + int copied = 0; + while (copied 0) { + int toCopy = Math.min(len - copied, bbuf.remaining()); + bbuf.put(buf, copied, toCopy); + decodeBuffer(); + copied = copied + toCopy; + } + } + + @Override + public void write(byte[] buf) throws IOException { + write(buf, 0, buf.length); + } +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/tag/layout/DummyJspWriter.java b/stripes/src/test/java/net/sourceforge/stripes/tag/layout/DummyJspWriter.java new file mode 100644 index 000000000..637f03cef --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/tag/layout/DummyJspWriter.java @@ -0,0 +1,157 @@ +package net.sourceforge.stripes.tag.layout; + +import javax.servlet.jsp.JspWriter; +import java.io.IOException; + +/** + * Created by hst92 on 24.02.2017. + */ +public class DummyJspWriter extends JspWriter { + + private StringBuffer buffer = new StringBuffer(); + + public DummyJspWriter(final int bufferSize, final boolean autoFlush) { + super(bufferSize, autoFlush); + } + + @Override + public void newLine() throws IOException { + buffer.append("
"); + } + + @Override + public void print(boolean b) throws IOException { + buffer.append(b); + } + + @Override + public void print(char c) throws IOException { + buffer.append(c); + } + + @Override + public void print(int i) throws IOException { + buffer.append(i); + } + + @Override + public void print(long l) throws IOException { + buffer.append(l); + } + + @Override + public void print(float f) throws IOException { + buffer.append(f); + } + + @Override + public void print(double d) throws IOException { + buffer.append(d); + } + + @Override + public void print(char[] s) throws IOException { + buffer.append(s); + } + + @Override + public void print(String s) throws IOException { + buffer.append(s); + } + + @Override + public void print(Object obj) throws IOException { + buffer.append(obj); + } + + @Override + public void println() throws IOException { + this.newLine(); + } + + @Override + public void println(boolean x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(char x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(int x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(long x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(float x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(double x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(char[] x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(String x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void println(Object x) throws IOException { + this.print(x); + this.newLine(); + } + + @Override + public void clear() throws IOException { + this.buffer = new StringBuffer(); + } + + @Override + public void clearBuffer() throws IOException { + buffer.delete(0, buffer.length()); + } + + @Override + public void write(char[] chars, int i, int i1) throws IOException { + buffer.append(chars); + } + + @Override + public void flush() throws IOException { + } + + @Override + public void close() throws IOException { + } + + @Override + public int getRemaining() { + return 0; + } + + public String getOutput() { + return buffer.toString(); + } +} diff --git a/stripes/src/test/java/net/sourceforge/stripes/tag/layout/MyServletOutputStreamTest.java b/stripes/src/test/java/net/sourceforge/stripes/tag/layout/MyServletOutputStreamTest.java new file mode 100644 index 000000000..9e2afd2d5 --- /dev/null +++ b/stripes/src/test/java/net/sourceforge/stripes/tag/layout/MyServletOutputStreamTest.java @@ -0,0 +1,108 @@ +package net.sourceforge.stripes.tag.layout; + +import org.testng.annotations.Test; + +import java.nio.ByteBuffer; + +/** + * Created by hst92 on 27.02.2017. + */ +public class MyServletOutputStreamTest { + + @Test + public void shouldWriteSuccess() throws Exception { + MyServletOutputStream stream = new MyServletOutputStream(new DummyJspWriter(4080, true)); + String text = "This is an example é◌"; + StringBuffer buffer = new StringBuffer(); + for (int i=0;i<4080;i=buffer.length()) { + buffer.append(text); + } + byte[] bytes = buffer.toString().substring(0, 4080).getBytes(); + stream.write(bytes, 0, 4080); + } + + @Test + public void shouldWriteFailArraySize() throws Exception { + MyServletOutputStream stream = new MyServletOutputStream(new DummyJspWriter(4080, true)); + String text = "é◌"; + StringBuffer buffer = new StringBuffer(); + for (int i=0;i<4080;i=buffer.length()) { + buffer.append(text); + } + byte[] bytes = buffer.toString().substring(0, 4080).getBytes(); + stream.write(bytes, 0, 4080); + } + + @Test + public void shouldWriteFailRemainingNull() throws Exception { + MyServletOutputStream stream = new MyServletOutputStream(new DummyJspWriter(4080, true)); + stream.bbuf = ByteBuffer.allocate(0); + String text = "Some Text é◌"; + StringBuffer buffer = new StringBuffer(); + for (int i=0;i<4080;i=buffer.length()) { + buffer.append(text); + } + byte[] bytes = buffer.toString().substring(0, 4080).getBytes(); + stream.write(bytes, 0, 4080); + } + + @Test + public void shouldWriteFailNull2() throws Exception { + MyServletOutputStream stream = new MyServletOutputStream(new DummyJspWriter(4080, true)); + String text = "4164\">21031 Hamburg, Bodestraße 1