mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1570 lines
58 KiB
C++
1570 lines
58 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// HttpLog.h should generally be included first
|
|
#include "HttpLog.h"
|
|
|
|
// Log on level :5, instead of default :4.
|
|
#undef LOG
|
|
#define LOG(args) LOG5(args)
|
|
#undef LOG_ENABLED
|
|
#define LOG_ENABLED() LOG5_ENABLED()
|
|
|
|
#include "mozilla/Endian.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "nsHttp.h"
|
|
#include "nsHttpHandler.h"
|
|
#include "nsHttpRequestHead.h"
|
|
#include "nsISocketTransport.h"
|
|
#include "nsISupportsPriority.h"
|
|
#include "prnetdb.h"
|
|
#include "SpdyPush3.h"
|
|
#include "SpdySession3.h"
|
|
#include "SpdyStream3.h"
|
|
#include "PSpdyPush.h"
|
|
#include "SpdyZlibReporter.h"
|
|
#include "TunnelUtils.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#ifdef DEBUG
|
|
// defined by the socket transport service while active
|
|
extern PRThread *gSocketThread;
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction,
|
|
SpdySession3 *spdySession,
|
|
int32_t priority)
|
|
: mStreamID(0)
|
|
, mSession(spdySession)
|
|
, mUpstreamState(GENERATING_SYN_STREAM)
|
|
, mSynFrameComplete(0)
|
|
, mSentFinOnData(0)
|
|
, mTransaction(httpTransaction)
|
|
, mSocketTransport(spdySession->SocketTransport())
|
|
, mSegmentReader(nullptr)
|
|
, mSegmentWriter(nullptr)
|
|
, mChunkSize(spdySession->SendingChunkSize())
|
|
, mRequestBlockedOnRead(0)
|
|
, mRecvdFin(0)
|
|
, mFullyOpen(0)
|
|
, mSentWaitingFor(0)
|
|
, mReceivedData(0)
|
|
, mSetTCPSocketBuffer(0)
|
|
, mTxInlineFrameSize(SpdySession3::kDefaultBufferSize)
|
|
, mTxInlineFrameUsed(0)
|
|
, mTxStreamFrameSize(0)
|
|
, mZlib(spdySession->UpstreamZlib())
|
|
, mDecompressBufferSize(SpdySession3::kDefaultBufferSize)
|
|
, mDecompressBufferUsed(0)
|
|
, mDecompressedBytes(0)
|
|
, mRequestBodyLenRemaining(0)
|
|
, mPriority(priority)
|
|
, mLocalUnacked(0)
|
|
, mBlockedOnRwin(false)
|
|
, mTotalSent(0)
|
|
, mTotalRead(0)
|
|
, mPushSource(nullptr)
|
|
, mIsTunnel(false)
|
|
{
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
|
|
LOG3(("SpdyStream3::SpdyStream3 %p", this));
|
|
|
|
mRemoteWindow = spdySession->GetServerInitialWindow();
|
|
mLocalWindow = spdySession->PushAllowance();
|
|
|
|
mTxInlineFrame = new uint8_t[mTxInlineFrameSize];
|
|
mDecompressBuffer = new char[mDecompressBufferSize];
|
|
}
|
|
|
|
SpdyStream3::~SpdyStream3()
|
|
{
|
|
ClearTransactionsBlockedOnTunnel();
|
|
mStreamID = SpdySession3::kDeadStreamID;
|
|
}
|
|
|
|
// ReadSegments() is used to write data down the socket. Generally, HTTP
|
|
// request data is pulled from the approriate transaction and
|
|
// converted to SPDY data. Sometimes control data like a window-update is
|
|
// generated instead.
|
|
|
|
nsresult
|
|
SpdyStream3::ReadSegments(nsAHttpSegmentReader *reader,
|
|
uint32_t count,
|
|
uint32_t *countRead)
|
|
{
|
|
LOG3(("SpdyStream3 %p ReadSegments reader=%p count=%d state=%x",
|
|
this, reader, count, mUpstreamState));
|
|
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
|
|
nsresult rv = NS_ERROR_UNEXPECTED;
|
|
mRequestBlockedOnRead = 0;
|
|
|
|
switch (mUpstreamState) {
|
|
case GENERATING_SYN_STREAM:
|
|
case GENERATING_REQUEST_BODY:
|
|
case SENDING_REQUEST_BODY:
|
|
// Call into the HTTP Transaction to generate the HTTP request
|
|
// stream. That stream will show up in OnReadSegment().
|
|
mSegmentReader = reader;
|
|
rv = mTransaction->ReadSegments(this, count, countRead);
|
|
mSegmentReader = nullptr;
|
|
LOG3(("SpdyStream3::ReadSegments %p trans readsegments rv %x read=%d\n",
|
|
this, rv, *countRead));
|
|
// Check to see if the transaction's request could be written out now.
|
|
// If not, mark the stream for callback when writing can proceed.
|
|
if (NS_SUCCEEDED(rv) &&
|
|
mUpstreamState == GENERATING_SYN_STREAM &&
|
|
!mSynFrameComplete)
|
|
mSession->TransactionHasDataToWrite(this);
|
|
|
|
// mTxinlineFrameUsed represents any queued un-sent frame. It might
|
|
// be 0 if there is no such frame, which is not a gurantee that we
|
|
// don't have more request body to send - just that any data that was
|
|
// sent comprised a complete SPDY frame. Likewise, a non 0 value is
|
|
// a queued, but complete, spdy frame length.
|
|
|
|
// Mark that we are blocked on read if the http transaction needs to
|
|
// provide more of the request message body and there is nothing queued
|
|
// for writing
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
|
|
mRequestBlockedOnRead = 1;
|
|
|
|
// If the sending flow control window is open (!mBlockedOnRwin) then
|
|
// continue sending the request
|
|
if (!mBlockedOnRwin &&
|
|
!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
|
|
LOG3(("SpdyStream3::ReadSegments %p 0x%X: Sending request data complete, "
|
|
"mUpstreamState=%x finondata=%d",this, mStreamID,
|
|
mUpstreamState, mSentFinOnData));
|
|
if (mSentFinOnData) {
|
|
ChangeState(UPSTREAM_COMPLETE);
|
|
}
|
|
else {
|
|
GenerateDataFrameHeader(0, true);
|
|
ChangeState(SENDING_FIN_STREAM);
|
|
mSession->TransactionHasDataToWrite(this);
|
|
rv = NS_BASE_STREAM_WOULD_BLOCK;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SENDING_FIN_STREAM:
|
|
// We were trying to send the FIN-STREAM but were blocked from
|
|
// sending it out - try again.
|
|
if (!mSentFinOnData) {
|
|
mSegmentReader = reader;
|
|
rv = TransmitFrame(nullptr, nullptr, false);
|
|
mSegmentReader = nullptr;
|
|
MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
|
|
"Transmit Frame should be all or nothing");
|
|
if (NS_SUCCEEDED(rv))
|
|
ChangeState(UPSTREAM_COMPLETE);
|
|
}
|
|
else {
|
|
rv = NS_OK;
|
|
mTxInlineFrameUsed = 0; // cancel fin data packet
|
|
ChangeState(UPSTREAM_COMPLETE);
|
|
}
|
|
|
|
*countRead = 0;
|
|
|
|
// don't change OK to WOULD BLOCK. we are really done sending if OK
|
|
break;
|
|
|
|
case UPSTREAM_COMPLETE:
|
|
*countRead = 0;
|
|
rv = NS_OK;
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false, "SpdyStream3::ReadSegments unknown state");
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// WriteSegments() is used to read data off the socket. Generally this is
|
|
// just a call through to the associate nsHttpTransaciton for this stream
|
|
// for the remaining data bytes indicated by the current DATA frame.
|
|
|
|
nsresult
|
|
SpdyStream3::WriteSegments(nsAHttpSegmentWriter *writer,
|
|
uint32_t count,
|
|
uint32_t *countWritten)
|
|
{
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
|
|
|
|
LOG3(("SpdyStream3::WriteSegments %p count=%d state=%x",
|
|
this, count, mUpstreamState));
|
|
|
|
mSegmentWriter = writer;
|
|
nsresult rv = mTransaction->WriteSegments(this, count, countWritten);
|
|
mSegmentWriter = nullptr;
|
|
|
|
return rv;
|
|
}
|
|
|
|
PLDHashOperator
|
|
SpdyStream3::hdrHashEnumerate(const nsACString &key,
|
|
nsAutoPtr<nsCString> &value,
|
|
void *closure)
|
|
{
|
|
SpdyStream3 *self = static_cast<SpdyStream3 *>(closure);
|
|
|
|
self->CompressToFrame(key);
|
|
self->CompressToFrame(value.get());
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
void
|
|
SpdyStream3::CreatePushHashKey(const nsCString &scheme,
|
|
const nsCString &hostHeader,
|
|
uint64_t serial,
|
|
const nsCSubstring &pathInfo,
|
|
nsCString &outOrigin,
|
|
nsCString &outKey)
|
|
{
|
|
outOrigin = scheme;
|
|
outOrigin.AppendLiteral("://");
|
|
outOrigin.Append(hostHeader);
|
|
|
|
outKey = outOrigin;
|
|
outKey.AppendLiteral("/[spdy3.");
|
|
outKey.AppendInt(serial);
|
|
outKey.Append(']');
|
|
outKey.Append(pathInfo);
|
|
}
|
|
|
|
|
|
nsresult
|
|
SpdyStream3::ParseHttpRequestHeaders(const char *buf,
|
|
uint32_t avail,
|
|
uint32_t *countUsed)
|
|
{
|
|
// Returns NS_OK even if the headers are incomplete
|
|
// set mSynFrameComplete flag if they are complete
|
|
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
MOZ_ASSERT(mUpstreamState == GENERATING_SYN_STREAM);
|
|
|
|
LOG3(("SpdyStream3::ParseHttpRequestHeaders %p avail=%d state=%x",
|
|
this, avail, mUpstreamState));
|
|
|
|
mFlatHttpRequestHeaders.Append(buf, avail);
|
|
|
|
// We can use the simple double crlf because firefox is the
|
|
// only client we are parsing
|
|
int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
|
|
|
|
if (endHeader == kNotFound) {
|
|
// We don't have all the headers yet
|
|
LOG3(("SpdyStream3::ParseHttpRequestHeaders %p "
|
|
"Need more header bytes. Len = %d",
|
|
this, mFlatHttpRequestHeaders.Length()));
|
|
*countUsed = avail;
|
|
return NS_OK;
|
|
}
|
|
|
|
// We have recvd all the headers, trim the local
|
|
// buffer of the final empty line, and set countUsed to reflect
|
|
// the whole header has been consumed.
|
|
uint32_t oldLen = mFlatHttpRequestHeaders.Length();
|
|
mFlatHttpRequestHeaders.SetLength(endHeader + 2);
|
|
*countUsed = avail - (oldLen - endHeader) + 4;
|
|
mSynFrameComplete = 1;
|
|
|
|
nsAutoCString hostHeader;
|
|
nsAutoCString hashkey;
|
|
mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
|
|
|
|
CreatePushHashKey(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"),
|
|
hostHeader, mSession->Serial(),
|
|
mTransaction->RequestHead()->RequestURI(),
|
|
mOrigin, hashkey);
|
|
|
|
// check the push cache for GET
|
|
if (mTransaction->RequestHead()->IsGet()) {
|
|
// from :scheme, :host, :path
|
|
nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
|
|
SpdyPushCache *cache = nullptr;
|
|
if (loadGroupCI)
|
|
loadGroupCI->GetSpdyPushCache(&cache);
|
|
|
|
SpdyPushedStream3 *pushedStream = nullptr;
|
|
// we remove the pushedstream from the push cache so that
|
|
// it will not be used for another GET. This does not destroy the
|
|
// stream itself - that is done when the transactionhash is done with it.
|
|
if (cache)
|
|
pushedStream = cache->RemovePushedStreamSpdy3(hashkey);
|
|
|
|
if (pushedStream) {
|
|
LOG3(("Pushed Stream Match located id=0x%X key=%s\n",
|
|
pushedStream->StreamID(), hashkey.get()));
|
|
pushedStream->SetConsumerStream(this);
|
|
mPushSource = pushedStream;
|
|
mSentFinOnData = 1;
|
|
|
|
// This stream has been activated (and thus counts against the concurrency
|
|
// limit intentionally), but will not be registered via
|
|
// RegisterStreamID (below) because of the push match. Therefore the
|
|
// concurrency sempahore needs to be balanced.
|
|
mSession->DecrementConcurrent(this);
|
|
|
|
// There is probably pushed data buffered so trigger a read manually
|
|
// as we can't rely on future network events to do it
|
|
mSession->ConnectPushedStream(this);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// It is now OK to assign a streamID that we are assured will
|
|
// be monotonically increasing amongst syn-streams on this
|
|
// session
|
|
mStreamID = mSession->RegisterStreamID(this);
|
|
MOZ_ASSERT(mStreamID & 1, "Spdy Stream Channel ID must be odd");
|
|
|
|
if (mStreamID >= 0x80000000) {
|
|
// streamID must fit in 31 bits. This is theoretically possible
|
|
// because stream ID assignment is asynchronous to stream creation
|
|
// because of the protocol requirement that the ID in syn-stream
|
|
// be monotonically increasing. In reality this is really not possible
|
|
// because new streams stop being added to a session with 0x10000000 / 2
|
|
// IDs still available and no race condition is going to bridge that gap,
|
|
// so we can be comfortable on just erroring out for correctness in that
|
|
// case.
|
|
LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// Now we need to convert the flat http headers into a set
|
|
// of SPDY headers.. writing to mTxInlineFrame{sz}
|
|
|
|
mTxInlineFrame[0] = SpdySession3::kFlag_Control;
|
|
mTxInlineFrame[1] = SpdySession3::kVersion;
|
|
mTxInlineFrame[2] = 0;
|
|
mTxInlineFrame[3] = SpdySession3::CONTROL_TYPE_SYN_STREAM;
|
|
// 4 to 7 are length and flags, we'll fill that in later
|
|
|
|
NetworkEndian::writeUint32(mTxInlineFrame + 8, mStreamID);
|
|
|
|
// this is the associated-to field, which is not used sending
|
|
// from the client in the http binding
|
|
memset(mTxInlineFrame + 12, 0, 4);
|
|
|
|
// Priority flags are the E0 mask of byte 16.
|
|
// 0 is highest priority, 7 is lowest.
|
|
// The other 5 bits of byte 16 are unused.
|
|
|
|
if (mPriority >= nsISupportsPriority::PRIORITY_LOWEST)
|
|
mTxInlineFrame[16] = 7 << 5;
|
|
else if (mPriority <= nsISupportsPriority::PRIORITY_HIGHEST)
|
|
mTxInlineFrame[16] = 0 << 5;
|
|
else {
|
|
// The priority mapping relies on the unfiltered ranged to be
|
|
// between -20 .. +20
|
|
PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST == 20);
|
|
PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_HIGHEST == -20);
|
|
|
|
// Add one to the priority so that values such as -10 and -11
|
|
// get different spdy priorities - this appears to be an important
|
|
// breaking line in the priorities content assigns to
|
|
// transactions.
|
|
uint8_t calculatedPriority = 3 + ((mPriority + 1) / 5);
|
|
MOZ_ASSERT (!(calculatedPriority & 0xf8),
|
|
"Calculated Priority Out Of Range");
|
|
mTxInlineFrame[16] = calculatedPriority << 5;
|
|
}
|
|
|
|
// The client cert "slot". Right now we don't send client certs
|
|
mTxInlineFrame[17] = 0;
|
|
|
|
nsCString versionHeader;
|
|
if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1)
|
|
versionHeader = NS_LITERAL_CSTRING("HTTP/1.1");
|
|
else
|
|
versionHeader = NS_LITERAL_CSTRING("HTTP/1.0");
|
|
|
|
// use mRequestHead() to get a sense of how big to make the hash,
|
|
// even though we are parsing the actual text stream because
|
|
// it is legit to append headers.
|
|
nsClassHashtable<nsCStringHashKey, nsCString>
|
|
hdrHash(1 + (mTransaction->RequestHead()->Headers().Count() * 2));
|
|
|
|
const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
|
|
|
|
// need to hash all the headers together to remove duplicates, special
|
|
// headers, etc..
|
|
|
|
int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
|
|
while (true) {
|
|
int32_t startIndex = crlfIndex + 2;
|
|
|
|
crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
|
|
if (crlfIndex == -1)
|
|
break;
|
|
|
|
int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
|
|
crlfIndex - startIndex);
|
|
if (colonIndex == -1)
|
|
break;
|
|
|
|
nsDependentCSubstring name = Substring(beginBuffer + startIndex,
|
|
beginBuffer + colonIndex);
|
|
// all header names are lower case in spdy
|
|
ToLowerCase(name);
|
|
|
|
// exclusions.. mostly from 3.2.1
|
|
if (name.EqualsLiteral("connection") ||
|
|
name.EqualsLiteral("keep-alive") ||
|
|
name.EqualsLiteral("host") ||
|
|
name.EqualsLiteral("accept-encoding") ||
|
|
name.EqualsLiteral("te") ||
|
|
name.EqualsLiteral("transfer-encoding"))
|
|
continue;
|
|
|
|
nsCString *val = hdrHash.Get(name);
|
|
if (!val) {
|
|
val = new nsCString();
|
|
hdrHash.Put(name, val);
|
|
}
|
|
|
|
int32_t valueIndex = colonIndex + 1;
|
|
while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
|
|
++valueIndex;
|
|
|
|
nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
|
|
beginBuffer + crlfIndex);
|
|
if (!val->IsEmpty())
|
|
val->Append(static_cast<char>(0));
|
|
val->Append(v);
|
|
|
|
if (name.EqualsLiteral("content-length")) {
|
|
int64_t len;
|
|
if (nsHttp::ParseInt64(val->get(), nullptr, &len))
|
|
mRequestBodyLenRemaining = len;
|
|
}
|
|
}
|
|
|
|
mTxInlineFrameUsed = 18;
|
|
|
|
// Do not naively log the request headers here beacuse they might
|
|
// contain auth. The http transaction already logs the sanitized request
|
|
// headers at this same level so it is not necessary to do so here.
|
|
|
|
const char *methodHeader = mTransaction->RequestHead()->Method().get();
|
|
LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, methodHeader));
|
|
|
|
// The header block length
|
|
uint16_t count = hdrHash.Count() + 4; /* :method, :path, :version, :host */
|
|
if (mTransaction->RequestHead()->IsConnect()) {
|
|
mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
|
|
} else {
|
|
++count; // :scheme used if not connect
|
|
}
|
|
CompressToFrame(count);
|
|
|
|
// :method, :path, :version comprise a HTTP/1 request line, so send those first
|
|
// to make life easy for any gateways
|
|
CompressToFrame(NS_LITERAL_CSTRING(":method"));
|
|
CompressToFrame(methodHeader, strlen(methodHeader));
|
|
|
|
CompressToFrame(NS_LITERAL_CSTRING(":path"));
|
|
if (!mTransaction->RequestHead()->IsConnect()) {
|
|
CompressToFrame(mTransaction->RequestHead()->RequestURI());
|
|
} else {
|
|
MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction());
|
|
mIsTunnel = true;
|
|
// Connect places host:port in :path. Don't use default port.
|
|
nsHttpConnectionInfo *ci = mTransaction->ConnectionInfo();
|
|
if (!ci) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
nsAutoCString route;
|
|
route = ci->GetHost();
|
|
route.AppendLiteral(":");
|
|
route.AppendInt(ci->Port());
|
|
CompressToFrame(route);
|
|
}
|
|
|
|
CompressToFrame(NS_LITERAL_CSTRING(":version"));
|
|
CompressToFrame(versionHeader);
|
|
|
|
CompressToFrame(NS_LITERAL_CSTRING(":host"));
|
|
CompressToFrame(hostHeader);
|
|
|
|
if (!mTransaction->RequestHead()->IsConnect()) {
|
|
// no :scheme with connect
|
|
CompressToFrame(NS_LITERAL_CSTRING(":scheme"));
|
|
CompressToFrame(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"));
|
|
}
|
|
|
|
hdrHash.Enumerate(hdrHashEnumerate, this);
|
|
CompressFlushFrame();
|
|
|
|
// 4 to 7 are length and flags, which we can now fill in
|
|
NetworkEndian::writeUint32(mTxInlineFrame + 1 * sizeof(uint32_t),
|
|
mTxInlineFrameUsed - 8);
|
|
|
|
MOZ_ASSERT(!mTxInlineFrame[4], "Size greater than 24 bits");
|
|
|
|
// Determine whether to put the fin bit on the syn stream frame or whether
|
|
// to wait for a data packet to put it on.
|
|
|
|
if (mTransaction->RequestHead()->IsGet() ||
|
|
mTransaction->RequestHead()->IsHead()) {
|
|
// for GET and HEAD place the fin bit right on the
|
|
// syn stream packet
|
|
|
|
mSentFinOnData = 1;
|
|
mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
|
|
}
|
|
else if (mTransaction->RequestHead()->IsPost() ||
|
|
mTransaction->RequestHead()->IsPut() ||
|
|
mTransaction->RequestHead()->IsConnect() ||
|
|
mTransaction->RequestHead()->IsOptions()) {
|
|
// place fin in a data frame even for 0 length messages, I've seen
|
|
// the google gateway be unhappy with fin-on-syn for 0 length POST
|
|
}
|
|
else if (!mRequestBodyLenRemaining) {
|
|
// for other HTTP extension methods, rely on the content-length
|
|
// to determine whether or not to put fin on syn
|
|
mSentFinOnData = 1;
|
|
mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
|
|
}
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameUsed - 18);
|
|
|
|
// The size of the input headers is approximate
|
|
uint32_t ratio =
|
|
(mTxInlineFrameUsed - 18) * 100 /
|
|
(11 + mTransaction->RequestHead()->RequestURI().Length() +
|
|
mFlatHttpRequestHeaders.Length());
|
|
|
|
Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
SpdyStream3::AdjustInitialWindow()
|
|
{
|
|
MOZ_ASSERT(mSession->PushAllowance() <= ASpdySession::kInitialRwin);
|
|
|
|
// The session initial_window is sized for serverpushed streams. When we
|
|
// generate a client pulled stream we want to adjust the initial window
|
|
// to a huge value in a pipeline with that SYN_STREAM.
|
|
|
|
// >0 even numbered IDs are pushed streams.
|
|
// odd numbered IDs are pulled streams.
|
|
// 0 is the sink for a pushed stream.
|
|
SpdyStream3 *stream = this;
|
|
if (!mStreamID) {
|
|
MOZ_ASSERT(mPushSource);
|
|
if (!mPushSource)
|
|
return;
|
|
stream = mPushSource;
|
|
MOZ_ASSERT(stream->mStreamID);
|
|
MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
|
|
|
|
// If the pushed stream has sent a FIN, there is no reason to update
|
|
// the window
|
|
if (stream->RecvdFin())
|
|
return;
|
|
}
|
|
|
|
// For server pushes we also want to include in the ack any data that has been
|
|
// buffered but unacknowledged.
|
|
|
|
// mLocalUnacked is technically 64 bits, but because it can never grow larger than
|
|
// our window size (which is closer to 29bits max) we know it fits comfortably in 32.
|
|
// However we don't really enforce that, and track it as a 64 so that broken senders
|
|
// can still interoperate. That means we have to be careful with this calculation.
|
|
uint64_t toack64 = (ASpdySession::kInitialRwin - mSession->PushAllowance()) +
|
|
stream->mLocalUnacked;
|
|
stream->mLocalUnacked = 0;
|
|
if (toack64 > 0x7fffffff) {
|
|
stream->mLocalUnacked = toack64 - 0x7fffffff;
|
|
toack64 = 0x7fffffff;
|
|
}
|
|
uint32_t toack = static_cast<uint32_t>(toack64);
|
|
if (!toack)
|
|
return;
|
|
toack = PR_htonl(toack);
|
|
|
|
EnsureBuffer(mTxInlineFrame, mTxInlineFrameUsed + 16,
|
|
mTxInlineFrameUsed, mTxInlineFrameSize);
|
|
|
|
unsigned char *packet = mTxInlineFrame.get() + mTxInlineFrameUsed;
|
|
mTxInlineFrameUsed += 16;
|
|
|
|
memset(packet, 0, 8);
|
|
packet[0] = SpdySession3::kFlag_Control;
|
|
packet[1] = SpdySession3::kVersion;
|
|
packet[3] = SpdySession3::CONTROL_TYPE_WINDOW_UPDATE;
|
|
packet[7] = 8; // 8 data bytes after 8 byte header
|
|
|
|
uint32_t id = PR_htonl(stream->mStreamID);
|
|
memcpy(packet + 8, &id, 4);
|
|
memcpy(packet + 12, &toack, 4);
|
|
|
|
stream->mLocalWindow += PR_ntohl(toack);
|
|
LOG3(("AdjustInitialwindow %p 0x%X %u\n",
|
|
this, stream->mStreamID, PR_ntohl(toack)));
|
|
}
|
|
|
|
void
|
|
SpdyStream3::UpdateTransportReadEvents(uint32_t count)
|
|
{
|
|
mTotalRead += count;
|
|
if (!mSocketTransport) {
|
|
return;
|
|
}
|
|
|
|
mTransaction->OnTransportStatus(mSocketTransport,
|
|
NS_NET_STATUS_RECEIVING_FROM,
|
|
mTotalRead);
|
|
}
|
|
|
|
void
|
|
SpdyStream3::UpdateTransportSendEvents(uint32_t count)
|
|
{
|
|
mTotalSent += count;
|
|
|
|
// normally on non-windows platform we use TCP autotuning for
|
|
// the socket buffers, and this works well (managing enough
|
|
// buffers for BDP while conserving memory) for HTTP even when
|
|
// it creates really deep queues. However this 'buffer bloat' is
|
|
// a problem for spdy because it ruins the low latency properties
|
|
// necessary for PING and cancel to work meaningfully.
|
|
//
|
|
// If this stream represents a large upload, disable autotuning for
|
|
// the session and cap the send buffers by default at 128KB.
|
|
// (10Mbit/sec @ 100ms)
|
|
//
|
|
uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
|
|
if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
|
|
mSetTCPSocketBuffer = 1;
|
|
mSocketTransport->SetSendBufferSize(bufferSize);
|
|
}
|
|
|
|
if (mUpstreamState != SENDING_FIN_STREAM)
|
|
mTransaction->OnTransportStatus(mSocketTransport,
|
|
NS_NET_STATUS_SENDING_TO,
|
|
mTotalSent);
|
|
|
|
if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
|
|
mSentWaitingFor = 1;
|
|
mTransaction->OnTransportStatus(mSocketTransport,
|
|
NS_NET_STATUS_WAITING_FOR,
|
|
0);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
SpdyStream3::TransmitFrame(const char *buf,
|
|
uint32_t *countUsed,
|
|
bool forceCommitment)
|
|
{
|
|
// If TransmitFrame returns SUCCESS than all the data is sent (or at least
|
|
// buffered at the session level), if it returns WOULD_BLOCK then none of
|
|
// the data is sent.
|
|
|
|
// You can call this function with no data and no out parameter in order to
|
|
// flush internal buffers that were previously blocked on writing. You can
|
|
// of course feed new data to it as well.
|
|
|
|
LOG3(("SpdyStream3::TransmitFrame %p inline=%d stream=%d",
|
|
this, mTxInlineFrameUsed, mTxStreamFrameSize));
|
|
if (countUsed)
|
|
*countUsed = 0;
|
|
|
|
if (!mTxInlineFrameUsed) {
|
|
MOZ_ASSERT(!buf);
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
|
|
MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
|
|
MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
|
|
"TransmitFrame arguments inconsistent");
|
|
|
|
uint32_t transmittedCount;
|
|
nsresult rv;
|
|
|
|
// In the (relatively common) event that we have a small amount of data
|
|
// split between the inlineframe and the streamframe, then move the stream
|
|
// data into the inlineframe via copy in order to coalesce into one write.
|
|
// Given the interaction with ssl this is worth the small copy cost.
|
|
if (mTxStreamFrameSize && mTxInlineFrameUsed &&
|
|
mTxStreamFrameSize < SpdySession3::kDefaultBufferSize &&
|
|
mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
|
|
LOG3(("Coalesce Transmit"));
|
|
memcpy (mTxInlineFrame + mTxInlineFrameUsed,
|
|
buf, mTxStreamFrameSize);
|
|
if (countUsed)
|
|
*countUsed += mTxStreamFrameSize;
|
|
mTxInlineFrameUsed += mTxStreamFrameSize;
|
|
mTxStreamFrameSize = 0;
|
|
}
|
|
|
|
rv =
|
|
mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
|
|
forceCommitment);
|
|
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
|
|
mSession->TransactionHasDataToWrite(this);
|
|
}
|
|
if (NS_FAILED(rv)) // this will include WOULD_BLOCK
|
|
return rv;
|
|
|
|
// This function calls mSegmentReader->OnReadSegment to report the actual SPDY
|
|
// bytes through to the SpdySession3 and then the HttpConnection which calls
|
|
// the socket write function. It will accept all of the inline and stream
|
|
// data because of the above 'commitment' even if it has to buffer
|
|
|
|
rv = mSegmentReader->OnReadSegment(reinterpret_cast<char*>(mTxInlineFrame.get()),
|
|
mTxInlineFrameUsed,
|
|
&transmittedCount);
|
|
LOG3(("SpdyStream3::TransmitFrame for inline session=%p "
|
|
"stream=%p result %x len=%d",
|
|
mSession, this, rv, transmittedCount));
|
|
|
|
MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
|
|
"inconsistent inline commitment result");
|
|
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
|
|
"inconsistent inline commitment count");
|
|
|
|
SpdySession3::LogIO(mSession, this, "Writing from Inline Buffer",
|
|
reinterpret_cast<char*>(mTxInlineFrame.get()),
|
|
transmittedCount);
|
|
|
|
if (mTxStreamFrameSize) {
|
|
if (!buf) {
|
|
// this cannot happen
|
|
MOZ_ASSERT(false, "Stream transmit with null buf argument to "
|
|
"TransmitFrame()");
|
|
LOG(("Stream transmit with null buf argument to TransmitFrame()\n"));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
rv = mSegmentReader->OnReadSegment(buf, mTxStreamFrameSize,
|
|
&transmittedCount);
|
|
|
|
LOG3(("SpdyStream3::TransmitFrame for regular session=%p "
|
|
"stream=%p result %x len=%d",
|
|
mSession, this, rv, transmittedCount));
|
|
|
|
MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
|
|
"inconsistent stream commitment result");
|
|
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
|
|
"inconsistent stream commitment count");
|
|
|
|
SpdySession3::LogIO(mSession, this, "Writing from Transaction Buffer",
|
|
buf, transmittedCount);
|
|
|
|
*countUsed += mTxStreamFrameSize;
|
|
}
|
|
|
|
// calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
|
|
UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
|
|
|
|
mTxInlineFrameUsed = 0;
|
|
mTxStreamFrameSize = 0;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
SpdyStream3::ChangeState(enum stateType newState)
|
|
{
|
|
LOG3(("SpdyStream3::ChangeState() %p from %X to %X",
|
|
this, mUpstreamState, newState));
|
|
mUpstreamState = newState;
|
|
return;
|
|
}
|
|
|
|
void
|
|
SpdyStream3::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
|
|
{
|
|
LOG3(("SpdyStream3::GenerateDataFrameHeader %p len=%d last=%d",
|
|
this, dataLength, lastFrame));
|
|
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
|
|
MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
|
|
MOZ_ASSERT(!(dataLength & 0xff000000), "datalength > 24 bits");
|
|
|
|
NetworkEndian::writeUint32(mTxInlineFrame, mStreamID);
|
|
NetworkEndian::writeUint32(mTxInlineFrame + 1 * sizeof(uint32_t), dataLength);
|
|
|
|
MOZ_ASSERT(!(mTxInlineFrame[0] & 0x80), "control bit set unexpectedly");
|
|
MOZ_ASSERT(!mTxInlineFrame[4], "flag bits set unexpectedly");
|
|
|
|
mTxInlineFrameUsed = 8;
|
|
mTxStreamFrameSize = dataLength;
|
|
|
|
if (lastFrame) {
|
|
mTxInlineFrame[4] |= SpdySession3::kFlag_Data_FIN;
|
|
if (dataLength)
|
|
mSentFinOnData = 1;
|
|
}
|
|
}
|
|
|
|
void
|
|
SpdyStream3::CompressToFrame(const nsACString &str)
|
|
{
|
|
CompressToFrame(str.BeginReading(), str.Length());
|
|
}
|
|
|
|
void
|
|
SpdyStream3::CompressToFrame(const nsACString *str)
|
|
{
|
|
CompressToFrame(str->BeginReading(), str->Length());
|
|
}
|
|
|
|
// Dictionary taken from
|
|
// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
|
|
|
|
const unsigned char SpdyStream3::kDictionary[] = {
|
|
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i
|
|
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h
|
|
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p
|
|
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p
|
|
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e
|
|
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - -
|
|
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - -
|
|
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t -
|
|
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
|
|
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e
|
|
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c
|
|
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o
|
|
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - -
|
|
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l
|
|
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e -
|
|
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p
|
|
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s
|
|
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e -
|
|
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w
|
|
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h
|
|
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o
|
|
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c
|
|
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r
|
|
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o
|
|
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n
|
|
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
|
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e
|
|
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
|
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o
|
|
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - -
|
|
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t -
|
|
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e
|
|
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t
|
|
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g
|
|
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o
|
|
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o
|
|
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - -
|
|
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
|
|
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - -
|
|
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t
|
|
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - -
|
|
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n
|
|
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - -
|
|
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - -
|
|
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - -
|
|
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t
|
|
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i
|
|
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f
|
|
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h
|
|
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i
|
|
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h -
|
|
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o
|
|
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s
|
|
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - -
|
|
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e -
|
|
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - -
|
|
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g
|
|
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f -
|
|
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i
|
|
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e
|
|
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t
|
|
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e
|
|
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c
|
|
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - -
|
|
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r
|
|
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - -
|
|
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a -
|
|
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y
|
|
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t
|
|
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - -
|
|
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a
|
|
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a
|
|
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - -
|
|
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - -
|
|
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r
|
|
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r
|
|
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r -
|
|
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e
|
|
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e -
|
|
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l
|
|
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r
|
|
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e
|
|
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g -
|
|
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a
|
|
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s
|
|
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t
|
|
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y
|
|
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a -
|
|
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i
|
|
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w
|
|
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n
|
|
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - -
|
|
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d
|
|
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t -
|
|
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u
|
|
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0
|
|
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v
|
|
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - -
|
|
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1
|
|
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r
|
|
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b
|
|
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s
|
|
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i
|
|
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e
|
|
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e -
|
|
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i
|
|
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2
|
|
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5
|
|
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0
|
|
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3
|
|
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7
|
|
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0
|
|
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4
|
|
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1
|
|
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1
|
|
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4
|
|
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4
|
|
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N
|
|
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o
|
|
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e
|
|
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a
|
|
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 -
|
|
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e
|
|
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o
|
|
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m
|
|
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4
|
|
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R
|
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0
|
|
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h
|
|
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0
|
|
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d
|
|
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N
|
|
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d
|
|
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e
|
|
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r
|
|
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o
|
|
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t
|
|
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e
|
|
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 -
|
|
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e -
|
|
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a
|
|
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F
|
|
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A
|
|
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J
|
|
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A
|
|
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t -
|
|
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v -
|
|
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0
|
|
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n
|
|
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W
|
|
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u -
|
|
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a
|
|
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - -
|
|
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k
|
|
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t -
|
|
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a
|
|
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i
|
|
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g
|
|
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g
|
|
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i
|
|
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
|
|
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i
|
|
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x
|
|
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l
|
|
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l
|
|
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t
|
|
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r
|
|
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l
|
|
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t
|
|
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e
|
|
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e
|
|
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d
|
|
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e
|
|
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c
|
|
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i
|
|
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 -
|
|
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - -
|
|
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 -
|
|
};
|
|
|
|
// This can be called N times.. 1 for syn_reply and 0->N for headers
|
|
nsresult
|
|
SpdyStream3::Uncompress(z_stream *context,
|
|
char *blockStart,
|
|
uint32_t blockLen)
|
|
{
|
|
mDecompressedBytes += blockLen;
|
|
|
|
context->avail_in = blockLen;
|
|
context->next_in = reinterpret_cast<unsigned char *>(blockStart);
|
|
bool triedDictionary = false;
|
|
|
|
do {
|
|
context->next_out =
|
|
reinterpret_cast<unsigned char *>(mDecompressBuffer.get()) +
|
|
mDecompressBufferUsed;
|
|
context->avail_out = mDecompressBufferSize - mDecompressBufferUsed;
|
|
int zlib_rv = inflate(context, Z_NO_FLUSH);
|
|
|
|
if (zlib_rv == Z_NEED_DICT) {
|
|
if (triedDictionary) {
|
|
LOG3(("SpdySession3::Uncompress %p Dictionary Error\n", this));
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
triedDictionary = true;
|
|
inflateSetDictionary(context, kDictionary, sizeof(kDictionary));
|
|
}
|
|
|
|
if (zlib_rv == Z_DATA_ERROR)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
if (zlib_rv == Z_MEM_ERROR)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// zlib's inflate() decreases context->avail_out by the amount it places
|
|
// in the output buffer
|
|
|
|
mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed -
|
|
context->avail_out;
|
|
|
|
// When there is no more output room, but input still available then
|
|
// increase the output space
|
|
if (zlib_rv == Z_OK &&
|
|
!context->avail_out && context->avail_in) {
|
|
LOG3(("SpdyStream3::Uncompress %p Large Headers - so far %d",
|
|
this, mDecompressBufferSize));
|
|
EnsureBuffer(mDecompressBuffer, mDecompressBufferSize + 4096,
|
|
mDecompressBufferUsed, mDecompressBufferSize);
|
|
}
|
|
}
|
|
while (context->avail_in);
|
|
return NS_OK;
|
|
}
|
|
|
|
// mDecompressBuffer contains 0 to N uncompressed Name/Value Header blocks
|
|
nsresult
|
|
SpdyStream3::FindHeader(nsCString name,
|
|
nsDependentCSubstring &value)
|
|
{
|
|
const unsigned char *nvpair = reinterpret_cast<unsigned char *>
|
|
(mDecompressBuffer.get()) + 4;
|
|
const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
|
|
(mDecompressBuffer.get()) + mDecompressBufferUsed;
|
|
if (lastHeaderByte < nvpair)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
do {
|
|
uint32_t numPairs = NetworkEndian::readUint32(nvpair - 1 * sizeof(uint32_t));
|
|
|
|
for (uint32_t index = 0; index < numPairs; ++index) {
|
|
if (lastHeaderByte < nvpair + 4)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) +
|
|
(nvpair[2] << 8) + nvpair[3];
|
|
if (lastHeaderByte < nvpair + 4 + nameLen)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
nsDependentCSubstring nameString =
|
|
Substring(reinterpret_cast<const char *>(nvpair) + 4,
|
|
reinterpret_cast<const char *>(nvpair) + 4 + nameLen);
|
|
if (lastHeaderByte < nvpair + 8 + nameLen)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) +
|
|
(nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen];
|
|
if (lastHeaderByte < nvpair + 8 + nameLen + valueLen)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
if (nameString.Equals(name)) {
|
|
value.Assign(((char *)nvpair) + 8 + nameLen, valueLen);
|
|
return NS_OK;
|
|
}
|
|
|
|
// that pair didn't match - try the next one in this block
|
|
nvpair += 8 + nameLen + valueLen;
|
|
}
|
|
|
|
// move to the next name/value header block (if there is one) - the
|
|
// first pair is offset 4 bytes into it
|
|
nvpair += 4;
|
|
} while (lastHeaderByte >= nvpair);
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// ConvertHeaders is used to convert the response headers
|
|
// in a syn_reply or in 0..N headers frames that follow it into
|
|
// HTTP/1 format
|
|
nsresult
|
|
SpdyStream3::ConvertHeaders(nsACString &aHeadersOut)
|
|
{
|
|
// :status and :version are required.
|
|
nsDependentCSubstring status, version;
|
|
nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"),
|
|
status);
|
|
if (NS_FAILED(rv))
|
|
return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv;
|
|
|
|
rv = FindHeader(NS_LITERAL_CSTRING(":version"),
|
|
version);
|
|
if (NS_FAILED(rv))
|
|
return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv;
|
|
|
|
if (mDecompressedBytes && mDecompressBufferUsed) {
|
|
Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, mDecompressedBytes);
|
|
uint32_t ratio =
|
|
mDecompressedBytes * 100 / mDecompressBufferUsed;
|
|
Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
|
|
}
|
|
|
|
aHeadersOut.Truncate();
|
|
aHeadersOut.SetCapacity(mDecompressBufferUsed + 64);
|
|
|
|
// Connection, Keep-Alive and chunked transfer encodings are to be
|
|
// removed.
|
|
|
|
// Content-Length is 'advisory'.. we will not strip it because it can
|
|
// create UI feedback.
|
|
|
|
aHeadersOut.Append(version);
|
|
aHeadersOut.Append(' ');
|
|
aHeadersOut.Append(status);
|
|
aHeadersOut.AppendLiteral("\r\n");
|
|
|
|
const unsigned char *nvpair = reinterpret_cast<unsigned char *>
|
|
(mDecompressBuffer.get()) + 4;
|
|
const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
|
|
(mDecompressBuffer.get()) + mDecompressBufferUsed;
|
|
if (lastHeaderByte < nvpair)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
do {
|
|
uint32_t numPairs = NetworkEndian::readUint32(nvpair - 1 * sizeof(uint32_t));
|
|
|
|
for (uint32_t index = 0; index < numPairs; ++index) {
|
|
if (lastHeaderByte < nvpair + 4)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) +
|
|
(nvpair[2] << 8) + nvpair[3];
|
|
if (lastHeaderByte < nvpair + 4 + nameLen)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
nsDependentCSubstring nameString =
|
|
Substring(reinterpret_cast<const char *>(nvpair) + 4,
|
|
reinterpret_cast<const char *>(nvpair) + 4 + nameLen);
|
|
|
|
if (lastHeaderByte < nvpair + 8 + nameLen)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
// Look for illegal characters in the nameString.
|
|
// This includes upper case characters and nulls (as they will
|
|
// break the fixup-nulls-in-value-string algorithm)
|
|
// Look for upper case characters in the name. They are illegal.
|
|
for (char *cPtr = nameString.BeginWriting();
|
|
cPtr && cPtr < nameString.EndWriting();
|
|
++cPtr) {
|
|
if (*cPtr <= 'Z' && *cPtr >= 'A') {
|
|
nsCString toLog(nameString);
|
|
|
|
LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p "
|
|
"upper case response header found. [%s]\n",
|
|
mSession, this, toLog.get()));
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
// check for null characters
|
|
if (*cPtr == '\0')
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
// HTTP Chunked responses are not legal over spdy. We do not need
|
|
// to look for chunked specifically because it is the only HTTP
|
|
// allowed default encoding and we did not negotiate further encodings
|
|
// via TE
|
|
if (nameString.EqualsLiteral("transfer-encoding")) {
|
|
LOG3(("SpdyStream3::ConvertHeaders session=%p stream=%p "
|
|
"transfer-encoding found. Chunked is invalid and no TE sent.",
|
|
mSession, this));
|
|
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
uint32_t valueLen =
|
|
(nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) +
|
|
(nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen];
|
|
|
|
if (lastHeaderByte < nvpair + 8 + nameLen + valueLen)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
// spdy transport level headers shouldn't be gatewayed into http/1
|
|
if (!nameString.IsEmpty() && nameString[0] != ':' &&
|
|
!nameString.EqualsLiteral("connection") &&
|
|
!nameString.EqualsLiteral("keep-alive")) {
|
|
nsDependentCSubstring valueString =
|
|
Substring(reinterpret_cast<const char *>(nvpair) + 8 + nameLen,
|
|
reinterpret_cast<const char *>(nvpair) + 8 + nameLen +
|
|
valueLen);
|
|
|
|
aHeadersOut.Append(nameString);
|
|
aHeadersOut.AppendLiteral(": ");
|
|
|
|
// expand nullptr bytes in the value string
|
|
for (char *cPtr = valueString.BeginWriting();
|
|
cPtr && cPtr < valueString.EndWriting();
|
|
++cPtr) {
|
|
if (*cPtr != 0) {
|
|
aHeadersOut.Append(*cPtr);
|
|
continue;
|
|
}
|
|
|
|
// NULLs are really "\r\nhdr: "
|
|
aHeadersOut.AppendLiteral("\r\n");
|
|
aHeadersOut.Append(nameString);
|
|
aHeadersOut.AppendLiteral(": ");
|
|
}
|
|
|
|
aHeadersOut.AppendLiteral("\r\n");
|
|
}
|
|
// move to the next name/value pair in this block
|
|
nvpair += 8 + nameLen + valueLen;
|
|
}
|
|
|
|
// move to the next name/value header block (if there is one) - the
|
|
// first pair is offset 4 bytes into it
|
|
nvpair += 4;
|
|
} while (lastHeaderByte >= nvpair);
|
|
|
|
aHeadersOut.AppendLiteral("X-Firefox-Spdy: 3\r\n\r\n");
|
|
LOG (("decoded response headers are:\n%s",
|
|
aHeadersOut.BeginReading()));
|
|
|
|
// The spdy formatted buffer isnt needed anymore - free it up
|
|
mDecompressBuffer = nullptr;
|
|
mDecompressBufferSize = 0;
|
|
mDecompressBufferUsed = 0;
|
|
|
|
if (mIsTunnel) {
|
|
aHeadersOut.Truncate();
|
|
LOG(("SpdyStream3::ConvertHeaders %p 0x%X headers removed for tunnel\n",
|
|
this, mStreamID));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
SpdyStream3::ExecuteCompress(uint32_t flushMode)
|
|
{
|
|
// Expect mZlib->avail_in and mZlib->next_in to be set.
|
|
// Append the compressed version of next_in to mTxInlineFrame
|
|
|
|
do
|
|
{
|
|
uint32_t avail = mTxInlineFrameSize - mTxInlineFrameUsed;
|
|
if (avail < 1) {
|
|
EnsureBuffer(mTxInlineFrame, mTxInlineFrameSize + 2000,
|
|
mTxInlineFrameUsed, mTxInlineFrameSize);
|
|
avail = mTxInlineFrameSize - mTxInlineFrameUsed;
|
|
}
|
|
|
|
mZlib->next_out = mTxInlineFrame + mTxInlineFrameUsed;
|
|
mZlib->avail_out = avail;
|
|
deflate(mZlib, flushMode);
|
|
mTxInlineFrameUsed += avail - mZlib->avail_out;
|
|
} while (mZlib->avail_in > 0 || !mZlib->avail_out);
|
|
}
|
|
|
|
void
|
|
SpdyStream3::CompressToFrame(uint32_t data)
|
|
{
|
|
// convert the data to 4 byte network byte order and write that
|
|
// to the compressed stream
|
|
unsigned char databuf[sizeof(data)];
|
|
NetworkEndian::writeUint32(databuf, data);
|
|
|
|
mZlib->next_in = databuf;
|
|
mZlib->avail_in = sizeof(databuf);
|
|
ExecuteCompress(Z_NO_FLUSH);
|
|
}
|
|
|
|
|
|
void
|
|
SpdyStream3::CompressToFrame(const char *data, uint32_t len)
|
|
{
|
|
// Format calls for a network ordered 32 bit length
|
|
// followed by the utf8 string
|
|
|
|
unsigned char lenbuf[sizeof(len)];
|
|
NetworkEndian::writeUint32(lenbuf, len);
|
|
|
|
// write out the length
|
|
mZlib->next_in = lenbuf;
|
|
mZlib->avail_in = sizeof(lenbuf);
|
|
ExecuteCompress(Z_NO_FLUSH);
|
|
|
|
// write out the data
|
|
mZlib->next_in = (unsigned char *)data;
|
|
mZlib->avail_in = len;
|
|
ExecuteCompress(Z_NO_FLUSH);
|
|
}
|
|
|
|
void
|
|
SpdyStream3::CompressFlushFrame()
|
|
{
|
|
mZlib->next_in = (unsigned char *) "";
|
|
mZlib->avail_in = 0;
|
|
ExecuteCompress(Z_SYNC_FLUSH);
|
|
}
|
|
|
|
bool
|
|
SpdyStream3::GetFullyOpen()
|
|
{
|
|
return mFullyOpen;
|
|
}
|
|
|
|
nsresult
|
|
SpdyStream3::SetFullyOpen()
|
|
{
|
|
MOZ_ASSERT(!mFullyOpen);
|
|
mFullyOpen = 1;
|
|
if (mIsTunnel) {
|
|
nsDependentCSubstring statusSubstring;
|
|
nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"),
|
|
statusSubstring);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCString status(statusSubstring);
|
|
nsresult errcode;
|
|
|
|
if (status.ToInteger(&errcode) != 200) {
|
|
LOG3(("SpdyStream3::SetFullyOpen %p Tunnel not 200", this));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
LOG3(("SpdyStream3::SetFullyOpen %p Tunnel 200 OK", this));
|
|
}
|
|
|
|
MapStreamToHttpConnection();
|
|
ClearTransactionsBlockedOnTunnel();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
SpdyStream3::Close(nsresult reason)
|
|
{
|
|
mTransaction->Close(reason);
|
|
}
|
|
|
|
void
|
|
SpdyStream3::UpdateRemoteWindow(int32_t delta)
|
|
{
|
|
int64_t oldRemoteWindow = mRemoteWindow;
|
|
mRemoteWindow += delta;
|
|
if (oldRemoteWindow <= 0 && mRemoteWindow > 0) {
|
|
// the window has been opened :)
|
|
mSession->TransactionHasDataToWrite(this);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsAHttpSegmentReader
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
SpdyStream3::OnReadSegment(const char *buf,
|
|
uint32_t count,
|
|
uint32_t *countRead)
|
|
{
|
|
LOG3(("SpdyStream3::OnReadSegment %p count=%d state=%x",
|
|
this, count, mUpstreamState));
|
|
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
|
|
|
|
nsresult rv = NS_ERROR_UNEXPECTED;
|
|
uint32_t dataLength;
|
|
|
|
switch (mUpstreamState) {
|
|
case GENERATING_SYN_STREAM:
|
|
// The buffer is the HTTP request stream, including at least part of the
|
|
// HTTP request header. This state's job is to build a SYN_STREAM frame
|
|
// from the header information. count is the number of http bytes available
|
|
// (which may include more than the header), and in countRead we return
|
|
// the number of those bytes that we consume (i.e. the portion that are
|
|
// header bytes)
|
|
|
|
rv = ParseHttpRequestHeaders(buf, count, countRead);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
|
|
this, *countRead, count, mSynFrameComplete));
|
|
if (mSynFrameComplete) {
|
|
AdjustInitialWindow();
|
|
rv = TransmitFrame(nullptr, nullptr, true);
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
|
|
// this can't happen
|
|
MOZ_ASSERT(false, "Transmit Frame SYN_FRAME must at least buffer data");
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
ChangeState(GENERATING_REQUEST_BODY);
|
|
break;
|
|
}
|
|
MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
|
|
break;
|
|
|
|
case GENERATING_REQUEST_BODY:
|
|
if (mRemoteWindow <= 0) {
|
|
*countRead = 0;
|
|
LOG3(("SpdyStream3 this=%p, id 0x%X request body suspended because "
|
|
"remote window is %d.\n", this, mStreamID, mRemoteWindow));
|
|
mBlockedOnRwin = true;
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
}
|
|
mBlockedOnRwin = false;
|
|
|
|
dataLength = std::min(count, mChunkSize);
|
|
|
|
if (dataLength > mRemoteWindow)
|
|
dataLength = static_cast<uint32_t>(mRemoteWindow);
|
|
|
|
LOG3(("SpdyStream3 this=%p id 0x%X remote window is %d. Chunk is %d\n",
|
|
this, mStreamID, mRemoteWindow, dataLength));
|
|
mRemoteWindow -= dataLength;
|
|
|
|
LOG3(("SpdyStream3 %p id %x request len remaining %u, "
|
|
"count avail %u, chunk used %u",
|
|
this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
|
|
if (!dataLength && mRequestBodyLenRemaining) {
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
}
|
|
if (dataLength > mRequestBodyLenRemaining) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
mRequestBodyLenRemaining -= dataLength;
|
|
GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
|
|
ChangeState(SENDING_REQUEST_BODY);
|
|
// NO BREAK
|
|
|
|
case SENDING_REQUEST_BODY:
|
|
MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
|
|
rv = TransmitFrame(buf, countRead, false);
|
|
MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
|
|
"Transmit Frame should be all or nothing");
|
|
|
|
LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
|
|
"Header is %d Body is %d.",
|
|
rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
|
|
|
|
// normalize a partial write with a WOULD_BLOCK into just a partial write
|
|
// as some code will take WOULD_BLOCK to mean an error with nothing
|
|
// written (e.g. nsHttpTransaction::ReadRequestSegment()
|
|
if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
|
|
rv = NS_OK;
|
|
|
|
// If that frame was all sent, look for another one
|
|
if (!mTxInlineFrameUsed)
|
|
ChangeState(GENERATING_REQUEST_BODY);
|
|
break;
|
|
|
|
case SENDING_FIN_STREAM:
|
|
MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT(false, "SpdyStream3::OnReadSegment non-write state");
|
|
break;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsAHttpSegmentWriter
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
SpdyStream3::OnWriteSegment(char *buf,
|
|
uint32_t count,
|
|
uint32_t *countWritten)
|
|
{
|
|
LOG3(("SpdyStream3::OnWriteSegment %p count=%d state=%x 0x%X\n",
|
|
this, count, mUpstreamState, mStreamID));
|
|
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
MOZ_ASSERT(mSegmentWriter);
|
|
|
|
if (!mPushSource)
|
|
return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
|
|
|
|
nsresult rv;
|
|
rv = mPushSource->GetBufferedData(buf, count, countWritten);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
mSession->ConnectPushedStream(this);
|
|
return NS_OK;
|
|
}
|
|
|
|
/// connect tunnels
|
|
|
|
void
|
|
SpdyStream3::ClearTransactionsBlockedOnTunnel()
|
|
{
|
|
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
|
|
|
|
if (!mIsTunnel) {
|
|
return;
|
|
}
|
|
gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
|
|
}
|
|
|
|
void
|
|
SpdyStream3::MapStreamToHttpConnection()
|
|
{
|
|
nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
|
|
MOZ_ASSERT(qiTrans);
|
|
qiTrans->MapStreamToHttpConnection(mSocketTransport,
|
|
mTransaction->ConnectionInfo());
|
|
}
|
|
|
|
} // namespace mozilla::net
|
|
} // namespace mozilla
|