1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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 #include "InternalResponse.h"
8 
9 #include "mozilla/Assertions.h"
10 #include "mozilla/RefPtr.h"
11 #include "mozilla/dom/FetchTypes.h"
12 #include "mozilla/dom/InternalHeaders.h"
13 #include "mozilla/dom/cache/CacheTypes.h"
14 #include "mozilla/ipc/PBackgroundSharedTypes.h"
15 #include "mozilla/ipc/IPCStreamUtils.h"
16 #include "mozilla/RandomNum.h"
17 #include "mozilla/RemoteLazyInputStreamStorage.h"
18 #include "nsIRandomGenerator.h"
19 #include "nsStreamUtils.h"
20 
21 namespace mozilla::dom {
22 
23 namespace {
24 
25 // Const variable for generate padding size
26 // XXX This will be tweaked to something more meaningful in Bug 1383656.
27 const uint32_t kMaxRandomNumber = 102400;
28 
TakeStreamFromStorage(const BodyStreamVariant & aVariant,int64_t aBodySize)29 nsCOMPtr<nsIInputStream> TakeStreamFromStorage(
30     const BodyStreamVariant& aVariant, int64_t aBodySize) {
31   MOZ_ASSERT(aVariant.type() == BodyStreamVariant::TParentToParentStream);
32   const auto& uuid = aVariant.get_ParentToParentStream().uuid();
33 
34   auto storageOrErr = RemoteLazyInputStreamStorage::Get();
35   MOZ_ASSERT(storageOrErr.isOk());
36   auto storage = storageOrErr.unwrap();
37   auto stream = storage->ForgetStream(uuid);
38   MOZ_ASSERT(stream);
39 
40   return stream;
41 }
42 
43 }  // namespace
44 
InternalResponse(uint16_t aStatus,const nsACString & aStatusText,RequestCredentials aCredentialsMode)45 InternalResponse::InternalResponse(uint16_t aStatus,
46                                    const nsACString& aStatusText,
47                                    RequestCredentials aCredentialsMode)
48     : mType(ResponseType::Default),
49       mStatus(aStatus),
50       mStatusText(aStatusText),
51       mHeaders(new InternalHeaders(HeadersGuardEnum::Response)),
52       mBodySize(UNKNOWN_BODY_SIZE),
53       mPaddingSize(UNKNOWN_PADDING_SIZE),
54       mErrorCode(NS_OK),
55       mCredentialsMode(aCredentialsMode) {}
56 
FromIPC(const IPCInternalResponse & aIPCResponse)57 /* static */ RefPtr<InternalResponse> InternalResponse::FromIPC(
58     const IPCInternalResponse& aIPCResponse) {
59   if (aIPCResponse.type() == ResponseType::Error) {
60     return InternalResponse::NetworkError(aIPCResponse.errorCode());
61   }
62 
63   RefPtr<InternalResponse> response =
64       new InternalResponse(aIPCResponse.status(), aIPCResponse.statusText());
65 
66   response->SetURLList(aIPCResponse.urlList());
67   response->mHeaders =
68       new InternalHeaders(aIPCResponse.headers(), aIPCResponse.headersGuard());
69 
70   if (aIPCResponse.body()) {
71     auto bodySize = aIPCResponse.bodySize();
72     nsCOMPtr<nsIInputStream> body =
73         TakeStreamFromStorage(*aIPCResponse.body(), bodySize);
74     response->SetBody(body, bodySize);
75   }
76 
77   response->SetAlternativeDataType(aIPCResponse.alternativeDataType());
78 
79   if (aIPCResponse.alternativeBody()) {
80     nsCOMPtr<nsIInputStream> alternativeBody = TakeStreamFromStorage(
81         *aIPCResponse.alternativeBody(), UNKNOWN_BODY_SIZE);
82     response->SetAlternativeBody(alternativeBody);
83   }
84 
85   response->InitChannelInfo(aIPCResponse.channelInfo());
86 
87   if (aIPCResponse.principalInfo()) {
88     response->SetPrincipalInfo(MakeUnique<mozilla::ipc::PrincipalInfo>(
89         aIPCResponse.principalInfo().ref()));
90   }
91 
92   switch (aIPCResponse.type()) {
93     case ResponseType::Basic:
94       response = response->BasicResponse();
95       break;
96     case ResponseType::Cors:
97       response = response->CORSResponse();
98       break;
99     case ResponseType::Default:
100       break;
101     case ResponseType::Opaque:
102       response = response->OpaqueResponse();
103       break;
104     case ResponseType::Opaqueredirect:
105       response = response->OpaqueRedirectResponse();
106       break;
107     default:
108       MOZ_CRASH("Unexpected ResponseType!");
109   }
110 
111   MOZ_ASSERT(response);
112 
113   return response;
114 }
115 
116 InternalResponse::~InternalResponse() = default;
117 
ToIPC(IPCInternalResponse * aIPCResponse,mozilla::ipc::PBackgroundChild * aManager,UniquePtr<mozilla::ipc::AutoIPCStream> & aAutoBodyStream,UniquePtr<mozilla::ipc::AutoIPCStream> & aAutoAlternativeBodyStream)118 void InternalResponse::ToIPC(
119     IPCInternalResponse* aIPCResponse, mozilla::ipc::PBackgroundChild* aManager,
120     UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoBodyStream,
121     UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoAlternativeBodyStream) {
122   nsTArray<HeadersEntry> headers;
123   HeadersGuardEnum headersGuard;
124   UnfilteredHeaders()->ToIPC(headers, headersGuard);
125 
126   Maybe<mozilla::ipc::PrincipalInfo> principalInfo =
127       mPrincipalInfo ? Some(*mPrincipalInfo) : Nothing();
128 
129   // Note: all the arguments are copied rather than moved, which would be more
130   // efficient, because there's no move-friendly constructor generated.
131   *aIPCResponse =
132       IPCInternalResponse(mType, GetUnfilteredURLList(), GetUnfilteredStatus(),
133                           GetUnfilteredStatusText(), headersGuard, headers,
134                           Nothing(), static_cast<uint64_t>(UNKNOWN_BODY_SIZE),
135                           mErrorCode, GetAlternativeDataType(), Nothing(),
136                           mChannelInfo.AsIPCChannelInfo(), principalInfo);
137 
138   nsCOMPtr<nsIInputStream> body;
139   int64_t bodySize;
140   GetUnfilteredBody(getter_AddRefs(body), &bodySize);
141 
142   if (body) {
143     aIPCResponse->body().emplace(ChildToParentStream());
144     aIPCResponse->bodySize() = bodySize;
145 
146     aAutoBodyStream.reset(new mozilla::ipc::AutoIPCStream(
147         aIPCResponse->body()->get_ChildToParentStream().stream()));
148     DebugOnly<bool> ok = aAutoBodyStream->Serialize(body, aManager);
149     MOZ_ASSERT(ok);
150   }
151 
152   nsCOMPtr<nsIInputStream> alternativeBody = TakeAlternativeBody();
153   if (alternativeBody) {
154     aIPCResponse->alternativeBody().emplace(ChildToParentStream());
155 
156     aAutoAlternativeBodyStream.reset(new mozilla::ipc::AutoIPCStream(
157         aIPCResponse->alternativeBody()->get_ChildToParentStream().stream()));
158     DebugOnly<bool> ok =
159         aAutoAlternativeBodyStream->Serialize(alternativeBody, aManager);
160     MOZ_ASSERT(ok);
161   }
162 }
163 
Clone(CloneType aCloneType)164 already_AddRefed<InternalResponse> InternalResponse::Clone(
165     CloneType aCloneType) {
166   RefPtr<InternalResponse> clone = CreateIncompleteCopy();
167 
168   clone->mHeaders = new InternalHeaders(*mHeaders);
169 
170   // Make sure the clone response will have the same padding size.
171   clone->mPaddingInfo = mPaddingInfo;
172   clone->mPaddingSize = mPaddingSize;
173 
174   clone->mCacheInfoChannel = mCacheInfoChannel;
175 
176   if (mWrappedResponse) {
177     clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);
178     MOZ_ASSERT(!mBody);
179     return clone.forget();
180   }
181 
182   if (!mBody || aCloneType == eDontCloneInputStream) {
183     return clone.forget();
184   }
185 
186   nsCOMPtr<nsIInputStream> clonedBody;
187   nsCOMPtr<nsIInputStream> replacementBody;
188 
189   nsresult rv = NS_CloneInputStream(mBody, getter_AddRefs(clonedBody),
190                                     getter_AddRefs(replacementBody));
191   if (NS_WARN_IF(NS_FAILED(rv))) {
192     return nullptr;
193   }
194 
195   clone->mBody.swap(clonedBody);
196   if (replacementBody) {
197     mBody.swap(replacementBody);
198   }
199 
200   return clone.forget();
201 }
202 
BasicResponse()203 already_AddRefed<InternalResponse> InternalResponse::BasicResponse() {
204   MOZ_ASSERT(!mWrappedResponse,
205              "Can't BasicResponse a already wrapped response");
206   RefPtr<InternalResponse> basic = CreateIncompleteCopy();
207   basic->mType = ResponseType::Basic;
208   basic->mHeaders = InternalHeaders::BasicHeaders(Headers());
209   basic->mWrappedResponse = this;
210   return basic.forget();
211 }
212 
CORSResponse()213 already_AddRefed<InternalResponse> InternalResponse::CORSResponse() {
214   MOZ_ASSERT(!mWrappedResponse,
215              "Can't CORSResponse a already wrapped response");
216   RefPtr<InternalResponse> cors = CreateIncompleteCopy();
217   cors->mType = ResponseType::Cors;
218   cors->mHeaders = InternalHeaders::CORSHeaders(Headers(), mCredentialsMode);
219   cors->mWrappedResponse = this;
220   return cors.forget();
221 }
222 
GetPaddingInfo()223 uint32_t InternalResponse::GetPaddingInfo() {
224   // If it's an opaque response, the paddingInfo should be generated only when
225   // paddingSize is unknown size.
226   // If it's not, the paddingInfo should be nothing and the paddingSize should
227   // be unknown size.
228   MOZ_DIAGNOSTIC_ASSERT(
229       (mType == ResponseType::Opaque && mPaddingSize == UNKNOWN_PADDING_SIZE &&
230        mPaddingInfo.isSome()) ||
231       (mType == ResponseType::Opaque && mPaddingSize != UNKNOWN_PADDING_SIZE &&
232        mPaddingInfo.isNothing()) ||
233       (mType != ResponseType::Opaque && mPaddingSize == UNKNOWN_PADDING_SIZE &&
234        mPaddingInfo.isNothing()));
235   return mPaddingInfo.isSome() ? mPaddingInfo.ref() : 0;
236 }
237 
GeneratePaddingInfo()238 nsresult InternalResponse::GeneratePaddingInfo() {
239   MOZ_DIAGNOSTIC_ASSERT(mType == ResponseType::Opaque);
240   MOZ_DIAGNOSTIC_ASSERT(mPaddingSize == UNKNOWN_PADDING_SIZE);
241 
242   // Utilize random generator to generator a random number
243   nsresult rv;
244   uint32_t randomNumber = 0;
245   nsCOMPtr<nsIRandomGenerator> randomGenerator =
246       do_GetService("@mozilla.org/security/random-generator;1", &rv);
247   if (NS_WARN_IF(NS_FAILED(rv))) {
248     Maybe<uint64_t> maybeRandomNum = RandomUint64();
249     if (maybeRandomNum.isSome()) {
250       mPaddingInfo.emplace(uint32_t(maybeRandomNum.value() % kMaxRandomNumber));
251       return NS_OK;
252     }
253     return rv;
254   }
255 
256   MOZ_DIAGNOSTIC_ASSERT(randomGenerator);
257 
258   uint8_t* buffer;
259   rv = randomGenerator->GenerateRandomBytes(sizeof(randomNumber), &buffer);
260   if (NS_WARN_IF(NS_FAILED(rv))) {
261     Maybe<uint64_t> maybeRandomNum = RandomUint64();
262     if (maybeRandomNum.isSome()) {
263       mPaddingInfo.emplace(uint32_t(maybeRandomNum.value() % kMaxRandomNumber));
264       return NS_OK;
265     }
266     return rv;
267   }
268 
269   memcpy(&randomNumber, buffer, sizeof(randomNumber));
270   free(buffer);
271 
272   mPaddingInfo.emplace(randomNumber % kMaxRandomNumber);
273 
274   return rv;
275 }
276 
GetPaddingSize()277 int64_t InternalResponse::GetPaddingSize() {
278   // We initialize padding size to an unknown size (-1). After cached, we only
279   // pad opaque response. Opaque response's padding size might be unknown before
280   // cached.
281   MOZ_DIAGNOSTIC_ASSERT(mType == ResponseType::Opaque ||
282                         mPaddingSize == UNKNOWN_PADDING_SIZE);
283   MOZ_DIAGNOSTIC_ASSERT(mPaddingSize == UNKNOWN_PADDING_SIZE ||
284                         mPaddingSize >= 0);
285 
286   return mPaddingSize;
287 }
288 
SetPaddingSize(int64_t aPaddingSize)289 void InternalResponse::SetPaddingSize(int64_t aPaddingSize) {
290   // We should only pad the opaque response.
291   MOZ_DIAGNOSTIC_ASSERT(
292       (mType == ResponseType::Opaque) !=
293       (aPaddingSize == InternalResponse::UNKNOWN_PADDING_SIZE));
294   MOZ_DIAGNOSTIC_ASSERT(aPaddingSize == UNKNOWN_PADDING_SIZE ||
295                         aPaddingSize >= 0);
296 
297   mPaddingSize = aPaddingSize;
298 }
299 
SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo)300 void InternalResponse::SetPrincipalInfo(
301     UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) {
302   mPrincipalInfo = std::move(aPrincipalInfo);
303 }
304 
GetTainting() const305 LoadTainting InternalResponse::GetTainting() const {
306   switch (mType) {
307     case ResponseType::Cors:
308       return LoadTainting::CORS;
309     case ResponseType::Opaque:
310       return LoadTainting::Opaque;
311     default:
312       return LoadTainting::Basic;
313   }
314 }
315 
Unfiltered()316 already_AddRefed<InternalResponse> InternalResponse::Unfiltered() {
317   RefPtr<InternalResponse> ref = mWrappedResponse;
318   if (!ref) {
319     ref = this;
320   }
321   return ref.forget();
322 }
323 
OpaqueResponse()324 already_AddRefed<InternalResponse> InternalResponse::OpaqueResponse() {
325   MOZ_ASSERT(!mWrappedResponse,
326              "Can't OpaqueResponse a already wrapped response");
327   RefPtr<InternalResponse> response = new InternalResponse(0, ""_ns);
328   response->mType = ResponseType::Opaque;
329   response->mChannelInfo = mChannelInfo;
330   if (mPrincipalInfo) {
331     response->mPrincipalInfo =
332         MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
333   }
334   response->mWrappedResponse = this;
335   return response.forget();
336 }
337 
OpaqueRedirectResponse()338 already_AddRefed<InternalResponse> InternalResponse::OpaqueRedirectResponse() {
339   MOZ_ASSERT(!mWrappedResponse,
340              "Can't OpaqueRedirectResponse a already wrapped response");
341   MOZ_ASSERT(!mURLList.IsEmpty(),
342              "URLList should not be emtpy for internalResponse");
343   RefPtr<InternalResponse> response = OpaqueResponse();
344   response->mType = ResponseType::Opaqueredirect;
345   response->mURLList = mURLList.Clone();
346   return response.forget();
347 }
348 
CreateIncompleteCopy()349 already_AddRefed<InternalResponse> InternalResponse::CreateIncompleteCopy() {
350   RefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText);
351   copy->mType = mType;
352   copy->mURLList = mURLList.Clone();
353   copy->mChannelInfo = mChannelInfo;
354   if (mPrincipalInfo) {
355     copy->mPrincipalInfo =
356         MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
357   }
358   return copy.forget();
359 }
360 
361 }  // namespace mozilla::dom
362