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