1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
9 
10 // Log on level :5, instead of default :4.
11 #undef LOG
12 #define LOG(args) LOG5(args)
13 #undef LOG_ENABLED
14 #define LOG_ENABLED() LOG5_ENABLED()
15 
16 #include <algorithm>
17 
18 #include "Http2Push.h"
19 #include "nsHttpChannel.h"
20 #include "nsIHttpPushListener.h"
21 #include "nsString.h"
22 
23 namespace mozilla {
24 namespace net {
25 
26 class CallChannelOnPush final : public Runnable {
27  public:
CallChannelOnPush(nsIHttpChannelInternal * associatedChannel,const nsACString & pushedURI,Http2PushedStream * pushStream)28   CallChannelOnPush(nsIHttpChannelInternal *associatedChannel,
29                     const nsACString &pushedURI, Http2PushedStream *pushStream)
30       : Runnable("net::CallChannelOnPush"),
31         mAssociatedChannel(associatedChannel),
32         mPushedURI(pushedURI) {
33     mPushedStreamWrapper = new Http2PushedStreamWrapper(pushStream);
34   }
35 
Run()36   NS_IMETHOD Run() override {
37     MOZ_ASSERT(NS_IsMainThread());
38     RefPtr<nsHttpChannel> channel;
39     CallQueryInterface(mAssociatedChannel, channel.StartAssignment());
40     MOZ_ASSERT(channel);
41     if (channel &&
42         NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStreamWrapper))) {
43       return NS_OK;
44     }
45 
46     LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this));
47     mPushedStreamWrapper->OnPushFailed();
48     return NS_OK;
49   }
50 
51  private:
52   nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel;
53   const nsCString mPushedURI;
54   RefPtr<Http2PushedStreamWrapper> mPushedStreamWrapper;
55 };
56 
57 // Because WeakPtr isn't thread-safe we must ensure that the object is destroyed
58 // on the socket thread, so any Release() called on a different thread is
59 // dispatched to the socket thread.
DispatchRelease()60 bool Http2PushedStreamWrapper::DispatchRelease() {
61   if (OnSocketThread()) {
62     return false;
63   }
64 
65   gSocketTransportService->Dispatch(
66       NewNonOwningRunnableMethod("net::Http2PushedStreamWrapper::Release", this,
67                                  &Http2PushedStreamWrapper::Release),
68       NS_DISPATCH_NORMAL);
69 
70   return true;
71 }
72 
73 NS_IMPL_ADDREF(Http2PushedStreamWrapper)
NS_IMETHODIMP_(MozExternalRefCountType)74 NS_IMETHODIMP_(MozExternalRefCountType)
75 Http2PushedStreamWrapper::Release() {
76   nsrefcnt count = mRefCnt - 1;
77   if (DispatchRelease()) {
78     // Redispatched to the socket thread.
79     return count;
80   }
81 
82   MOZ_ASSERT(0 != mRefCnt, "dup release");
83   count = --mRefCnt;
84   NS_LOG_RELEASE(this, count, "Http2PushedStreamWrapper");
85 
86   if (0 == count) {
87     mRefCnt = 1;
88     delete (this);
89     return 0;
90   }
91 
92   return count;
93 }
94 
NS_INTERFACE_MAP_BEGIN(Http2PushedStreamWrapper)95 NS_INTERFACE_MAP_BEGIN(Http2PushedStreamWrapper)
96 NS_INTERFACE_MAP_END
97 
98 Http2PushedStreamWrapper::Http2PushedStreamWrapper(
99     Http2PushedStream* aPushStream) {
100   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
101   mStream = aPushStream;
102   mRequestString = aPushStream->GetRequestString();
103 }
104 
~Http2PushedStreamWrapper()105 Http2PushedStreamWrapper::~Http2PushedStreamWrapper() {
106   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
107 }
108 
GetStream()109 Http2PushedStream* Http2PushedStreamWrapper::GetStream() {
110   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
111   if (mStream) {
112     Http2Stream* stream = mStream;
113     return static_cast<Http2PushedStream*>(stream);
114   }
115   return nullptr;
116 }
117 
OnPushFailed()118 void Http2PushedStreamWrapper::OnPushFailed() {
119   if (OnSocketThread()) {
120     if (mStream) {
121       Http2Stream* stream = mStream;
122       static_cast<Http2PushedStream*>(stream)->OnPushFailed();
123     }
124   } else {
125     gSocketTransportService->Dispatch(
126         NewRunnableMethod("net::Http2PushedStreamWrapper::OnPushFailed", this,
127                           &Http2PushedStreamWrapper::OnPushFailed),
128         NS_DISPATCH_NORMAL);
129   }
130 }
131 
132 //////////////////////////////////////////
133 // Http2PushedStream
134 //////////////////////////////////////////
135 
Http2PushedStream(Http2PushTransactionBuffer * aTransaction,Http2Session * aSession,Http2Stream * aAssociatedStream,uint32_t aID,uint64_t aCurrentForegroundTabOuterContentWindowId)136 Http2PushedStream::Http2PushedStream(
137     Http2PushTransactionBuffer *aTransaction, Http2Session *aSession,
138     Http2Stream *aAssociatedStream, uint32_t aID,
139     uint64_t aCurrentForegroundTabOuterContentWindowId)
140     : Http2Stream(aTransaction, aSession, 0,
141                   aCurrentForegroundTabOuterContentWindowId),
142       mConsumerStream(nullptr),
143       mAssociatedTransaction(aAssociatedStream->Transaction()),
144       mBufferedPush(aTransaction),
145       mStatus(NS_OK),
146       mPushCompleted(false),
147       mDeferCleanupOnSuccess(true),
148       mDeferCleanupOnPush(false),
149       mOnPushFailed(false) {
150   LOG3(("Http2PushedStream ctor this=%p 0x%X\n", this, aID));
151   mStreamID = aID;
152   MOZ_ASSERT(!(aID & 1));  // must be even to be a pushed stream
153   mBufferedPush->SetPushStream(this);
154   mRequestContext = aAssociatedStream->RequestContext();
155   mLastRead = TimeStamp::Now();
156   SetPriority(aAssociatedStream->Priority() + 1);
157 }
158 
GetPushComplete()159 bool Http2PushedStream::GetPushComplete() { return mPushCompleted; }
160 
WriteSegments(nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)161 nsresult Http2PushedStream::WriteSegments(nsAHttpSegmentWriter *writer,
162                                           uint32_t count,
163                                           uint32_t *countWritten) {
164   nsresult rv = Http2Stream::WriteSegments(writer, count, countWritten);
165   if (NS_SUCCEEDED(rv) && *countWritten) {
166     mLastRead = TimeStamp::Now();
167   }
168 
169   if (rv == NS_BASE_STREAM_CLOSED) {
170     mPushCompleted = true;
171     rv = NS_OK;  // this is what a normal HTTP transaction would do
172   }
173   if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) mStatus = rv;
174   return rv;
175 }
176 
DeferCleanup(nsresult status)177 bool Http2PushedStream::DeferCleanup(nsresult status) {
178   LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 "\n", this,
179         static_cast<uint32_t>(status)));
180 
181   if (NS_SUCCEEDED(status) && mDeferCleanupOnSuccess) {
182     LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer on success\n",
183           this, static_cast<uint32_t>(status)));
184     return true;
185   }
186   if (mDeferCleanupOnPush) {
187     LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32 " defer onPush ref\n",
188           this, static_cast<uint32_t>(status)));
189     return true;
190   }
191   if (mConsumerStream) {
192     LOG3(("Http2PushedStream::DeferCleanup %p %" PRIx32
193           " defer active consumer\n",
194           this, static_cast<uint32_t>(status)));
195     return true;
196   }
197   LOG3(("Http2PushedStream::DeferCleanup Query %p %" PRIx32 " not deferred\n",
198         this, static_cast<uint32_t>(status)));
199   return false;
200 }
201 
202 // return true if channel implements nsIHttpPushListener
TryOnPush()203 bool Http2PushedStream::TryOnPush() {
204   nsHttpTransaction *trans = mAssociatedTransaction->QueryHttpTransaction();
205   if (!trans) {
206     return false;
207   }
208 
209   nsCOMPtr<nsIHttpChannelInternal> associatedChannel =
210       do_QueryInterface(trans->HttpChannel());
211   if (!associatedChannel) {
212     return false;
213   }
214 
215   if (!(trans->Caps() & NS_HTTP_ONPUSH_LISTENER)) {
216     return false;
217   }
218 
219   mDeferCleanupOnPush = true;
220   nsCString uri = Origin() + Path();
221   NS_DispatchToMainThread(new CallChannelOnPush(associatedChannel, uri, this));
222   return true;
223 }
224 
225 // side effect free static method to determine if Http2Stream implements
226 // nsIHttpPushListener
TestOnPush(Http2Stream * stream)227 bool Http2PushedStream::TestOnPush(Http2Stream *stream) {
228   if (!stream) {
229     return false;
230   }
231   nsAHttpTransaction *abstractTransaction = stream->Transaction();
232   if (!abstractTransaction) {
233     return false;
234   }
235   nsHttpTransaction *trans = abstractTransaction->QueryHttpTransaction();
236   if (!trans) {
237     return false;
238   }
239   nsCOMPtr<nsIHttpChannelInternal> associatedChannel =
240       do_QueryInterface(trans->HttpChannel());
241   if (!associatedChannel) {
242     return false;
243   }
244   return (trans->Caps() & NS_HTTP_ONPUSH_LISTENER);
245 }
246 
ReadSegments(nsAHttpSegmentReader * reader,uint32_t,uint32_t * count)247 nsresult Http2PushedStream::ReadSegments(nsAHttpSegmentReader *reader, uint32_t,
248                                          uint32_t *count) {
249   nsresult rv = NS_OK;
250   *count = 0;
251 
252   mozilla::OriginAttributes originAttributes;
253   switch (mUpstreamState) {
254     case GENERATING_HEADERS:
255       // The request headers for this has been processed, so we need to verify
256       // that :authority, :scheme, and :path MUST be present. :method MUST NOT
257       // be present
258       mSocketTransport->GetOriginAttributes(&originAttributes);
259       CreatePushHashKey(mHeaderScheme, mHeaderHost, originAttributes,
260                         mSession->Serial(), mHeaderPath, mOrigin, mHashKey);
261 
262       LOG3(("Http2PushStream 0x%X hash key %s\n", mStreamID, mHashKey.get()));
263 
264       // the write side of a pushed transaction just involves manipulating a
265       // little state
266       SetSentFin(true);
267       Http2Stream::mRequestHeadersDone = 1;
268       Http2Stream::mOpenGenerated = 1;
269       Http2Stream::ChangeState(UPSTREAM_COMPLETE);
270       break;
271 
272     case UPSTREAM_COMPLETE:
273       // Let's just clear the stream's transmit buffer by pushing it into
274       // the session. This is probably a window adjustment.
275       LOG3(("Http2Push::ReadSegments 0x%X \n", mStreamID));
276       mSegmentReader = reader;
277       rv = TransmitFrame(nullptr, nullptr, true);
278       mSegmentReader = nullptr;
279       break;
280 
281     case GENERATING_BODY:
282     case SENDING_BODY:
283     case SENDING_FIN_STREAM:
284     default:
285       break;
286   }
287 
288   return rv;
289 }
290 
AdjustInitialWindow()291 void Http2PushedStream::AdjustInitialWindow() {
292   LOG3(("Http2PushStream %p 0x%X AdjustInitialWindow", this, mStreamID));
293   if (mConsumerStream) {
294     LOG3(
295         ("Http2PushStream::AdjustInitialWindow %p 0x%X "
296          "calling super consumer %p 0x%X\n",
297          this, mStreamID, mConsumerStream, mConsumerStream->StreamID()));
298     Http2Stream::AdjustInitialWindow();
299     // Http2PushedStream::ReadSegments is needed to call TransmitFrame()
300     // and actually get this information into the session bytestream
301     mSession->TransactionHasDataToWrite(this);
302   }
303   // Otherwise, when we get hooked up, the initial window will get bumped
304   // anyway, so we're good to go.
305 }
306 
SetConsumerStream(Http2Stream * consumer)307 void Http2PushedStream::SetConsumerStream(Http2Stream *consumer) {
308   mConsumerStream = consumer;
309   mDeferCleanupOnPush = false;
310 }
311 
GetHashKey(nsCString & key)312 bool Http2PushedStream::GetHashKey(nsCString &key) {
313   if (mHashKey.IsEmpty()) return false;
314 
315   key = mHashKey;
316   return true;
317 }
318 
ConnectPushedStream(Http2Stream * stream)319 void Http2PushedStream::ConnectPushedStream(Http2Stream *stream) {
320   mSession->ConnectPushedStream(stream);
321 }
322 
IsOrphaned(TimeStamp now)323 bool Http2PushedStream::IsOrphaned(TimeStamp now) {
324   MOZ_ASSERT(!now.IsNull());
325 
326   // if session is not transmitting, and is also not connected to a consumer
327   // stream, and its been like that for too long then it is oprhaned
328 
329   if (mConsumerStream || mDeferCleanupOnPush) {
330     return false;
331   }
332 
333   if (mOnPushFailed) {
334     return true;
335   }
336 
337   bool rv = ((now - mLastRead).ToSeconds() > 30.0);
338   if (rv) {
339     LOG3(("Http2PushedStream:IsOrphaned 0x%X IsOrphaned %3.2f\n", mStreamID,
340           (now - mLastRead).ToSeconds()));
341   }
342   return rv;
343 }
344 
GetBufferedData(char * buf,uint32_t count,uint32_t * countWritten)345 nsresult Http2PushedStream::GetBufferedData(char *buf, uint32_t count,
346                                             uint32_t *countWritten) {
347   if (NS_FAILED(mStatus)) return mStatus;
348 
349   nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten);
350   if (NS_FAILED(rv)) return rv;
351 
352   if (!*countWritten)
353     rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK;
354 
355   return rv;
356 }
357 
358 //////////////////////////////////////////
359 // Http2PushTransactionBuffer
360 // This is the nsAHttpTransction owned by the stream when the pushed
361 // stream has not yet been matched with a pull request
362 //////////////////////////////////////////
363 
NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)364 NS_IMPL_ISUPPORTS0(Http2PushTransactionBuffer)
365 
366 Http2PushTransactionBuffer::Http2PushTransactionBuffer()
367     : mStatus(NS_OK),
368       mRequestHead(nullptr),
369       mPushStream(nullptr),
370       mIsDone(false),
371       mBufferedHTTP1Size(kDefaultBufferSize),
372       mBufferedHTTP1Used(0),
373       mBufferedHTTP1Consumed(0) {
374   mBufferedHTTP1 = MakeUnique<char[]>(mBufferedHTTP1Size);
375 }
376 
~Http2PushTransactionBuffer()377 Http2PushTransactionBuffer::~Http2PushTransactionBuffer() {
378   delete mRequestHead;
379 }
380 
SetConnection(nsAHttpConnection * conn)381 void Http2PushTransactionBuffer::SetConnection(nsAHttpConnection *conn) {}
382 
Connection()383 nsAHttpConnection *Http2PushTransactionBuffer::Connection() { return nullptr; }
384 
GetSecurityCallbacks(nsIInterfaceRequestor ** outCB)385 void Http2PushTransactionBuffer::GetSecurityCallbacks(
386     nsIInterfaceRequestor **outCB) {
387   *outCB = nullptr;
388 }
389 
OnTransportStatus(nsITransport * transport,nsresult status,int64_t progress)390 void Http2PushTransactionBuffer::OnTransportStatus(nsITransport *transport,
391                                                    nsresult status,
392                                                    int64_t progress) {}
393 
ConnectionInfo()394 nsHttpConnectionInfo *Http2PushTransactionBuffer::ConnectionInfo() {
395   if (!mPushStream) {
396     return nullptr;
397   }
398   if (!mPushStream->Transaction()) {
399     return nullptr;
400   }
401   MOZ_ASSERT(mPushStream->Transaction() != this);
402   return mPushStream->Transaction()->ConnectionInfo();
403 }
404 
IsDone()405 bool Http2PushTransactionBuffer::IsDone() { return mIsDone; }
406 
Status()407 nsresult Http2PushTransactionBuffer::Status() { return mStatus; }
408 
Caps()409 uint32_t Http2PushTransactionBuffer::Caps() { return 0; }
410 
SetDNSWasRefreshed()411 void Http2PushTransactionBuffer::SetDNSWasRefreshed() {}
412 
Available()413 uint64_t Http2PushTransactionBuffer::Available() {
414   return mBufferedHTTP1Used - mBufferedHTTP1Consumed;
415 }
416 
ReadSegments(nsAHttpSegmentReader * reader,uint32_t count,uint32_t * countRead)417 nsresult Http2PushTransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader,
418                                                   uint32_t count,
419                                                   uint32_t *countRead) {
420   *countRead = 0;
421   return NS_ERROR_NOT_IMPLEMENTED;
422 }
423 
WriteSegments(nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)424 nsresult Http2PushTransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer,
425                                                    uint32_t count,
426                                                    uint32_t *countWritten) {
427   if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) {
428     EnsureBuffer(mBufferedHTTP1, mBufferedHTTP1Size + kDefaultBufferSize,
429                  mBufferedHTTP1Used, mBufferedHTTP1Size);
430   }
431 
432   count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used);
433   nsresult rv = writer->OnWriteSegment(&mBufferedHTTP1[mBufferedHTTP1Used],
434                                        count, countWritten);
435   if (NS_SUCCEEDED(rv)) {
436     mBufferedHTTP1Used += *countWritten;
437   } else if (rv == NS_BASE_STREAM_CLOSED) {
438     mIsDone = true;
439   }
440 
441   if (Available() || mIsDone) {
442     Http2Stream *consumer = mPushStream->GetConsumerStream();
443 
444     if (consumer) {
445       LOG3(
446           ("Http2PushTransactionBuffer::WriteSegments notifying connection "
447            "consumer data available 0x%X [%" PRIu64 "] done=%d\n",
448            mPushStream->StreamID(), Available(), mIsDone));
449       mPushStream->ConnectPushedStream(consumer);
450     }
451   }
452 
453   return rv;
454 }
455 
Http1xTransactionCount()456 uint32_t Http2PushTransactionBuffer::Http1xTransactionCount() { return 0; }
457 
RequestHead()458 nsHttpRequestHead *Http2PushTransactionBuffer::RequestHead() {
459   if (!mRequestHead) mRequestHead = new nsHttpRequestHead();
460   return mRequestHead;
461 }
462 
TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction>> & outTransactions)463 nsresult Http2PushTransactionBuffer::TakeSubTransactions(
464     nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) {
465   return NS_ERROR_NOT_IMPLEMENTED;
466 }
467 
SetProxyConnectFailed()468 void Http2PushTransactionBuffer::SetProxyConnectFailed() {}
469 
Close(nsresult reason)470 void Http2PushTransactionBuffer::Close(nsresult reason) {
471   mStatus = reason;
472   mIsDone = true;
473 }
474 
GetBufferedData(char * buf,uint32_t count,uint32_t * countWritten)475 nsresult Http2PushTransactionBuffer::GetBufferedData(char *buf, uint32_t count,
476                                                      uint32_t *countWritten) {
477   *countWritten = std::min(count, static_cast<uint32_t>(Available()));
478   if (*countWritten) {
479     memcpy(buf, &mBufferedHTTP1[mBufferedHTTP1Consumed], *countWritten);
480     mBufferedHTTP1Consumed += *countWritten;
481   }
482 
483   // If all the data has been consumed then reset the buffer
484   if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) {
485     mBufferedHTTP1Consumed = 0;
486     mBufferedHTTP1Used = 0;
487   }
488 
489   return NS_OK;
490 }
491 
492 }  // namespace net
493 }  // namespace mozilla
494