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