Skip to content

Commit

Permalink
some cleanup of TokenReplacingReader and adding a TokenFinder interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Berry committed Jul 10, 2015
1 parent 8b20112 commit c7f64a1
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JndiTokenFinder
public class JndiTokenFinder implements TokenFinder
{
private final Context appServerContext;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.bladerunnerjs.appserver.util;


public interface TokenFinder
{
public String findTokenValue(String tokenName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,77 +32,64 @@ public TokenReplacingReader(JndiTokenFinder tokenFinder, Reader sourceReader)
@Override
public int read(char[] destBuffer, int offset, int maxCharacters) throws IOException

This comment has been minimized.

Copy link
@dchambers

dchambers Aug 3, 2015

Contributor

@thecapdan, the changes to this class all seem very reasonable, but we need to be careful when testing this pull-request since changes to the read() methods of the filtering code can easily lead to performance degradation.

{
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
{
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);
}
}

Expand All @@ -112,22 +105,36 @@ 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)
{
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
{
Expand Down

0 comments on commit c7f64a1

Please sign in to comment.