blob: 087bba0aeeca72cf96fea0188ec26bdebdc634b0 [file] [log] [blame]
/*
* Created on 10-Nov-2005 TODO To change the template for this generated file go
* to Window - Preferences - Java - Code Style - Code Templates
*/
package org.apache.test;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author hawkeye TODO To change the template for this generated type comment
* go to Window - Preferences - Java - Code Style - Code Templates
*/
// We cache the entire output file in an array of char[]
public class Response
{
private static final char CR ='\r';
private static final char LF ='\n';
public static final String CRLF =""+CR+LF;
public static final String DOUBLE_CRLF =CRLF+CRLF;
private static final String CONTENT_LENGTH ="Content-Length:";
private char[] message;
private boolean chunked=false;
// whether the message has a "Connection: Close" http header in it.
private boolean hasCloseConnectionHeader=false;
public boolean hasCloseConnectionHeader( )
{
return hasCloseConnectionHeader;
}
/**
* Because the repositories we use accomodate the dos2unix conversion we
* have a problem. On unix the response files have the crlf converted to
* just cr. this method returns the crlf ! The HTTP header and chunk sizes
* need to be delimited with CR LF's. The 'server response' expected files
* used to mimic the response of a server when performing unit tests also
* need to have the correct delimiting character combinations in the fake
* response messages (generated by the monitor). Problems arise because the
* expected response files have been captured on different operating systems
* (Unix and Windows) and then edited using a variety of editors that
* interpret the CR character differently. Thus the file may be stored with
* or without the CR characters. Also, the souce control application can be
* set to convert a single LF character into a CR LF combination. The
* outcome of all this character manipulation is that LF's may or may not
* have the necessary associated CR for it to be recognised by the HTTP
* transport. It is not just sufficient to look for every LF and to prefix
* it with a CR because the context of that LF must be taken into
* consideration. For example, if a LF is found in the HTTP header then it
* definitely needs a CR prefix. This also applies when the message is
* chunked and the chunk size must also be delimited by the CR LF sequence.
* But, when a LF appears inside a chunk of data it should remain unchanged
* unless the chunk size is different from the actual data size between
* chunks, in which case the CR has probably been stripped out and needs to
* be replaced. Below is a low level design of what should work in all
* instances... Is the message chunked? NO: Convert all single LF sequences
* to CR+LF. NB: Because the value associated with the 'Content-Length'
* label is not used correctly, if the size of the file grows larger than
* the Content-Length value, no action is taken. Exit. YES: Convert all
* single LF sequences to CR+LF in the HTTP header. Set Index = location of
* the first 'chunk' value. Read the chunk size. While the chunk size > 0
* Convert any single LF sequences to CR+LF that surround the chunk value.
* Is the character at offset Index + chunk size equal to CR or LF? NO:
* Count up the number of unaccompanied LF's in the chunk. Is the difference
* between the expected and actual chunk data length double the number of
* unaccompanied LF's? NO: Error - Irreconcilable differences between
* expected and actual chunk data sizes. Exit. YES: Convert all single LF
* sequences to CR+LF in the chunk data. Continue. Index = Index + chunk
* size. Read the new chunk size. End of while. Exit.
*/
public Response(String message)
{
this.message=message.toCharArray( );
// parse the message to see if it has a Connection: Close in it
if (message.toLowerCase( ).indexOf("connection: close")>-1)
hasCloseConnectionHeader=true;
// Find out if the message is chunked
// simply look for the content length string
chunked= message.indexOf(CONTENT_LENGTH)==-1;
// emulate None OS machines when you want to test out the CRLF method
//emulateNoneWinmachines();
// check and correct CRLF's
CheckAndCorrectCRLF( );
}
/**
* This method is used when we are testing out this class.
* It converts ALL CRLF's into LF (as it might be on a none win machine)
*/
private void emulateNoneWinmachines( )
{
String msgString = new String(getMessage());
Pattern pattern=Pattern.compile(CRLF);
Matcher matcher=pattern.matcher(msgString);
if(matcher.find())
msgString = matcher.replaceAll(""+LF);
message = msgString.toCharArray();
}
public char[] getMessage( )
{
return message;
}
public String toString( )
{
return new String(message);
}
/**
* On platforms other than windows the CRLF's can get "helpfully" converted
* So, check the last two digits for CRLF - if they are LFLF then this is
* wrong and we will convert it to be correct. e.g. 0a 0a converts to 0d0a
* 0d0a
*/
private void CheckAndCorrectCRLF( )
{
String modifiedResponse="";
if (chunked)
{
System.out.println("Information - Response is chunked.");
String messageString= new String(getMessage());
modifiedResponse = correctHTTPHeaderSection(messageString);
if (messageString.indexOf("###") != -1)
modifiedResponse +=(correctChunkedSizes(getPostHTTPHeaderData(messageString)));
else
modifiedResponse +=(correctChunkedData(getPostHTTPHeaderData(messageString)));
message = modifiedResponse.toCharArray();
}
else
{
System.out.println("Information - Response is NOT chunked.");
if (System.getProperty("os.name").toLowerCase( ).startsWith("windows"))
System.out.println("Windows operating system - not converting crlf's");
else
{
String request=new String(getMessage( ));
modifiedResponse = correctHTTPHeaderSection(request);
message=modifiedResponse.toCharArray();
}
}
}
/**
* @param message2
* @return
*/
private String getPostHTTPHeaderData(String response)
{
Pattern pattern=Pattern.compile( "(^HTTP.*["+CRLF+"]*)(.*: .*["+CRLF+"]*)*(.*)");
Matcher matcher = pattern.matcher(response);
if(!matcher.find())
System.err.println( "ERROR: Response message does not contain a correctly formatted HTTP header:" +response);
return matcher.replaceAll("$3");
}
private String correctChunkedSizes(String orgResponse)
{
// If called, there must be hash's for chunksizes.
String hash = "###";
String newResponse = "";
// Remove hash line.
int hashPos = orgResponse.indexOf( hash );
int endOfLinePos = orgResponse.indexOf('\n', hashPos);
orgResponse = orgResponse.substring( endOfLinePos + 1 );
String chunk;
while( hashPos != -1 && orgResponse.length() > 0)
{
// Find the next hash in the original response.
hashPos = orgResponse.indexOf( hash);
boolean eom = false;
if( hashPos == -1)
{
hashPos = orgResponse.lastIndexOf( "0");
eom = true;
}
// Ensure chunk ends with CRLF - if it does not,
// remove ending LF and replace with CRLF
chunk = orgResponse.substring( 0, hashPos);
if (!chunk.endsWith("\r\n"))
chunk = chunk.substring(0, chunk.length()-1) + "\r\n";
// Add the next chunk length and data from the original to the new response.
newResponse += Integer.toHexString(chunk.length()-2) + "\r\n" + chunk;
// Remove the old chunk from the original response message.
if( eom)
{
newResponse += "0\r\n\r\n";
orgResponse = "";
}
else
{
endOfLinePos = orgResponse.indexOf('\n', hashPos);
orgResponse = orgResponse.substring( endOfLinePos + 1);
}
}
return newResponse;
}
/**
* @param string the response message - HTTPheaders
* @return correctly formatted (as best we can) http headers.
*/
private String correctChunkedData(String request)
{
// A chunk Is defined by
// <hexnumber><potentially CR><LF><any data><potentially CR><LF><hexnumber>
StringBuffer theCorrectedResponse = new StringBuffer();
String theChunkStartPatternREGEX = "(\\p{XDigit}+)("+CR+"?"+LF+")";
String theNextChunkSizeREGEX ="("+CR+"?"+LF+")"+theChunkStartPatternREGEX;
Pattern chunkStartPattern=Pattern.compile(theChunkStartPatternREGEX);
Pattern theNextChunkSizePattern = Pattern.compile(theNextChunkSizeREGEX);
Matcher chunkStartMatcher = chunkStartPattern.matcher(request);
Matcher theNextChunkSizeMatcher = theNextChunkSizePattern.matcher(request);
int chunkStart = 0;
int chunkEnd=0;
int chunkSize = 0;
int nextChunkSize = -1;
int nextChunkStart= -1;
int nextChunkEnd= -1;
String nextChunkSizeString="";
while(chunkStartMatcher.find() && nextChunkSize!=0)
{
String chunkSizeString="";
if(nextChunkSize==-1)
{
// means this is the first time round the loop so work it out
chunkSizeString = request.substring(chunkStartMatcher.start(), chunkStartMatcher.end());
chunkSize = Integer.parseInt(chunkSizeString.trim(), 16);
chunkStart = chunkStartMatcher.end();
// ensure the string has the correct crlf on it
chunkSizeString = chunkSizeString.trim()+CRLF;
}
else
{
// we've already been round the loop and got the chunkSize
chunkSizeString = nextChunkSizeString;
chunkSize = nextChunkSize;
chunkStart = nextChunkStart;
// ensure the string has the correct crlf on it
// this one came with a different parsing algorithm so add on crlf to *both* ends !
chunkSizeString = CRLF+chunkSizeString.trim()+CRLF;
}
// System.out.println( "chunkString = "+chunkSizeString.trim());
// System.out.println( "Chunk size = "+chunkSize);
// System.out.println( "Chunked Start="+chunkStart);
// System.out.println( "chunkend = "+chunkEnd);
String chunkFromSize=null;
if(chunkStart+chunkSize>request.length())
{
System.err.println( "ChunksSize String is wrong");
System.err.println( "chunksStart = "+chunkStart);
System.err.println( "ChunkSize = "+chunkSize);
System.err.println( "request.length = "+request.length());
// We can work this out though it's probably the last or only chunk so just guess !
// do this later on so we don't duplicate work>
// chunkFromSize is null so we can see later on that we need to guess
}
else
chunkFromSize = request.substring(chunkStart, chunkStart+chunkSize);
String chunk="";
int endOfThisChunk;
// Now find the end of the chunk (and while we do that we actually find the next chunk size too !
if(!theNextChunkSizeMatcher.find())
endOfThisChunk = request.length()-1;
else
{
chunkEnd = theNextChunkSizeMatcher.start();
// get the nextchunkSize while we're here!
nextChunkSizeString = request.substring(theNextChunkSizeMatcher.start(), theNextChunkSizeMatcher.end());
nextChunkStart = theNextChunkSizeMatcher.end();
nextChunkSize = Integer.parseInt(nextChunkSizeString.trim(), 16);
endOfThisChunk = theNextChunkSizeMatcher.start();
}
chunk = request.substring(chunkStart, endOfThisChunk);
if(chunkFromSize==null)
{
// we need to set this value now we have probably worked out what it is
// this stops us from stopping but hopefully we won't fail too !
chunkFromSize = chunk;
chunkSize=chunk.length();
System.err.println( "Guessing the size is "+chunkFromSize.length());
// we now also have to take into account the chunkSize and it's \r\n
// test it again to see whether we made it right or not
// make sure we set the chunksize string
if(!chunkSizeString.startsWith(""+CR))
{
// Then it's the beginning of the message
chunkSizeString = ""+Integer.toHexString(chunk.length())+CRLF;
}
else
chunkSizeString = CRLF+Integer.toHexString(chunk.length())+CRLF;
}
// Check that the chunk size really is the same size as it should be.
if(chunk.length() !=chunkSize)
{
System.out.println( "Warning: Chunksize is not correct");
System.out.println( "chunk.length = "+chunk.length());
System.out.println( "ChunkSize = "+chunkSize);
System.out.println( "chunkString "+chunkSizeString);
// then we might be on a unix system and lost the CR of a CRLF combo
if (System.getProperty("os.name").toLowerCase( ).startsWith(
"windows"))
{
System.out.println( "Warning: Chunksize is not correct. On windows so not attempting to fix");
}
else
{
System.out.println( "Warning: Chunksize is not correct. Attempting to correct with additional \\r\\n");
String newChunk = chunk.replaceAll(""+LF, CRLF);
if(newChunk.length()+chunkSizeString.trim().length()+2 !=chunkSize)
{
System.err.println( "Attempt to correct the chunksize failed");
}
}
}
theCorrectedResponse.append(chunkSizeString);
// append the chunk to the rest of the correct messages
theCorrectedResponse.append(chunk);
}
// append the last chunk on
// sometimes the chunk already has a \n on it so we need to remove it
if(theCorrectedResponse.charAt(theCorrectedResponse.length()-1) == LF)
{
theCorrectedResponse.deleteCharAt(theCorrectedResponse.length()-1);
}
theCorrectedResponse.append(CRLF+"0"+DOUBLE_CRLF);
return theCorrectedResponse.toString();
}
/**
* @param sResponse
* @param iIndex
* @param iEOL
*/
private static String getResponseLine(String sResponse, int iIndex, int iEOL)
{
String sLine=sResponse.substring(iIndex, iEOL);
if ((!sLine.endsWith(CRLF))&&(iEOL>iIndex))
{
sLine=sResponse.substring(iIndex, iEOL-1)+CRLF;
}
return sLine;
}
private String correctHTTPHeaderSection(String response)
{
String returnedResponse ="";
// Process HTTP header block. At the end of each line in
// the header block, if there is only a LF character,
// prefix it with a CR. The end of the HTTP header block
// is denoted by an empty line (i.e. CR LF).
String sLine;
int iIndex=0;
int iEoL=response.indexOf(LF, iIndex)+1;
do
{
sLine=getResponseLine(response, iIndex, iEoL);
returnedResponse+=sLine;
// Get next line.
iIndex=iEoL;
iEoL=response.indexOf(LF, iIndex)+1;
}
while (iEoL>0&&!sLine.equals(CRLF));
if (chunked)
return returnedResponse;
else
{
// Just in case HTTP header has ###, indication that
// the Content-Length value needs to be set to the size
// of the body of the http response
String returnedHttpBody = response.substring(iIndex);
returnedResponse = returnedResponse.replaceFirst("###", Integer.toString(returnedHttpBody.length()));
return returnedResponse + returnedHttpBody;
}
}
}