diff --git a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/filter/TokenisingServletFilter.java b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/filter/TokenisingServletFilter.java index 8cb65493d..2b5fbebb8 100644 --- a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/filter/TokenisingServletFilter.java +++ b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/filter/TokenisingServletFilter.java @@ -59,8 +59,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha try { if (!response.isCommitted()) { // only write the content if the headers havent been commited (an error code hasnt been sent) - String filteredResponse = IOUtils.toString( getStreamTokeniser(responseWrapper.getReader()) ); - byte[] filteredData = filteredResponse.toString().getBytes(response.getCharacterEncoding()); + byte[] filteredData = IOUtils.toByteArray( getStreamTokeniser(responseWrapper.getReader()) ); response.setContentLength(filteredData.length); out.write(filteredData); response.flushBuffer(); diff --git a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/JndiTokenFinder.java b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/JndiTokenFinder.java index cc4ee353e..8d3769a7f 100644 --- a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/JndiTokenFinder.java +++ b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/JndiTokenFinder.java @@ -4,7 +4,7 @@ import javax.naming.InitialContext; import javax.naming.NamingException; -public class JndiTokenFinder +public class JndiTokenFinder implements TokenFinder { private final Context appServerContext; diff --git a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenFinder.java b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenFinder.java new file mode 100644 index 000000000..f32237b73 --- /dev/null +++ b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenFinder.java @@ -0,0 +1,7 @@ +package org.bladerunnerjs.appserver.util; + + +public interface TokenFinder +{ + public String findTokenValue(String tokenName); +} diff --git a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenReplacingReader.java b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenReplacingReader.java index 0d5927c39..ab1eeaa1c 100644 --- a/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenReplacingReader.java +++ b/brjs-servlet/src/main/java/org/bladerunnerjs/appserver/util/TokenReplacingReader.java @@ -4,20 +4,26 @@ import java.io.Reader; +// this class only reads a single char at a time from the SourceReader as it massively simplifies the code and we're not reading from large files as we might with the JS 'stripping' readers public class TokenReplacingReader extends Reader { - private static final int MAX_SINGLE_WRITE = 3; + public static final char tokenStart = '@'; public static final char tokenEnd = '@'; - private JndiTokenFinder tokenFinder; + + private TokenFinder tokenFinder; private Reader sourceReader; - private int nextCharPos = 0; - private int lastCharPos = 0; private boolean withinToken = false; - private StringBuffer tokenString; + private StringBuffer currentTokenString; + + private StringBuffer tokenReplacementBuffer = new StringBuffer(); - public TokenReplacingReader(JndiTokenFinder tokenFinder, Reader sourceReader) + private int currentOffset; + private int maxCharactersToWrite; + private int totalNumberOfCharsWritten; + + public TokenReplacingReader(TokenFinder tokenFinder, Reader sourceReader) { this.tokenFinder = tokenFinder; this.sourceReader = sourceReader; @@ -26,64 +32,51 @@ public TokenReplacingReader(JndiTokenFinder tokenFinder, Reader sourceReader) @Override public int read(char[] destBuffer, int offset, int maxCharacters) throws IOException { - if(lastCharPos == -1) { - return -1; - } + currentOffset = offset; + totalNumberOfCharsWritten = 0; + maxCharactersToWrite = maxCharacters; - int currentOffset = offset; - int maxOffset = offset + maxCharacters - (MAX_SINGLE_WRITE - 1); + int nextCharVal = -1; char nextChar = '\0'; - char[] sourceBuffer = new char[4096]; - while(currentOffset < maxOffset) { - if(nextCharPos == lastCharPos) { - nextCharPos = 0; - lastCharPos = sourceReader.read(sourceBuffer, 0, sourceBuffer.length - 1); - - if(lastCharPos == -1) { - break; - } - } - - nextChar = sourceBuffer[nextCharPos++]; + if (tokenReplacementBuffer.length() > 0) { + appendTokenReplacement(destBuffer); + } + + while (canWriteMoreChars()) { + nextCharVal = sourceReader.read(); - if (nextChar == '\r' && nextChar != '\n') { - throw new IOException("Mac line endings detected. This type of line ending is not supported."); + if (nextCharVal == -1) { + break; } - + nextChar = (char) nextCharVal; if (withinToken) { if (isValidTokenChar(nextChar)) { - tokenString.append(nextChar); + currentTokenString.append(nextChar); } else if (nextChar == tokenEnd) { withinToken = false; - tokenString.append(nextChar); - if (tokenString.length() <= 2) + currentTokenString.append(nextChar); + if (currentTokenString.length() <= 2) { - currentOffset = addToCharBuffer(destBuffer, currentOffset, tokenString.toString().toCharArray()); + addToCharBuffer(destBuffer, currentTokenString.toString().toCharArray()); } else { - String tokenReplacement = findTokenReplacement(tokenString.toString(), tokenFinder); - if (tokenReplacement != null) - { - currentOffset = addToCharBuffer(destBuffer, currentOffset, tokenReplacement.toString().toCharArray()); - } - else - { - throw new IllegalArgumentException("No replacement found for token " + tokenString); - } + tokenReplacementBuffer.ensureCapacity(0); + tokenReplacementBuffer.append( findTokenReplacement(currentTokenString.toString()) ); + appendTokenReplacement(destBuffer); } } else { withinToken = false; - currentOffset = addToCharBuffer(destBuffer, currentOffset, tokenString.toString().toCharArray()); - currentOffset = addToCharBuffer(destBuffer, currentOffset, nextChar); + addToCharBuffer(destBuffer, currentTokenString.toString().toCharArray()); + addToCharBuffer(destBuffer, nextChar); } } else @@ -91,12 +84,12 @@ else if (nextChar == tokenEnd) if (nextChar == tokenStart) { withinToken = true; - tokenString = new StringBuffer(); - tokenString.append(nextChar); + currentTokenString = new StringBuffer(); + currentTokenString.append(nextChar); } else { - currentOffset = addToCharBuffer(destBuffer, currentOffset, nextChar); + addToCharBuffer(destBuffer, nextChar); } } @@ -112,11 +105,22 @@ public void close() throws IOException sourceReader.close(); } - private int addToCharBuffer(char[] destBuffer, int currentOffset, char... chars) { + private boolean canWriteMoreChars() { + return totalNumberOfCharsWritten < maxCharactersToWrite; + } + + private void addToCharBuffer(char[] destBuffer, char... chars) { for (char c : chars) { destBuffer[currentOffset++] = c; + totalNumberOfCharsWritten ++; + } + } + + private void appendTokenReplacement(char[] destBuffer) { + while (tokenReplacementBuffer.length() > 0 && canWriteMoreChars()) { + addToCharBuffer(destBuffer, tokenReplacementBuffer.charAt(0)); + tokenReplacementBuffer.delete(0, 1); } - return currentOffset; } private boolean isValidTokenChar(char c) @@ -124,10 +128,13 @@ private boolean isValidTokenChar(char c) return Character.isUpperCase(c) || c == '.'; } - private String findTokenReplacement(String tokenName, JndiTokenFinder tokenFinder) + private String findTokenReplacement(String tokenName) { tokenName = tokenName.substring(1, tokenName.length() - 1); - String tokenReplacement = tokenFinder.findTokenValue(tokenName); + String tokenReplacement = tokenFinder.findTokenValue(tokenName); + if (tokenReplacement == null) { + throw new IllegalArgumentException("No replacement found for token " + tokenName); + } return tokenReplacement; } diff --git a/brjs-servlet/src/test/java/org/bladerunnerjs/appserver/util/TokenReplacingReaderTest.java b/brjs-servlet/src/test/java/org/bladerunnerjs/appserver/util/TokenReplacingReaderTest.java index 6c925976d..5c50675a9 100644 --- a/brjs-servlet/src/test/java/org/bladerunnerjs/appserver/util/TokenReplacingReaderTest.java +++ b/brjs-servlet/src/test/java/org/bladerunnerjs/appserver/util/TokenReplacingReaderTest.java @@ -5,11 +5,13 @@ import java.io.Reader; import java.io.StringReader; +import java.util.Arrays; import javax.naming.Context; import javax.naming.NamingException; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.bladerunnerjs.appserver.filter.TestContextFactory; import org.junit.After; import org.junit.Before; @@ -34,6 +36,7 @@ public void setup() throws Exception when(mockJndiContext.lookup("java:comp/env/AN.EMPTY.TOKEN")).thenReturn(""); when(mockJndiContext.lookup("java:comp/env/A.NULL.TOKEN")).thenReturn(null); when(mockJndiContext.lookup("java:comp/env/A.NONEXISTANT.TOKEN")).thenThrow(NamingException.class); + when(mockJndiContext.lookup("java:comp/env/LONG.TOKEN.REPLACEMENT")).thenReturn( StringUtils.leftPad("", 5000, "0") ); } @After @@ -66,7 +69,6 @@ public void tokenReplacementWorksForEmptyStringValues() throws Exception Reader tokenisingReader = new TokenReplacingReader( new JndiTokenFinder(mockJndiContext), new StringReader("@AN.EMPTY.TOKEN@") ); String replacedContent = IOUtils.toString( tokenisingReader ); assertEquals("", replacedContent); - verify(mockJndiContext, times(1)).lookup("java:comp/env/AN.EMPTY.TOKEN"); } @Test @@ -75,7 +77,6 @@ public void tokenReplacementWorksForNullStringValues() throws Exception Reader tokenisingReader = new TokenReplacingReader( new JndiTokenFinder(mockJndiContext), new StringReader("@A.NULL.TOKEN@") ); String replacedContent = IOUtils.toString( tokenisingReader ); assertEquals("", replacedContent); - verify(mockJndiContext, times(1)).lookup("java:comp/env/A.NULL.TOKEN"); } @Test @@ -86,6 +87,40 @@ public void testExceptionIsThrownIfTokenCannotBeReplaced() throws Exception IOUtils.readLines( tokenisingReader ); } + @Test + public void longTokenReplacementsCanBeUsed() throws Exception + { + Reader tokenisingReader = new TokenReplacingReader( new JndiTokenFinder(mockJndiContext), new StringReader("@LONG.TOKEN.REPLACEMENT@") ); + String replacedContent = IOUtils.toString( tokenisingReader ); + assertEquals(StringUtils.leftPad("", 5000, "0"), replacedContent); + } + + @Test + public void tokenStringsCanSpanBufferLimits() throws Exception + { + Reader tokenisingReader = new TokenReplacingReader( new JndiTokenFinder(mockJndiContext), new StringReader( + StringUtils.leftPad("", 4094, "0")+" @A.TOKEN@ "+StringUtils.leftPad("", 4094, "0")) + ); + String replacedContent = IOUtils.toString( tokenisingReader ); + assertEquals( + StringUtils.leftPad("", 4094, "0")+" token replacement "+StringUtils.leftPad("", 4094, "0") + , replacedContent); + } + + @Test + public void tokensAreReplacedInsideOfLargeContent() throws Exception + { + for (int padLength : Arrays.asList(4096, 5000, 10000)) { + Reader tokenisingReader = new TokenReplacingReader( new JndiTokenFinder(mockJndiContext), new StringReader( + StringUtils.leftPad("", padLength, "0")+" @A.TOKEN@ "+StringUtils.leftPad("", padLength, "0")) + ); + String replacedContent = IOUtils.toString( tokenisingReader ); + assertEquals( + StringUtils.leftPad("", padLength, "0")+" token replacement "+StringUtils.leftPad("", padLength, "0") + , replacedContent); + } + } + @Test public void closeMethodClosesTheSourceReader() throws Exception {