Reworked Server-Status and Response-Header parsing...

This commit is contained in:
rpotts%netscape.com 1999-06-23 07:41:22 +00:00
parent fc69b0751c
commit 7a70a758f7
2 changed files with 242 additions and 213 deletions

View File

@ -24,7 +24,6 @@
#include "nsHTTPResponse.h"
#include "nsIHttpEventSink.h"
#include "nsCRT.h"
#include "stdio.h" //sscanf
#include "nsIHttpNotify.h"
#include "nsINetModRegEntry.h"
@ -34,18 +33,12 @@
#include "nsIEventQueueService.h"
#include "nsIBuffer.h"
static const int kMAX_FIRST_LINE_SIZE= 256;
static const int kMAX_BUFFER_SIZE = 1024;
//
// This specifies the maximum allowable size for a server Status-Line
// or Response-Header.
//
static const int kMAX_HEADER_SIZE = 60000;
static const char* kCRLF = "\r\n";
static const char kSP = ' ';
static const char kHT = '\t';
#define Skipln(p,max) \
PR_BEGIN_MACRO \
while ((*p != LF) && (max > p)) \
++p; \
PR_END_MACRO
nsHTTPResponseListener::nsHTTPResponseListener():
m_pConnection(nsnull),
@ -55,7 +48,8 @@ nsHTTPResponseListener::nsHTTPResponseListener():
m_ReadLength(0),
m_PartHeader(nsnull),
m_PartHeaderLen(0),
m_bHeadersDone(PR_FALSE)
m_bHeadersDone(PR_FALSE),
m_HeaderBuffer(eOneByte)
{
NS_INIT_REFCNT();
}
@ -85,6 +79,7 @@ nsHTTPResponseListener::OnDataAvailable(nsISupports* context,
PRUint32 i_Length)
{
nsresult rv = NS_OK;
PRUint32 actualBytesRead;
NS_ASSERTION(i_pStream, "No stream supplied by the transport!");
if (!m_pResponse)
@ -100,133 +95,35 @@ nsHTTPResponseListener::OnDataAvailable(nsISupports* context,
nsHTTPChannel* pTestCon = NS_STATIC_CAST(nsHTTPChannel*, m_pConnection);
pTestCon->SetResponse(m_pResponse);
}
//Rick- can the i_Length ever be zero?
if (0==i_Length)
return NS_OK; // Wait for next cycle.
if (m_bHeadersDone)
{
// Pass the notification out to the consumer...
NS_ASSERTION(m_pConsumer, "No Stream Listener!");
if (m_pConsumer) {
rv = m_pConsumer->OnDataAvailable(m_pConnection, i_pStream, 0, i_Length);
}
return rv;
}
if (!m_bHeadersDone) {
nsCOMPtr<nsIBuffer> pBuffer;
else if (!m_bFirstLineParsed) {
rv = ParseStatusLine(i_pStream);
rv = i_pStream->GetBuffer(getter_AddRefs(pBuffer));
if (NS_FAILED(rv)) return rv;
if (!m_bFirstLineParsed) {
rv = ParseStatusLine(pBuffer, i_Length, &actualBytesRead);
i_Length -= actualBytesRead;
}
while (NS_SUCCEEDED(rv) && i_Length && !m_bHeadersDone) {
rv = ParseHTTPHeader(pBuffer, i_Length, &actualBytesRead);
i_Length -= actualBytesRead;
}
if (NS_FAILED(rv)) return rv;
}
//Search for the end of the headers mark
//Rick- Is this max correct? or should it be the same as transport's max?
char buffer[kMAX_BUFFER_SIZE];
nsCOMPtr<nsIBuffer> pBuffer;
rv = i_pStream->GetBuffer(getter_AddRefs(pBuffer));
if (NS_FAILED(rv)) return rv;
const char* headerTerminationStr = "\r\n\r\n";
PRBool bFoundEnd = PR_FALSE;
PRUint32 offsetSearchedTo = i_Length;
rv = pBuffer->Search(headerTerminationStr, PR_FALSE, &bFoundEnd, &offsetSearchedTo);
if (NS_FAILED(rv)) return rv;
if (!bFoundEnd)
{
headerTerminationStr = "\n\n";
rv = pBuffer->Search(headerTerminationStr, PR_FALSE, &bFoundEnd, &offsetSearchedTo);
if (NS_FAILED(rv)) return rv;
NS_ASSERTION(m_pConsumer, "No Stream Listener!");
if (i_Length && m_pConsumer) {
rv = m_pConsumer->OnDataAvailable(m_pConnection, i_pStream, 0, i_Length);
}
// If the end of the headers was found then adjust the offset to include
// the termination characters...
if (bFoundEnd)
{
offsetSearchedTo += PL_strlen(headerTerminationStr);
}
// Now read the buffer upto the offsetSearchedTo
PRUint32 lengthRead = 0;
rv = pBuffer->Read(buffer, offsetSearchedTo, &lengthRead);
if (NS_FAILED(rv)) return rv;
buffer[lengthRead] = '\0';
char* p = buffer;
//parse this buffer-
while (buffer+lengthRead > p)
{
char* lineStart = p;
if (*lineStart == '\0' || *lineStart == CR || *lineStart == LF)
{
m_bHeadersDone = PR_TRUE;
// TODO process headers here.
// Based on the process headers we may or may not want to
// fire the headers available
FireOnHeadersAvailable();
// Fire the partial length onDataAvailable
if (i_Length > lengthRead)
{
NS_ASSERTION(m_pConsumer, "No Stream Listner!");
if (m_pConsumer)
rv = m_pConsumer->OnDataAvailable(m_pConnection, i_pStream, 0, i_Length-lengthRead);
}
break; // break off this buffer while
}
// Skip to end of line;
Skipln(p, buffer+lengthRead);
/*
if (!m_bFirstLineParsed)
{
char server_version[9]; // HTTP/1.1
PRUint32 stat = 0;
char stat_str[kMAX_FIRST_LINE_SIZE];
sscanf(lineStart, "%8s %d %s", server_version, &stat, stat_str);
m_pResponse->SetServerVersion(server_version);
m_pResponse->SetStatus(stat);
m_pResponse->SetStatusString(stat_str);
m_bFirstLineParsed = PR_TRUE;
}
else
*/
{
char* header = lineStart;
char* value = PL_strchr(lineStart, ':');
if(value)
{
//mark the end of header
*value = '\0';
value++;
//mark the end of value
*p = '\0';
if (m_PartHeaderLen == 0)
m_pResponse->SetHeaderInternal(header, value);
else
{
//append the header to the partheader
header = PL_strcat(m_PartHeader, header);
m_pResponse->SetHeaderInternal(header, value);
//Reset partHeader now
delete m_PartHeader;
m_PartHeader = 0;
m_PartHeaderLen = 0;
}
}
else // this is just a part of the header so save it for later use...
{
NS_ASSERTION(m_PartHeaderLen == 0, "Overwriting partial header!");
m_PartHeaderLen = p-header;
m_PartHeader = new char(m_PartHeaderLen+1);
PL_strncpy(m_PartHeader, lineStart, m_PartHeaderLen);
m_PartHeader[m_PartHeaderLen] = '\0';
}
}
p++;
}
return rv;
}
NS_IMETHODIMP
nsHTTPResponseListener::OnStartBinding(nsISupports* i_pContext)
{
@ -408,105 +305,230 @@ nsresult nsHTTPResponseListener::FireOnHeadersAvailable()
return rv;
}
char *nsHTTPResponseListener::EatWhiteSpace(char *aBuffer)
NS_METHOD
nsWriteToString(void* closure,
const char* fromRawSegment,
PRUint32 offset,
PRUint32 count,
PRUint32 *writeCount)
{
while ((*aBuffer == ' ') || (*aBuffer == '\t')) aBuffer++;
return aBuffer;
nsString *str = (nsString*)closure;
str->Append(fromRawSegment, count);
*writeCount = count;
return NS_OK;
}
nsresult nsHTTPResponseListener::ParseStatusLine(nsIBufferInputStream* aStream)
nsresult nsHTTPResponseListener::ParseStatusLine(nsIBuffer* aBuffer,
PRUint32 aLength,
PRUint32 *aBytesRead)
{
nsresult rv = NS_OK;
nsCOMPtr<nsIBuffer> pBuffer;
char statusLineBuffer[255];
char *start, *end;
PRBool bFoundString = PR_FALSE;
PRUint32 offset, length, bytesRead;
PRInt32 CRLFlength, statusCode;
PRUint32 offsetOfEnd, totalBytesToRead, actualBytesRead;
rv = aStream->GetBuffer(getter_AddRefs(pBuffer));
*aBytesRead = 0;
if (kMAX_HEADER_SIZE < m_HeaderBuffer.Length()) {
// This server is yanking our chain...
return NS_ERROR_FAILURE;
}
// Look for the LF which ends the Status-Line.
rv = aBuffer->Search("\n", PR_FALSE, &bFoundString, &offsetOfEnd);
if (NS_FAILED(rv)) return rv;
// Look for the CRLF which ends the Status-Line.
// If found, then offset will mark the beginning of the CRLF...
rv = pBuffer->Search(kCRLF, PR_FALSE, &bFoundString, &offset);
if (!bFoundString) {
//
// This is a partial header... Read the entire buffer and wait for
// more data...
//
totalBytesToRead = aLength;
} else {
// Do not forget to include the LF character in the read...
totalBytesToRead = offsetOfEnd+1;
}
rv = aBuffer->ReadSegments(nsWriteToString,
(void*)&m_HeaderBuffer,
totalBytesToRead,
&actualBytesRead);
if (NS_FAILED(rv)) return rv;
*aBytesRead += actualBytesRead;
// Wait for more data to arrive before processing the header...
if (!bFoundString) return NS_OK;
//
// Replace all LWS with single SP characters. Also remove the CRLF
// characters...
//
m_HeaderBuffer.CompressSet(" \t", ' ');
m_HeaderBuffer.StripChars("\r\n");
//
// The Status Line has the following: format:
// HTTP-Version SP Status-Code SP Reason-Phrase CRLF
//
if (bFoundString) {
// Include the CRLF as part of the line to read...
CRLFlength = PL_strlen(kCRLF);
length = offset + CRLFlength;
//
// Make sure the statusLineBuffer does not overflow. This would only
// happen if the Reason-Phrase returned from the server was large...
//
// If the Status-Line is too large, then limit it to the size of the
// buffer. This will truncate the Reason-Phrase, but who really cares?
//
NS_ASSERTION(length < sizeof(statusLineBuffer), "Status Line is too long!");
if (length >= sizeof(statusLineBuffer)) {
length = sizeof(statusLineBuffer);
}
const char *token;
nsAutoString str(eOneByte);
PRInt32 offset, error;
// Read the Status-Line out of the stream...
rv = aStream->Read(statusLineBuffer, length, &bytesRead);
if (NS_FAILED(rv)) return rv;
//
// Parse the HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
//
// Null terminate the buffer right before the CRLF...
// If the Status-Line was truncated then NULL terminate at the end...
if (bytesRead > offset) {
statusLineBuffer[bytesRead-CRLFlength] = '\0';
} else {
statusLineBuffer[bytesRead] = '\0';
}
//
// Parse the HTTP-Version -> "HTTP" "/" 1*DIGIT "." 1*DIGIT
//
start = EatWhiteSpace(statusLineBuffer); // Consume any leading whitespace
// Find the next space character...
for (end=start; (*end && (*end != kSP) && (*end != kHT)); end++) {};
if (! *end) {
// The status line is bogus...
return NS_ERROR_FAILURE;
}
*end = '\0'; // Replace the first space character with NULL...
m_pResponse->SetServerVersion(start);
//
// Parse the Status-Code -> 3DIGIT
//
start = EatWhiteSpace(end+1); // Consume any leading whitespace
end = start+3;
// Verify that the character after the 3 digits is whitespace...
if ((bytesRead < (PRUint32)(end - statusLineBuffer)) ||
((*end != kSP) && (*end != kHT))) {
// The status line is bogus...
return NS_ERROR_FAILURE;
}
*end = '\0'; // Replace the first space character with NULL...
statusCode = atoi(start);
m_pResponse->SetStatus(statusCode);
//
// Parse the Reason-Phrase -> *<TEXT excluding CR,LF>
//
start = EatWhiteSpace(end+1); // Consume any leading whitespace
m_pResponse->SetStatusString(start);
m_bFirstLineParsed = PR_TRUE;
offset = m_HeaderBuffer.Find(' ');
(void) m_HeaderBuffer.Left(str, offset);
if (!str.Length()) {
// The status line is bogus...
return NS_ERROR_FAILURE;
}
else {
// XXX: What do we do if only a partial status line is received?
rv = NS_ERROR_FAILURE;
token = str.GetBuffer();
m_pResponse->SetServerVersion(token);
m_HeaderBuffer.Cut(0, offset+1);
//
// Parse the Status-Code:: 3DIGIT
//
PRInt32 statusCode;
offset = m_HeaderBuffer.Find(' ');
(void) m_HeaderBuffer.Left(str, offset);
if (3 != str.Length()) {
// The status line is bogus...
return NS_ERROR_FAILURE;
}
statusCode = str.ToInteger(&error);
if (NS_FAILED(error)) return NS_ERROR_FAILURE;
m_pResponse->SetStatus(statusCode);
m_HeaderBuffer.Cut(0, offset+1);
//
// Parse the Reason-Phrase:: *<TEXT excluding CR,LF>
//
if (!m_HeaderBuffer.Length()) {
// The status line is bogus...
return NS_ERROR_FAILURE;
}
token = m_HeaderBuffer.GetBuffer();
m_pResponse->SetStatusString(token);
m_HeaderBuffer.Truncate();
m_bFirstLineParsed = PR_TRUE;
return rv;
}
nsresult nsHTTPResponseListener::ParseHTTPHeader(nsIBuffer* aBuffer,
PRUint32 aLength,
PRUint32 *aBytesRead)
{
nsresult rv = NS_OK;
const char *buf;
PRBool bFoundString;
PRUint32 offsetOfEnd, totalBytesToRead, actualBytesRead;
*aBytesRead = 0;
if (kMAX_HEADER_SIZE < m_HeaderBuffer.Length()) {
// This server is yanking our chain...
return NS_ERROR_FAILURE;
}
//
// Read the header from the input buffer... A header is terminated by
// a CRLF. Header values may be extended over multiple lines by preceeding
// each extran line with LWS...
//
do {
//
// If last character in the header string is a LF, then the header
// may be complete...
//
if (m_HeaderBuffer.Last() == '\n' ) {
rv = aBuffer->GetReadSegment(0, &buf, &actualBytesRead);
// Need to wait for more data to see if the header is complete.
if (0 == actualBytesRead) {
return NS_OK;
}
// Not LWS - The header is complete...
if ((*buf != ' ') && (*buf != '\t')) {
break;
}
}
// Look for the next LF in the buffer...
rv = aBuffer->Search("\n", PR_FALSE, &bFoundString, &offsetOfEnd);
if (NS_FAILED(rv)) return rv;
if (!bFoundString) {
//
// The buffer contains a partial header. Read the entire buffer
// and wait for more data...
//
totalBytesToRead = aLength;
} else {
// Do not forget to include the LF character in the read...
totalBytesToRead = offsetOfEnd+1;
}
// Append the buffer into the header string...
rv = aBuffer->ReadSegments(nsWriteToString,
(void*)&m_HeaderBuffer,
totalBytesToRead,
&actualBytesRead);
if (NS_FAILED(rv)) return rv;
*aBytesRead += actualBytesRead;
// Partial header - wait for more data to arrive...
if (!bFoundString) return NS_OK;
} while (PR_TRUE);
//
// Replace all LWS with single SP characters. And remove all of the CRLF
// characters...
//
m_HeaderBuffer.CompressSet(" \t", ' ');
m_HeaderBuffer.StripChars("\r\n");
if (!m_HeaderBuffer.Length()) {
m_bHeadersDone = PR_TRUE;
return NS_OK;
}
nsAutoString headerKey(eOneByte);
PRInt32 colonOffset;
// Extract the key field - everything up to the ':'
// The header name is case-insensitive...
colonOffset = m_HeaderBuffer.Find(':');
(void) m_HeaderBuffer.Left(headerKey, colonOffset);
headerKey.ToLowerCase();
// Extract the value field - everything past the ':'
// Trim any leading or trailing whitespace...
m_HeaderBuffer.Cut(0, colonOffset+1);
m_HeaderBuffer.Trim(" ");
rv = m_pResponse->SetHeaderInternal(headerKey.GetBuffer(), m_HeaderBuffer.GetBuffer());
m_HeaderBuffer.Truncate();
return rv;
}

View File

@ -20,9 +20,13 @@
#define _nsHTTPResponseListener_h_
#include "nsIStreamListener.h"
#include "nsString.h"
class nsIBuffer;
class nsIChannel;
class nsHTTPResponse;
class nsIHTTPChannel;
/*
The nsHTTPResponseListener class is the response reader listener that
receives notifications of OnStartBinding, OnDataAvailable and
@ -69,10 +73,11 @@ protected:
// nsHTTPResponseListener methods...
nsresult FireOnHeadersAvailable();
nsresult ParseStatusLine(nsIBufferInputStream* aStream);
nsresult ParseStatusLine(nsIBuffer* aBuffer, PRUint32 aLength,
PRUint32 *aBytesRead);
static char * EatWhiteSpace(char* aBuffer);
nsresult ParseHTTPHeader(nsIBuffer* aBuffer, PRUint32 aLength,
PRUint32* aBytesRead);
protected:
@ -84,6 +89,8 @@ protected:
PRUint32 m_ReadLength; // Already read
char* m_PartHeader; // used between calls of OnDataAvailable
PRUint32 m_PartHeaderLen;
nsString m_HeaderBuffer;
};
#endif /* _nsHTTPResponseListener_h_ */