bug 1130874 - update h2 alternate service extension to draft-06 r=hurley

This commit is contained in:
Patrick McManus 2015-02-06 17:45:05 -05:00
parent 9ec922dcb3
commit bfa57d2872
7 changed files with 203 additions and 183 deletions

View File

@ -7,6 +7,7 @@
#include "HttpLog.h"
#include "AlternateServices.h"
#include "nsEscape.h"
#include "nsHttpConnectionInfo.h"
#include "nsHttpHandler.h"
#include "nsThreadUtils.h"
@ -18,6 +19,81 @@
namespace mozilla {
namespace net {
void
AltSvcMapping::ProcessHeader(const nsCString &buf, const nsCString &originScheme,
const nsCString &originHost, int32_t originPort,
const nsACString &username, bool privateBrowsing,
nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
uint32_t caps)
{
MOZ_ASSERT(NS_IsMainThread());
LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
if (!callbacks) {
return;
}
bool isHttp = originScheme.Equals(NS_LITERAL_CSTRING("http"));
if (isHttp && !gHttpHandler->AllowAltSvcOE()) {
LOG(("Alt-Svc Response Header for http:// origin but OE disabled\n"));
return;
}
LOG(("Alt-Svc Response Header %s\n", buf.get()));
ParsedHeaderValueListList parsedAltSvc(buf);
for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
uint32_t maxage = 86400; // default
nsAutoCString hostname; // Always empty in the header form
nsAutoCString npnToken;
int32_t portno = originPort;
for (uint32_t pairIndex = 0;
pairIndex < parsedAltSvc.mValues[index].mValues.Length();
++pairIndex) {
nsDependentCSubstring &currentName =
parsedAltSvc.mValues[index].mValues[pairIndex].mName;
nsDependentCSubstring &currentValue =
parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
if (!pairIndex) {
// h2=:443
npnToken = currentName;
int32_t colonIndex = currentValue.FindChar(':');
if (colonIndex >= 0) {
portno =
atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
} else {
colonIndex = 0;
}
hostname.Assign(currentValue.BeginReading(), colonIndex);
} else if (currentName.Equals(NS_LITERAL_CSTRING("ma"))) {
maxage = atoi(PromiseFlatCString(currentValue).get());
break;
}
}
// unescape modifies a c string in place, so afterwards
// update nsCString length
nsUnescape(npnToken.BeginWriting());
npnToken.SetLength(strlen(npnToken.BeginReading()));
uint32_t spdyIndex;
SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
spdyInfo->ProtocolEnabled(spdyIndex))) {
LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
continue;
}
nsRefPtr<AltSvcMapping> mapping = new AltSvcMapping(originScheme,
originHost, originPort,
username, privateBrowsing,
NowInSeconds() + maxage,
hostname, portno, npnToken);
gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps);
}
}
AltSvcMapping::AltSvcMapping(const nsACString &originScheme,
const nsACString &originHost,
int32_t originPort,

View File

@ -7,7 +7,7 @@
/*
Alt-Svc allows separation of transport routing from the origin host without
using a proxy. See https://httpwg.github.io/http-extensions/alt-svc.html and
https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04
https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
Nice To Have Future Enhancements::
* flush on network change event when we have an indicator
@ -49,6 +49,12 @@ public:
int32_t alternatePort,
const nsACString &npnToken);
static void ProcessHeader(const nsCString &buf, const nsCString &originScheme,
const nsCString &originHost, int32_t originPort,
const nsACString &username, bool privateBrowsing,
nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
uint32_t caps);
const nsCString &AlternateHost() const { return mAlternateHost; }
const nsCString &OriginHost() const { return mOriginHost; }
const nsCString &HashKey() const { return mHashKey; }

View File

@ -2023,15 +2023,11 @@ Http2Session::RecvContinuation(Http2Session *self)
class UpdateAltSvcEvent : public nsRunnable
{
public:
UpdateAltSvcEvent(const nsCString &host, const uint16_t port,
const nsCString &npnToken, const uint32_t expires,
const nsCString &aOrigin,
nsHttpConnectionInfo *aCI,
nsIInterfaceRequestor *callbacks)
: mHost(host)
, mPort(port)
, mNPNToken(npnToken)
, mExpires(expires)
UpdateAltSvcEvent(const nsCString &header,
const nsCString &aOrigin,
nsHttpConnectionInfo *aCI,
nsIInterfaceRequestor *callbacks)
: mHeader(header)
, mOrigin(aOrigin)
, mCI(aCI)
, mCallbacks(callbacks)
@ -2056,36 +2052,22 @@ public:
uri->GetHost(originHost);
uri->GetPort(&originPort);
const char *username = mCI->Username();
const bool privateBrowsing = mCI->GetPrivate();
LOG(("UpdateAltSvcEvent location=%s:%u protocol=%s expires=%u "
"origin=%s://%s:%u user=%s private=%d", mHost.get(), mPort,
mNPNToken.get(), mExpires, originScheme.get(), originHost.get(),
originPort, username, privateBrowsing));
nsRefPtr<AltSvcMapping> mapping = new AltSvcMapping(
nsDependentCString(originScheme.get()),
nsDependentCString(originHost.get()),
originPort, nsDependentCString(username), privateBrowsing, mExpires,
mHost, mPort, mNPNToken);
nsProxyInfo *proxyInfo = mCI->ProxyInfo();
gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, mCallbacks, 0);
AltSvcMapping::ProcessHeader(mHeader, originScheme, originHost, originPort,
mCI->GetUsername(), mCI->GetPrivate(), mCallbacks,
mCI->ProxyInfo(), 0);
return NS_OK;
}
private:
nsCString mHost;
uint16_t mPort;
nsCString mNPNToken;
uint32_t mExpires;
nsCString mHeader;
nsCString mOrigin;
nsRefPtr<nsHttpConnectionInfo> mCI;
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
};
// defined as an http2 extension - alt-svc
// defines receipt of frame type 0x0A.. See AlternateSevices.h
// defines receipt of frame type 0x0A.. See AlternateSevices.h at least draft -06 sec 4
// as this is an extension, never generate protocol error - just ignore problems
nsresult
Http2Session::RecvAltSvc(Http2Session *self)
{
@ -2093,40 +2075,85 @@ Http2Session::RecvAltSvc(Http2Session *self)
LOG3(("Http2Session::RecvAltSvc %p Flags 0x%X id 0x%X\n", self,
self->mInputFrameFlags, self->mInputFrameID));
if (self->mInputFrameDataSize < 8) {
if (self->mInputFrameDataSize < 2) {
LOG3(("Http2Session::RecvAltSvc %p frame too small", self));
RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
}
uint32_t maxAge =
PR_ntohl(*reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get() + kFrameHeaderBytes));
uint16_t portRoute =
PR_ntohs(*reinterpret_cast<uint16_t *>(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 4));
uint8_t protoLen = self->mInputFrameBuffer.get()[kFrameHeaderBytes + 6];
LOG3(("Http2Session::RecvAltSvc %p maxAge=%d port=%d protoLen=%d", self,
maxAge, portRoute, protoLen));
if (self->mInputFrameDataSize < (8U + protoLen)) {
LOG3(("Http2Session::RecvAltSvc %p frame too small for protocol", self));
RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
}
nsAutoCString protocol;
protocol.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 7, protoLen);
uint32_t spdyIndex;
SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(protocol, &spdyIndex)) &&
spdyInfo->ProtocolEnabled(spdyIndex))) {
LOG3(("Http2Session::RecvAltSvc %p unknown protocol %s, ignoring", self,
protocol.BeginReading()));
self->ResetDownstreamState();
return NS_OK;
}
uint8_t hostLen = self->mInputFrameBuffer.get()[kFrameHeaderBytes + 7 + protoLen];
if (self->mInputFrameDataSize < (8U + protoLen + hostLen)) {
LOG3(("Http2Session::RecvAltSvc %p frame too small for host", self));
RETURN_SESSION_ERROR(self, FRAME_SIZE_ERROR);
uint16_t originLen =
PR_ntohs(*reinterpret_cast<uint16_t *>(self->mInputFrameBuffer.get() + kFrameHeaderBytes));
if (originLen + 2U > self->mInputFrameDataSize) {
LOG3(("Http2Session::RecvAltSvc %p origin len too big for frame", self));
self->ResetDownstreamState();
return NS_OK;
}
if (!gHttpHandler->AllowAltSvc()) {
LOG3(("Http2Session::RecvAltSvc %p frame alt service pref'd off", self));
self->ResetDownstreamState();
return NS_OK;
}
uint16_t altSvcFieldValueLen = static_cast<uint16_t>(self->mInputFrameDataSize) - 2U - originLen;
LOG3(("Http2Session::RecvAltSvc %p frame originLen=%u altSvcFieldValueLen=%u\n",
self, originLen, altSvcFieldValueLen));
if (self->mInputFrameDataSize > 2000) {
LOG3(("Http2Session::RecvAltSvc %p frame too large to parse sensibly", self));
self->ResetDownstreamState();
return NS_OK;
}
nsAutoCString origin;
bool impliedOrigin = true;
if (originLen) {
origin.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2, originLen);
impliedOrigin = false;
}
nsAutoCString altSvcFieldValue;
if (altSvcFieldValueLen) {
altSvcFieldValue.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 2 + originLen,
altSvcFieldValueLen);
}
if (altSvcFieldValue.IsEmpty() || !nsHttp::IsReasonableHeaderValue(altSvcFieldValue)) {
LOG(("Http2Session %p Alt-Svc Response Header seems unreasonable - skipping\n", self));
self->ResetDownstreamState();
return NS_OK;
}
if (self->mInputFrameID & 1) {
// pulled streams apply to the origin of the pulled stream.
// If the origin field is filled in the frame, the frame should be ignored
if (!origin.IsEmpty()) {
LOG(("Http2Session %p Alt-Svc pulled stream has non empty origin\n", self));
self->ResetDownstreamState();
return NS_OK;
}
if (NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
!self->mInputFrameDataStream->Transaction() ||
!self->mInputFrameDataStream->Transaction()->RequestHead()) {
LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
self->ResetDownstreamState();
return NS_OK;
}
origin.Assign(self->mInputFrameDataStream->Transaction()->RequestHead()->Origin());
} else if (!self->mInputFrameID) {
// ID 0 streams must supply their own origin
if (origin.IsEmpty()) {
LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
self->ResetDownstreamState();
return NS_OK;
}
} else {
// handling of push streams is not defined. Let's ignore it
LOG(("Http2Session %p Alt-Svc Stream 0 has empty origin\n", self));
self->ResetDownstreamState();
return NS_OK;
}
nsRefPtr<nsHttpConnectionInfo> ci(self->ConnectionInfo());
@ -2137,21 +2164,7 @@ Http2Session::RecvAltSvc(Http2Session *self)
return NS_OK;
}
nsAutoCString hostRoute;
hostRoute.Assign(self->mInputFrameBuffer.get() + kFrameHeaderBytes + 8 + protoLen, hostLen);
uint32_t originLen = self->mInputFrameDataSize - 8 - protoLen - hostLen;
nsAutoCString specifiedOrigin;
if (originLen) {
if (self->mInputFrameID) {
LOG3(("Http2Session::RecvAltSvc %p got frame w/origin on non zero stream", self));
self->ResetDownstreamState();
return NS_OK;
}
specifiedOrigin.Assign(
self->mInputFrameBuffer.get() + kFrameHeaderBytes + 8 + protoLen + hostLen,
originLen);
if (!impliedOrigin) {
bool okToReroute = true;
nsCOMPtr<nsISupports> securityInfo;
self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
@ -2163,17 +2176,15 @@ Http2Session::RecvAltSvc(Http2Session *self)
// a little off main thread origin parser. This is a non critical function because
// any alternate route created has to be verified anyhow
nsAutoCString specifiedOriginHost;
if (specifiedOrigin.EqualsIgnoreCase("https://", 8)) {
specifiedOriginHost.Assign(specifiedOrigin.get() + 8,
specifiedOrigin.Length() - 8);
if (origin.EqualsIgnoreCase("https://", 8)) {
specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
if (ci->GetRelaxed()) {
// technically this is ok because it will still be confirmed before being used
// but let's not support it.
okToReroute = false;
}
} else if (specifiedOrigin.EqualsIgnoreCase("http://", 7)) {
specifiedOriginHost.Assign(specifiedOrigin.get() + 7,
specifiedOrigin.Length() - 7);
} else if (origin.EqualsIgnoreCase("http://", 7)) {
specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
}
int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
@ -2184,40 +2195,22 @@ Http2Session::RecvAltSvc(Http2Session *self)
if (okToReroute) {
ssl->IsAcceptableForHost(specifiedOriginHost, &okToReroute);
}
if (!okToReroute) {
LOG3(("Http2Session::RecvAltSvc %p can't reroute non-authoritative origin %s",
self, specifiedOrigin.BeginReading()));
self, origin.BeginReading()));
self->ResetDownstreamState();
return NS_OK;
}
} else {
// no origin specified in frame. We need to have an active pull stream to match
// this up to as if it were a response header.
if (!(self->mInputFrameID & 0x1) ||
NS_FAILED(self->SetInputFrameDataStream(self->mInputFrameID)) ||
!self->mInputFrameDataStream->Transaction() ||
!self->mInputFrameDataStream->Transaction()->RequestHead()) {
LOG3(("Http2Session::RecvAltSvc %p got frame w/o origin on invalid stream", self));
self->ResetDownstreamState();
return NS_OK;
}
specifiedOrigin.Assign(
self->mInputFrameDataStream->Transaction()->RequestHead()->Origin());
}
nsCOMPtr<nsISupports> callbacks;
self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
nsRefPtr<UpdateAltSvcEvent> event = new UpdateAltSvcEvent(
hostRoute, portRoute, protocol, NowInSeconds() + maxAge,
specifiedOrigin, ci, irCallbacks);
nsRefPtr<UpdateAltSvcEvent> event =
new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
NS_DispatchToMainThread(event);
LOG3(("Http2Session::RecvAltSvc %p processed location=%s:%u protocol=%s "
"maxAge=%u origin=%s", self, hostRoute.get(), portRoute,
protocol.get(), maxAge, specifiedOrigin.get()));
self->ResetDownstreamState();
return NS_OK;
}

View File

@ -1278,104 +1278,35 @@ nsHttpChannel::ProcessAltService()
return;
}
if (isHttp && !gHttpHandler->AllowAltSvcOE()) {
return;
}
const char *altSvc;
if (!(altSvc = mResponseHead->PeekHeader(nsHttp::Alternate_Service))) {
return;
}
LOG(("nsHttpChannel %p Alt-Svc Response Header %s\n", this, altSvc));
nsCString buf(altSvc);
if (!nsHttp::IsReasonableHeaderValue(buf)) {
LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
return;
}
ParsedHeaderValueListList parsedAltSvc(buf);
nsRefPtr<AltSvcMapping> mapping;
nsAutoCString originHost;
int32_t originPort = 80;
mURI->GetPort(&originPort);
if (NS_FAILED(mURI->GetHost(originHost))) {
return;
}
uint32_t now = NowInSeconds(), currentAge = 0;
mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) {
uint32_t maxage = 86400; // default
nsAutoCString hostname; // Always empty in the header form
nsAutoCString npnToken;
int32_t portno = originPort;
for (uint32_t pairIndex = 0;
pairIndex < parsedAltSvc.mValues[index].mValues.Length();
++pairIndex) {
nsDependentCSubstring &currentName =
parsedAltSvc.mValues[index].mValues[pairIndex].mName;
nsDependentCSubstring &currentValue =
parsedAltSvc.mValues[index].mValues[pairIndex].mValue;
if (!pairIndex) {
// h2=:443
npnToken = currentName;
int32_t colonIndex = currentValue.FindChar(':');
if (colonIndex >= 0) {
portno =
atoi(PromiseFlatCString(currentValue).get() + colonIndex + 1);
} else {
colonIndex = 0;
}
hostname.Assign(currentValue.BeginReading(), colonIndex);
} else if (currentName.Equals(NS_LITERAL_CSTRING("ma"))) {
maxage = atoi(PromiseFlatCString(currentValue).get());
break;
}
}
// unescape modifies a c string in place, so afterwards
// update nsCString length
nsUnescape(npnToken.BeginWriting());
npnToken.SetLength(strlen(npnToken.BeginReading()));
uint32_t spdyIndex;
SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
spdyInfo->ProtocolEnabled(spdyIndex))) {
LOG(("Alt Svc %p unknown protocol %s, ignoring", this, npnToken.get()));
continue;
}
mapping = new AltSvcMapping(scheme,
originHost, originPort,
mUsername, mPrivateBrowsing,
NowInSeconds() + maxage,
hostname, portno, npnToken);
if (!mapping) {
continue;
}
nsCOMPtr<nsIInterfaceRequestor> callbacks;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
if (!callbacks) {
return;
}
nsCOMPtr<nsProxyInfo> proxyInfo;
if (mProxyInfo) {
proxyInfo = do_QueryInterface(mProxyInfo);
}
gHttpHandler->
UpdateAltServiceMapping(mapping, proxyInfo, callbacks,
mCaps & NS_HTTP_DISALLOW_SPDY);
nsCOMPtr<nsIInterfaceRequestor> callbacks;
nsCOMPtr<nsProxyInfo> proxyInfo;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
if (mProxyInfo) {
proxyInfo = do_QueryInterface(mProxyInfo);
}
AltSvcMapping::ProcessHeader(buf, scheme, originHost, originPort,
mUsername, mPrivateBrowsing, callbacks, proxyInfo,
mCaps & NS_HTTP_DISALLOW_SPDY);
}
nsresult
@ -4854,7 +4785,17 @@ nsHttpChannel::BeginConnect()
LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d\n", this,
scheme.get(), mapping->AlternateHost().get(),
mapping->AlternatePort()));
mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("1"));
if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
nsAutoCString altUsedLine(mapping->AlternateHost());
bool defaultPort = mapping->AlternatePort() ==
(isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
if (!defaultPort) {
altUsedLine.AppendLiteral(":");
altUsedLine.AppendInt(mapping->AlternatePort());
}
mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
}
nsCOMPtr<nsIConsoleService> consoleService =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);

View File

@ -102,6 +102,7 @@ public:
const nsCString &GetHost() { return mHost; }
const nsCString &GetNPNToken() { return mNPNToken; }
const nsCString &GetUsername() { return mUsername; }
// Returns true for any kind of proxy (http, socks, https, etc..)
bool UsingProxy();

View File

@ -264,6 +264,9 @@ nsHttpResponseHead::AssignDefaultStatusText()
case 417:
mStatusText.AssignLiteral("Expectation Failed");
break;
case 421:
mStatusText.AssignLiteral("Misdirected Request");
break;
case 501:
mStatusText.AssignLiteral("Not Implemented");
break;

View File

@ -1482,7 +1482,7 @@ nsHttpTransaction::HandleContentStart()
break;
case 421:
if (!mConnInfo->GetAuthenticationHost().IsEmpty()) {
LOG(("Not Authoritative.\n"));
LOG(("Misdirected Request.\n"));
gHttpHandler->ConnMgr()->
ClearHostMapping(mConnInfo->GetHost(), mConnInfo->Port());
}