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