Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/fetch/Response.cpp
Line
Count
Source (jump to first uncovered line)
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 "Response.h"
8
9
#include "nsISupportsImpl.h"
10
#include "nsIURI.h"
11
#include "nsNetUtil.h"
12
#include "nsPIDOMWindow.h"
13
14
#include "mozilla/ErrorResult.h"
15
#include "mozilla/dom/FetchBinding.h"
16
#include "mozilla/dom/ResponseBinding.h"
17
#include "mozilla/dom/Headers.h"
18
#include "mozilla/dom/Promise.h"
19
#include "mozilla/dom/URL.h"
20
#include "mozilla/dom/WorkerPrivate.h"
21
22
#include "nsDOMString.h"
23
24
#include "BodyExtractor.h"
25
#include "FetchStream.h"
26
#include "FetchStreamReader.h"
27
#include "InternalResponse.h"
28
29
namespace mozilla {
30
namespace dom {
31
32
NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
33
NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
34
35
NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
36
37
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Response)
38
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
39
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
40
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl)
41
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
42
0
43
0
  tmp->mReadableStreamBody = nullptr;
44
0
  tmp->mReadableStreamReader = nullptr;
45
0
46
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
47
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
48
49
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Response)
50
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
51
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
52
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl)
53
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
54
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
55
56
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Response)
57
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody)
58
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
59
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
60
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
61
62
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
63
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
64
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
65
0
NS_INTERFACE_MAP_END
66
67
Response::Response(nsIGlobalObject* aGlobal,
68
                   InternalResponse* aInternalResponse,
69
                   AbortSignalImpl* aSignalImpl)
70
  : FetchBody<Response>(aGlobal)
71
  , mInternalResponse(aInternalResponse)
72
  , mSignalImpl(aSignalImpl)
73
0
{
74
0
  MOZ_ASSERT(aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
75
0
             aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
76
0
  SetMimeType();
77
0
78
0
  mozilla::HoldJSObjects(this);
79
0
}
80
81
Response::~Response()
82
0
{
83
0
  mozilla::DropJSObjects(this);
84
0
}
85
86
/* static */ already_AddRefed<Response>
87
Response::Error(const GlobalObject& aGlobal)
88
0
{
89
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
90
0
  RefPtr<InternalResponse> error = InternalResponse::NetworkError(NS_ERROR_FAILURE);
91
0
  RefPtr<Response> r = new Response(global, error, nullptr);
92
0
  return r.forget();
93
0
}
94
95
/* static */ already_AddRefed<Response>
96
Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
97
                   uint16_t aStatus, ErrorResult& aRv)
98
0
{
99
0
  nsAutoString parsedURL;
100
0
101
0
  if (NS_IsMainThread()) {
102
0
    nsCOMPtr<nsIURI> baseURI;
103
0
    nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aGlobal.GetAsSupports()));
104
0
    nsIDocument* doc = inner ? inner->GetExtantDoc() : nullptr;
105
0
    if (doc) {
106
0
      baseURI = doc->GetBaseURI();
107
0
    }
108
0
    nsCOMPtr<nsIURI> resolvedURI;
109
0
    aRv = NS_NewURI(getter_AddRefs(resolvedURI), aUrl, nullptr, baseURI);
110
0
    if (NS_WARN_IF(aRv.Failed())) {
111
0
      return nullptr;
112
0
    }
113
0
114
0
    nsAutoCString spec;
115
0
    aRv = resolvedURI->GetSpec(spec);
116
0
    if (NS_WARN_IF(aRv.Failed())) {
117
0
      return nullptr;
118
0
    }
119
0
120
0
    CopyUTF8toUTF16(spec, parsedURL);
121
0
  } else {
122
0
    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
123
0
    MOZ_ASSERT(worker);
124
0
    worker->AssertIsOnWorkerThread();
125
0
126
0
    NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
127
0
    RefPtr<URL> url = URL::WorkerConstructor(aGlobal, aUrl, baseURL, aRv);
128
0
    if (aRv.Failed()) {
129
0
      return nullptr;
130
0
    }
131
0
132
0
    url->Stringify(parsedURL);
133
0
  }
134
0
135
0
  if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 && aStatus != 308) {
136
0
    aRv.ThrowRangeError<MSG_INVALID_REDIRECT_STATUSCODE_ERROR>();
137
0
    return nullptr;
138
0
  }
139
0
140
0
  Optional<Nullable<fetch::ResponseBodyInit>> body;
141
0
  ResponseInit init;
142
0
  init.mStatus = aStatus;
143
0
  RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
144
0
  if (NS_WARN_IF(aRv.Failed())) {
145
0
    return nullptr;
146
0
  }
147
0
148
0
  r->GetInternalHeaders()->Set(NS_LITERAL_CSTRING("Location"),
149
0
                               NS_ConvertUTF16toUTF8(parsedURL), aRv);
150
0
  if (NS_WARN_IF(aRv.Failed())) {
151
0
    return nullptr;
152
0
  }
153
0
  r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
154
0
  MOZ_ASSERT(!aRv.Failed());
155
0
156
0
  return r.forget();
157
0
}
158
159
/*static*/ already_AddRefed<Response>
160
Response::Constructor(const GlobalObject& aGlobal,
161
                      const Optional<Nullable<fetch::ResponseBodyInit>>& aBody,
162
                      const ResponseInit& aInit, ErrorResult& aRv)
163
0
{
164
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
165
0
166
0
  if (aInit.mStatus < 200 || aInit.mStatus > 599) {
167
0
    aRv.ThrowRangeError<MSG_INVALID_RESPONSE_STATUSCODE_ERROR>();
168
0
    return nullptr;
169
0
  }
170
0
171
0
  // Check if the status text contains illegal characters
172
0
  nsACString::const_iterator start, end;
173
0
  aInit.mStatusText.BeginReading(start);
174
0
  aInit.mStatusText.EndReading(end);
175
0
  if (FindCharInReadable('\r', start, end)) {
176
0
    aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
177
0
    return nullptr;
178
0
  }
179
0
  // Reset iterator since FindCharInReadable advances it.
180
0
  aInit.mStatusText.BeginReading(start);
181
0
  if (FindCharInReadable('\n', start, end)) {
182
0
    aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
183
0
    return nullptr;
184
0
  }
185
0
186
0
  RefPtr<InternalResponse> internalResponse =
187
0
    new InternalResponse(aInit.mStatus, aInit.mStatusText);
188
0
189
0
  // Grab a valid channel info from the global so this response is 'valid' for
190
0
  // interception.
191
0
  if (NS_IsMainThread()) {
192
0
    ChannelInfo info;
193
0
    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
194
0
    if (window) {
195
0
      nsIDocument* doc = window->GetExtantDoc();
196
0
      MOZ_ASSERT(doc);
197
0
      info.InitFromDocument(doc);
198
0
    } else {
199
0
      info.InitFromChromeGlobal(global);
200
0
    }
201
0
    internalResponse->InitChannelInfo(info);
202
0
  } else {
203
0
    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
204
0
    MOZ_ASSERT(worker);
205
0
    internalResponse->InitChannelInfo(worker->GetChannelInfo());
206
0
  }
207
0
208
0
  RefPtr<Response> r = new Response(global, internalResponse, nullptr);
209
0
210
0
  if (aInit.mHeaders.WasPassed()) {
211
0
    internalResponse->Headers()->Clear();
212
0
213
0
    // Instead of using Fill, create an object to allow the constructor to
214
0
    // unwrap the HeadersInit.
215
0
    RefPtr<Headers> headers =
216
0
      Headers::Create(global, aInit.mHeaders.Value(), aRv);
217
0
    if (aRv.Failed()) {
218
0
      return nullptr;
219
0
    }
220
0
221
0
    internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
222
0
    if (NS_WARN_IF(aRv.Failed())) {
223
0
      return nullptr;
224
0
    }
225
0
  }
226
0
227
0
  if (aBody.WasPassed() && !aBody.Value().IsNull()) {
228
0
    if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
229
0
      aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>();
230
0
      return nullptr;
231
0
    }
232
0
233
0
    nsCString contentTypeWithCharset;
234
0
    nsCOMPtr<nsIInputStream> bodyStream;
235
0
    int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
236
0
237
0
    const fetch::ResponseBodyInit& body = aBody.Value().Value();
238
0
    if (body.IsReadableStream()) {
239
0
      const ReadableStream& readableStream = body.GetAsReadableStream();
240
0
241
0
      JS::Rooted<JSObject*> readableStreamObj(aGlobal.Context(),
242
0
                                              readableStream.Obj());
243
0
244
0
      if (JS::ReadableStreamIsDisturbed(readableStreamObj) ||
245
0
          JS::ReadableStreamIsLocked(readableStreamObj) ||
246
0
          !JS::ReadableStreamIsReadable(readableStreamObj)) {
247
0
        aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
248
0
        return nullptr;
249
0
      }
250
0
251
0
      r->SetReadableStreamBody(aGlobal.Context(), readableStreamObj);
252
0
253
0
      if (JS::ReadableStreamGetMode(readableStreamObj) ==
254
0
            JS::ReadableStreamMode::ExternalSource) {
255
0
        // If this is a DOM generated ReadableStream, we can extract the
256
0
        // inputStream directly.
257
0
        void* underlyingSource = nullptr;
258
0
        if (!JS::ReadableStreamGetExternalUnderlyingSource(aGlobal.Context(),
259
0
                                                           readableStreamObj,
260
0
                                                           &underlyingSource)) {
261
0
          aRv.StealExceptionFromJSContext(aGlobal.Context());
262
0
          return nullptr;
263
0
        }
264
0
265
0
        MOZ_ASSERT(underlyingSource);
266
0
267
0
        aRv = FetchStream::RetrieveInputStream(underlyingSource,
268
0
                                               getter_AddRefs(bodyStream));
269
0
270
0
        // The releasing of the external source is needed in order to avoid an
271
0
        // extra stream lock.
272
0
        JS::ReadableStreamReleaseExternalUnderlyingSource(readableStreamObj);
273
0
        if (NS_WARN_IF(aRv.Failed())) {
274
0
          return nullptr;
275
0
        }
276
0
      } else {
277
0
        // If this is a JS-created ReadableStream, let's create a
278
0
        // FetchStreamReader.
279
0
        aRv = FetchStreamReader::Create(aGlobal.Context(), global,
280
0
                                        getter_AddRefs(r->mFetchStreamReader),
281
0
                                        getter_AddRefs(bodyStream));
282
0
        if (NS_WARN_IF(aRv.Failed())) {
283
0
          return nullptr;
284
0
        }
285
0
      }
286
0
    } else {
287
0
      uint64_t size = 0;
288
0
      aRv = ExtractByteStreamFromBody(body,
289
0
                                      getter_AddRefs(bodyStream),
290
0
                                      contentTypeWithCharset,
291
0
                                      size);
292
0
      if (NS_WARN_IF(aRv.Failed())) {
293
0
        return nullptr;
294
0
      }
295
0
296
0
      bodySize = size;
297
0
    }
298
0
299
0
    internalResponse->SetBody(bodyStream, bodySize);
300
0
301
0
    if (!contentTypeWithCharset.IsVoid() &&
302
0
        !internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"),
303
0
                                          aRv)) {
304
0
      // Ignore Append() failing here.
305
0
      ErrorResult error;
306
0
      internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"),
307
0
                                          contentTypeWithCharset, error);
308
0
      error.SuppressException();
309
0
    }
310
0
311
0
    if (aRv.Failed()) {
312
0
      return nullptr;
313
0
    }
314
0
  }
315
0
316
0
  r->SetMimeType();
317
0
  return r.forget();
318
0
}
319
320
already_AddRefed<Response>
321
Response::Clone(JSContext* aCx, ErrorResult& aRv)
322
0
{
323
0
  if (BodyUsed()) {
324
0
    aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
325
0
    return nullptr;
326
0
  }
327
0
328
0
  RefPtr<FetchStreamReader> streamReader;
329
0
  nsCOMPtr<nsIInputStream> inputStream;
330
0
331
0
  JS::Rooted<JSObject*> body(aCx);
332
0
  MaybeTeeReadableStreamBody(aCx, &body,
333
0
                             getter_AddRefs(streamReader),
334
0
                             getter_AddRefs(inputStream), aRv);
335
0
  if (NS_WARN_IF(aRv.Failed())) {
336
0
    return nullptr;
337
0
  }
338
0
339
0
  MOZ_ASSERT_IF(body, streamReader);
340
0
  MOZ_ASSERT_IF(body, inputStream);
341
0
342
0
  RefPtr<InternalResponse> ir =
343
0
    mInternalResponse->Clone(body
344
0
      ? InternalResponse::eDontCloneInputStream
345
0
      : InternalResponse::eCloneInputStream);
346
0
347
0
  RefPtr<Response> response = new Response(mOwner, ir, GetSignalImpl());
348
0
349
0
  if (body) {
350
0
    // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
351
0
    // if this body is a native stream.   In this case the InternalResponse will
352
0
    // have a clone of the native body and the ReadableStream will be created
353
0
    // lazily if needed.
354
0
    response->SetReadableStreamBody(aCx, body);
355
0
    response->mFetchStreamReader = streamReader;
356
0
    ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
357
0
  }
358
0
359
0
  return response.forget();
360
0
}
361
362
already_AddRefed<Response>
363
Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv)
364
0
{
365
0
  if (BodyUsed()) {
366
0
    aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
367
0
    return nullptr;
368
0
  }
369
0
370
0
  RefPtr<FetchStreamReader> streamReader;
371
0
  nsCOMPtr<nsIInputStream> inputStream;
372
0
373
0
  JS::Rooted<JSObject*> body(aCx);
374
0
  MaybeTeeReadableStreamBody(aCx, &body,
375
0
                             getter_AddRefs(streamReader),
376
0
                             getter_AddRefs(inputStream), aRv);
377
0
  if (NS_WARN_IF(aRv.Failed())) {
378
0
    return nullptr;
379
0
  }
380
0
381
0
  MOZ_ASSERT_IF(body, streamReader);
382
0
  MOZ_ASSERT_IF(body, inputStream);
383
0
384
0
  RefPtr<InternalResponse> clone =
385
0
    mInternalResponse->Clone(body
386
0
      ? InternalResponse::eDontCloneInputStream
387
0
      : InternalResponse::eCloneInputStream);
388
0
389
0
  RefPtr<InternalResponse> ir = clone->Unfiltered();
390
0
  RefPtr<Response> ref = new Response(mOwner, ir, GetSignalImpl());
391
0
392
0
  if (body) {
393
0
    // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
394
0
    // if this body is a native stream.   In this case the InternalResponse will
395
0
    // have a clone of the native body and the ReadableStream will be created
396
0
    // lazily if needed.
397
0
    ref->SetReadableStreamBody(aCx, body);
398
0
    ref->mFetchStreamReader = streamReader;
399
0
    ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
400
0
  }
401
0
402
0
  return ref.forget();
403
0
}
404
405
void
406
Response::SetBody(nsIInputStream* aBody, int64_t aBodySize)
407
0
{
408
0
  MOZ_ASSERT(!BodyUsed());
409
0
  mInternalResponse->SetBody(aBody, aBodySize);
410
0
}
411
412
already_AddRefed<InternalResponse>
413
Response::GetInternalResponse() const
414
0
{
415
0
  RefPtr<InternalResponse> ref = mInternalResponse;
416
0
  return ref.forget();
417
0
}
418
419
Headers*
420
Response::Headers_()
421
0
{
422
0
  if (!mHeaders) {
423
0
    mHeaders = new Headers(mOwner, mInternalResponse->Headers());
424
0
  }
425
0
426
0
  return mHeaders;
427
0
}
428
429
} // namespace dom
430
} // namespace mozilla