diff --git a/netwerk/protocol/http/src/nsHttp.h b/netwerk/protocol/http/src/nsHttp.h index 20b4dbaf227e..b5245a0a2a37 100644 --- a/netwerk/protocol/http/src/nsHttp.h +++ b/netwerk/protocol/http/src/nsHttp.h @@ -76,6 +76,9 @@ struct nsHttpAtom operator const char *() { return _val; } const char *get() { return _val; } + void operator=(const char *v) { _val = v; } + void operator=(const nsHttpAtom &a) { _val = a._val; } + // private const char *_val; }; diff --git a/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp b/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp index 1e2b8f3e3484..9dded3bec31c 100644 --- a/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp +++ b/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp @@ -36,6 +36,27 @@ nsHttpChunkedDecoder::HandleChunkedContent(char *buf, LOG(("nsHttpChunkedDecoder::HandleChunkedContent [count=%u]\n", count)); *countRead = 0; + + // from RFC2617 section 3.6.1, the chunked transfer coding is defined as: + // + // Chunked-Body = *chunk + // last-chunk + // trailer + // CRLF + // chunk = chunk-size [ chunk-extension ] CRLF + // chunk-data CRLF + // chunk-size = 1*HEX + // last-chunk = 1*("0") [ chunk-extension ] CRLF + // + // chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + // chunk-ext-name = token + // chunk-ext-val = token | quoted-string + // chunk-data = chunk-size(OCTET) + // trailer = *(entity-header CRLF) + // + // the chunk-size field is a string of hex digits indicating the size of the + // chunk. the chunked encoding is ended by any chunk whose size is zero, + // followed by the trailer, which is terminated by an empty line. while (count) { if (mChunkRemaining) { @@ -47,8 +68,9 @@ nsHttpChunkedDecoder::HandleChunkedContent(char *buf, *countRead += amt; buf += amt; } - - if (count) { + else if (mReachedEOF) + break; // done + else { PRUint32 bytesConsumed = 0; nsresult rv = ParseChunkRemaining(buf, count, &bytesConsumed); @@ -83,41 +105,59 @@ nsHttpChunkedDecoder::ParseChunkRemaining(char *buf, char *p = NS_STATIC_CAST(char *, memchr(buf, '\n', count)); if (p) { *p = 0; + if ((p > buf) && (*(p-1) == '\r')) // eliminate a preceding CR + *(p-1) = 0; *bytesConsumed = p - buf + 1; - if (mLineBuf.IsEmpty()) - p = buf; - else { - // append to the line buf and use that as the buf to parse. + + // make buf point to the full line buffer to parse + if (!mLineBuf.IsEmpty()) { mLineBuf.Append(buf); - p = (char *) mLineBuf.get(); + buf = (char *) mLineBuf.get(); } + + if (mWaitEOF) { + if (*buf) { + LOG(("got trailer: %s\n", buf)); + // allocate a header array for the trailers on demand + if (!mTrailers) { + mTrailers = new nsHttpHeaderArray(); + if (!mTrailers) + return NS_ERROR_OUT_OF_MEMORY; + } + mTrailers->ParseHeaderLine(buf); + } + else { + mWaitEOF = PR_FALSE; + mReachedEOF = PR_TRUE; + LOG(("reached end of chunked-body\n")); + } + } + else if (*buf) { + // ignore any chunk-extensions + if ((p = PL_strchr(buf, ';')) != nsnull) + *p = 0; + + if (!sscanf(buf, "%x", &mChunkRemaining)) { + LOG(("sscanf failed parsing hex on string [%s]\n", buf)); + return NS_ERROR_UNEXPECTED; + } + + // we've discovered the last chunk + if (mChunkRemaining == 0) + mWaitEOF = PR_TRUE; + } + + // ensure that the line buffer is clear + mLineBuf.Truncate(); } else { - mLineBuf.Append(buf, count); + // save the partial line; wait for more data *bytesConsumed = count; - return NS_OK; + // ignore a trailing CR + if (buf[count-1] == '\r') + count--; + mLineBuf.Append(buf, count); } - if (*p && *p != '\r') { - buf = p; - - // ignore any chunk-extensions - if ((p = PL_strchr(buf, ';')) != nsnull) - *p = 0; - - if (!sscanf(buf, "%x", &mChunkRemaining)) { - LOG(("sscanf failed parsing hex on string [%s]\n", buf)); - return NS_ERROR_UNEXPECTED; - } - - // XXX need to add code to consume "trailer" headers - if ((mChunkRemaining == 0) && (*buf != 0)) - mReachedEOF = PR_TRUE; - } - - // clear the line buffer if it is not already empty - if (!mLineBuf.IsEmpty()) - mLineBuf.SetLength(0); - return NS_OK; } diff --git a/netwerk/protocol/http/src/nsHttpChunkedDecoder.h b/netwerk/protocol/http/src/nsHttpChunkedDecoder.h index c07bbfcf4238..9a8682a5d53b 100644 --- a/netwerk/protocol/http/src/nsHttpChunkedDecoder.h +++ b/netwerk/protocol/http/src/nsHttpChunkedDecoder.h @@ -26,28 +26,41 @@ #include "nsError.h" #include "nsString.h" +#include "nsHttpHeaderArray.h" class nsHttpChunkedDecoder { public: - nsHttpChunkedDecoder() : mChunkRemaining(0), mReachedEOF(0) {} - ~nsHttpChunkedDecoder() {} + nsHttpChunkedDecoder() : mTrailers(nsnull) + , mChunkRemaining(0) + , mReachedEOF(PR_FALSE) + , mWaitEOF(PR_FALSE) {} + ~nsHttpChunkedDecoder() { delete mTrailers; } PRBool ReachedEOF() { return mReachedEOF; } - // Called by the transaction to handle chunked content. + // called by the transaction to handle chunked content. nsresult HandleChunkedContent(char *buf, PRUint32 count, PRUint32 *countRead); + + nsHttpHeaderArray *Trailers() { return mTrailers; } + + nsHttpHeaderArray *TakeTrailers() { nsHttpHeaderArray *h = mTrailers; + mTrailers = nsnull; + return h; } + private: nsresult ParseChunkRemaining(char *buf, PRUint32 count, PRUint32 *countRead); private: - PRUint32 mChunkRemaining; - nsCString mLineBuf; // may hold a partial line - PRPackedBool mReachedEOF; + nsHttpHeaderArray *mTrailers; + PRUint32 mChunkRemaining; + nsCString mLineBuf; // may hold a partial line + PRPackedBool mReachedEOF; + PRPackedBool mWaitEOF; }; #endif diff --git a/netwerk/protocol/http/src/nsHttpHeaderArray.cpp b/netwerk/protocol/http/src/nsHttpHeaderArray.cpp index 2d26a6e487f6..044297ebedcb 100644 --- a/netwerk/protocol/http/src/nsHttpHeaderArray.cpp +++ b/netwerk/protocol/http/src/nsHttpHeaderArray.cpp @@ -102,6 +102,62 @@ nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor) return NS_OK; } +void +nsHttpHeaderArray::ParseHeaderLine(char *line, nsHttpAtom *hdr, char **val) +{ + char *p = PL_strchr(line, ':'), *p2; + + // the header is malformed... but, there are malformed headers in the + // world. search for ' ' and '\t' to simulate 4.x/IE behavior. + if (!p) { + p = PL_strchr(line, ' '); + if (!p) { + p = PL_strchr(line, '\t'); + if (!p) { + // some broken cgi scripts even use '=' as a delimiter!! + p = PL_strchr(line, '='); + } + } + } + + if (p) { + // ignore whitespace between header name and colon + p2 = p; + while (--p2 >= line && ((*p2 == ' ') || (*p2 == '\t'))) + ; + *++p2= 0; // overwrite first char after header name + + nsHttpAtom atom = nsHttp::ResolveAtom(line); + if (atom) { + // skip over whitespace + do { + ++p; + } while ((*p == ' ') || (*p == '\t')); + + // trim trailing whitespace - bug 86608 + p2 = p + PL_strlen(p); + do { + --p2; + } while (p2 >= p && ((*p2 == ' ') || (*p2 == '\t'))); + *++p2 = 0; + + // assign return values + if (hdr) *hdr = atom; + if (val) *val = p; + + // assign response header + SetHeader(atom, p); + } + else + LOG(("unknown header; skipping\n")); + } + else + LOG(("malformed header\n")); + + // We ignore mal-formed headers in the hope that we'll still be able + // to do something useful with the response. +} + void nsHttpHeaderArray::Flatten(nsACString &buf) { diff --git a/netwerk/protocol/http/src/nsHttpHeaderArray.h b/netwerk/protocol/http/src/nsHttpHeaderArray.h index 92ccc554a3d5..e3bf5af99dea 100644 --- a/netwerk/protocol/http/src/nsHttpHeaderArray.h +++ b/netwerk/protocol/http/src/nsHttpHeaderArray.h @@ -43,7 +43,11 @@ public: nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor); - void Flatten(nsACString &); + // parse a header line, return the header atom and a pointer to the + // header value (the substring of the header line -- do not free). + void ParseHeaderLine(char *line, nsHttpAtom *header=nsnull, char **value=nsnull); + + void Flatten(nsACString &); PRUint32 Count() { return (PRUint32) mHeaders.Count(); } diff --git a/netwerk/protocol/http/src/nsHttpResponseHead.cpp b/netwerk/protocol/http/src/nsHttpResponseHead.cpp index 7a66bc2883c4..1e26574cc162 100644 --- a/netwerk/protocol/http/src/nsHttpResponseHead.cpp +++ b/netwerk/protocol/http/src/nsHttpResponseHead.cpp @@ -169,59 +169,16 @@ nsHttpResponseHead::ParseStatusLine(char *line) void nsHttpResponseHead::ParseHeaderLine(char *line) { - char *p = PL_strchr(line, ':'), *p2; + nsHttpAtom hdr; + char *val; - // the header is malformed... but, there are malformed headers in the - // world. search for ' ' and '\t' to simulate 4.x/IE behavior. - if (!p) { - p = PL_strchr(line, ' '); - if (!p) { - p = PL_strchr(line, '\t'); - if (!p) { - // some broken cgi scripts even use '=' as a delimiter!! - p = PL_strchr(line, '='); - } - } - } + mHeaders.ParseHeaderLine(line, &hdr, &val); - if (p) { - // ignore whitespace between header name and colon - p2 = p; - while (--p2 >= line && ((*p2 == ' ') || (*p2 == '\t'))) - ; - *++p2= 0; // overwrite first char after header name - - nsHttpAtom atom = nsHttp::ResolveAtom(line); - if (atom) { - // skip over whitespace - do { - ++p; - } while ((*p == ' ') || (*p == '\t')); - - // trim trailing whitespace - bug 86608 - p2 = p + PL_strlen(p); - do { - --p2; - } while (p2 >= p && ((*p2 == ' ') || (*p2 == '\t'))); - *++p2 = 0; - - // assign response header - mHeaders.SetHeader(atom, p); - - // handle some special case headers... - if (atom == nsHttp::Content_Length) - mContentLength = atoi(p); - else if (atom == nsHttp::Content_Type) - ParseContentType(p); - } - else - LOG(("unknown header; skipping\n")); - } - else - LOG(("malformed header\n")); - - // We ignore mal-formed headers in the hope that we'll still be able - // to do something useful with the response. + // handle some special case headers... + if (hdr == nsHttp::Content_Length) + mContentLength = atoi(val); + else if (hdr == nsHttp::Content_Type) + ParseContentType(val); } // From section 13.2.3 of RFC2616, we compute the current age of a cached diff --git a/netwerk/protocol/http/src/nsHttpTransaction.cpp b/netwerk/protocol/http/src/nsHttpTransaction.cpp index 0a3d8397a23c..ace598fc8416 100644 --- a/netwerk/protocol/http/src/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/src/nsHttpTransaction.cpp @@ -503,14 +503,6 @@ nsHttpTransaction::HandleContentStart() if (!mChunkedDecoder) return NS_ERROR_OUT_OF_MEMORY; LOG(("chunked decoder created\n")); - - val = mResponseHead->PeekHeader(nsHttp::Trailer); - if (val) { - LOG(("response contains a Trailer header\n")); - // FIXME we should at least eat the trailer headers so this - // connection could be reused. - mConnection->DontReuse(); - } } #if defined(PR_LOGGING) else if (mContentLength == -1)