tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Response.cpp (15902B)


      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 "BodyExtractor.h"
     10 #include "FetchStreamReader.h"
     11 #include "InternalResponse.h"
     12 #include "mozilla/BasePrincipal.h"
     13 #include "mozilla/ErrorResult.h"
     14 #include "mozilla/HoldDropJSObjects.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/dom/FetchBinding.h"
     17 #include "mozilla/dom/Headers.h"
     18 #include "mozilla/dom/Promise.h"
     19 #include "mozilla/dom/ReadableStreamDefaultReader.h"
     20 #include "mozilla/dom/ResponseBinding.h"
     21 #include "mozilla/dom/URL.h"
     22 #include "mozilla/dom/WorkerPrivate.h"
     23 #include "nsDOMString.h"
     24 #include "nsISupportsImpl.h"
     25 #include "nsIURI.h"
     26 #include "nsNetUtil.h"
     27 #include "nsPIDOMWindow.h"
     28 
     29 namespace mozilla::dom {
     30 
     31 NS_IMPL_ADDREF_INHERITED(Response, FetchBody<Response>)
     32 NS_IMPL_RELEASE_INHERITED(Response, FetchBody<Response>)
     33 
     34 NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
     35 
     36 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Response, FetchBody<Response>)
     37  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
     38  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
     39  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalImpl)
     40  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
     41  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
     42 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     43 
     44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Response, FetchBody<Response>)
     45  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
     46  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
     47  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalImpl)
     48  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
     49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     50 
     51 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Response, FetchBody<Response>)
     52  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
     53 NS_IMPL_CYCLE_COLLECTION_TRACE_END
     54 
     55 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
     56  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     57 NS_INTERFACE_MAP_END_INHERITING(FetchBody<Response>)
     58 
     59 Response::Response(nsIGlobalObject* aGlobal,
     60                   SafeRefPtr<InternalResponse> aInternalResponse,
     61                   AbortSignalImpl* aSignalImpl)
     62    : FetchBody<Response>(aGlobal),
     63      mInternalResponse(std::move(aInternalResponse)),
     64      mSignalImpl(aSignalImpl) {
     65  MOZ_ASSERT(
     66      mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
     67      mInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
     68 
     69  mozilla::HoldJSObjects(this);
     70 }
     71 
     72 Response::~Response() { mozilla::DropJSObjects(this); }
     73 
     74 /* static */
     75 already_AddRefed<Response> Response::Error(const GlobalObject& aGlobal) {
     76  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
     77  RefPtr<Response> r = new Response(
     78      global, InternalResponse::NetworkError(NS_ERROR_FAILURE), nullptr);
     79  return r.forget();
     80 }
     81 
     82 /* static */
     83 already_AddRefed<Response> Response::Redirect(const GlobalObject& aGlobal,
     84                                              const nsACString& aUrl,
     85                                              uint16_t aStatus,
     86                                              ErrorResult& aRv) {
     87  nsAutoCString parsedURL;
     88 
     89  if (NS_IsMainThread()) {
     90    nsIURI* baseURI = nullptr;
     91    nsCOMPtr<nsPIDOMWindowInner> inner(
     92        do_QueryInterface(aGlobal.GetAsSupports()));
     93    if (Document* doc = inner ? inner->GetExtantDoc() : nullptr) {
     94      baseURI = doc->GetBaseURI();
     95    }
     96    nsCOMPtr<nsIURI> resolvedURI;
     97    nsresult rv =
     98        NS_NewURI(getter_AddRefs(resolvedURI), aUrl, nullptr, baseURI);
     99    if (NS_WARN_IF(NS_FAILED(rv))) {
    100      aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl);
    101      return nullptr;
    102    }
    103 
    104    rv = resolvedURI->GetSpec(parsedURL);
    105    if (NS_WARN_IF(NS_FAILED(rv))) {
    106      aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl);
    107      return nullptr;
    108    }
    109  } else {
    110    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
    111    MOZ_ASSERT(worker);
    112    worker->AssertIsOnWorkerThread();
    113 
    114    const auto& baseURL = worker->GetLocationInfo().mHref;
    115    RefPtr<URL> url =
    116        URL::Constructor(aGlobal.GetAsSupports(), aUrl, baseURL, aRv);
    117    if (aRv.Failed()) {
    118      return nullptr;
    119    }
    120    url->GetHref(parsedURL);
    121  }
    122 
    123  if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 &&
    124      aStatus != 308) {
    125    aRv.ThrowRangeError("Invalid redirect status code.");
    126    return nullptr;
    127  }
    128 
    129  // We can't just pass nullptr for our null-valued Nullable, because the
    130  // fetch::ResponseBodyInit is a non-temporary type due to the MOZ_RAII
    131  // annotations on some of its members.
    132  Nullable<fetch::ResponseBodyInit> body;
    133  ResponseInit init;
    134  init.mStatus = aStatus;
    135  init.mStatusText.AssignASCII("");
    136  RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
    137  if (NS_WARN_IF(aRv.Failed())) {
    138    return nullptr;
    139  }
    140 
    141  r->GetInternalHeaders()->Set("Location"_ns, parsedURL, aRv);
    142  if (NS_WARN_IF(aRv.Failed())) {
    143    return nullptr;
    144  }
    145  r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv);
    146  MOZ_ASSERT(!aRv.Failed());
    147 
    148  return r.forget();
    149 }
    150 
    151 /* static */ already_AddRefed<Response> Response::CreateAndInitializeAResponse(
    152    const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
    153    const nsACString& aDefaultContentType, const ResponseInit& aInit,
    154    ErrorResult& aRv) {
    155  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    156 
    157  if (NS_WARN_IF(!global)) {
    158    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    159    return nullptr;
    160  }
    161 
    162  // Initialize a response, Step 1.
    163  if (aInit.mStatus < 200 || aInit.mStatus > 599) {
    164    aRv.ThrowRangeError("Invalid response status code.");
    165    return nullptr;
    166  }
    167 
    168  // Initialize a response, Step 2: Check if the status text contains illegal
    169  // characters
    170  nsACString::const_iterator start, end;
    171  aInit.mStatusText.BeginReading(start);
    172  aInit.mStatusText.EndReading(end);
    173  if (FindCharInReadable('\r', start, end)) {
    174    aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
    175    return nullptr;
    176  }
    177  // Reset iterator since FindCharInReadable advances it.
    178  aInit.mStatusText.BeginReading(start);
    179  if (FindCharInReadable('\n', start, end)) {
    180    aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>();
    181    return nullptr;
    182  }
    183 
    184  // Initialize a response, Step 3-4.
    185  SafeRefPtr<InternalResponse> internalResponse =
    186      MakeSafeRefPtr<InternalResponse>(aInit.mStatus, aInit.mStatusText);
    187 
    188  UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo;
    189 
    190  // Grab a valid channel info from the global so this response is 'valid' for
    191  // interception.
    192  if (NS_IsMainThread()) {
    193    ChannelInfo info;
    194    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
    195    if (window) {
    196      Document* doc = window->GetExtantDoc();
    197      MOZ_ASSERT(doc);
    198      info.InitFromDocument(doc);
    199 
    200      principalInfo.reset(new mozilla::ipc::PrincipalInfo());
    201      nsresult rv =
    202          PrincipalToPrincipalInfo(doc->NodePrincipal(), principalInfo.get());
    203      if (NS_WARN_IF(NS_FAILED(rv))) {
    204        aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
    205        return nullptr;
    206      }
    207 
    208      internalResponse->InitChannelInfo(info);
    209    } else if (global->PrincipalOrNull()->IsSystemPrincipal()) {
    210      info.InitFromChromeGlobal(global);
    211 
    212      internalResponse->InitChannelInfo(info);
    213    }
    214 
    215    /**
    216     * The channel info is left uninitialized if neither the above `if` nor
    217     * `else if` statements are executed; this could be because we're in a
    218     * WebExtensions content script, where the global (i.e. `global`) is a
    219     * wrapper, and the principal is an expanded principal. In this case,
    220     * as far as I can tell, there's no way to get the security info, but we'd
    221     * like the `Response` to be successfully constructed.
    222     */
    223  } else {
    224    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
    225    MOZ_ASSERT(worker);
    226    internalResponse->InitChannelInfo(worker->GetChannelInfo());
    227    principalInfo =
    228        MakeUnique<mozilla::ipc::PrincipalInfo>(worker->GetPrincipalInfo());
    229  }
    230 
    231  internalResponse->SetPrincipalInfo(std::move(principalInfo));
    232 
    233  RefPtr<Response> r =
    234      new Response(global, internalResponse.clonePtr(), nullptr);
    235 
    236  if (aInit.mHeaders.WasPassed()) {
    237    internalResponse->Headers()->Clear();
    238 
    239    // Instead of using Fill, create an object to allow the constructor to
    240    // unwrap the HeadersInit.
    241    RefPtr<Headers> headers =
    242        Headers::Create(global, aInit.mHeaders.Value(), aRv);
    243    if (aRv.Failed()) {
    244      return nullptr;
    245    }
    246 
    247    internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv);
    248    if (NS_WARN_IF(aRv.Failed())) {
    249      return nullptr;
    250    }
    251  }
    252 
    253  if (!aBody.IsNull()) {
    254    if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
    255      aRv.ThrowTypeError("Response body is given with a null body status.");
    256      return nullptr;
    257    }
    258 
    259    nsCString contentTypeWithCharset;
    260    contentTypeWithCharset.SetIsVoid(true);
    261    nsCOMPtr<nsIInputStream> bodyStream;
    262    int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
    263 
    264    const fetch::ResponseBodyInit& body = aBody.Value();
    265    if (body.IsReadableStream()) {
    266      JSContext* cx = aGlobal.Context();
    267      aRv.MightThrowJSException();
    268 
    269      ReadableStream& readableStream = body.GetAsReadableStream();
    270 
    271      if (readableStream.Locked() || readableStream.Disturbed()) {
    272        aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
    273        return nullptr;
    274      }
    275 
    276      r->SetReadableStreamBody(cx, &readableStream);
    277 
    278      // If this is a DOM generated ReadableStream, we can extract the
    279      // inputStream directly.
    280      if (nsIInputStream* underlyingSource =
    281              readableStream.MaybeGetInputStreamIfUnread()) {
    282        bodyStream = underlyingSource;
    283      } else {
    284        // If this is a JS-created ReadableStream, let's create a
    285        // FetchStreamReader.
    286        aRv = FetchStreamReader::Create(aGlobal.Context(), global,
    287                                        getter_AddRefs(r->mFetchStreamReader),
    288                                        getter_AddRefs(bodyStream));
    289        if (NS_WARN_IF(aRv.Failed())) {
    290          return nullptr;
    291        }
    292      }
    293    } else {
    294      uint64_t size = 0;
    295      aRv = ExtractByteStreamFromBody(body, getter_AddRefs(bodyStream),
    296                                      contentTypeWithCharset, size);
    297      if (NS_WARN_IF(aRv.Failed())) {
    298        return nullptr;
    299      }
    300 
    301      if (!aDefaultContentType.IsVoid()) {
    302        contentTypeWithCharset = aDefaultContentType;
    303      }
    304 
    305      bodySize = size;
    306    }
    307 
    308    internalResponse->SetBody(bodyStream, bodySize);
    309 
    310    if (!contentTypeWithCharset.IsVoid() &&
    311        !internalResponse->Headers()->Has("Content-Type"_ns, aRv)) {
    312      // Ignore Append() failing here.
    313      ErrorResult error;
    314      internalResponse->Headers()->Append("Content-Type"_ns,
    315                                          contentTypeWithCharset, error);
    316      error.SuppressException();
    317    }
    318 
    319    if (aRv.Failed()) {
    320      return nullptr;
    321    }
    322  }
    323 
    324  return r.forget();
    325 }
    326 
    327 /* static */
    328 already_AddRefed<Response> Response::CreateFromJson(const GlobalObject& aGlobal,
    329                                                    JSContext* aCx,
    330                                                    JS::Handle<JS::Value> aData,
    331                                                    const ResponseInit& aInit,
    332                                                    ErrorResult& aRv) {
    333  aRv.MightThrowJSException();
    334  nsAutoString serializedValue;
    335  if (!nsContentUtils::StringifyJSON(aCx, aData, serializedValue,
    336                                     UndefinedIsVoidString)) {
    337    aRv.StealExceptionFromJSContext(aCx);
    338    return nullptr;
    339  }
    340  if (serializedValue.IsVoid()) {
    341    aRv.ThrowTypeError<MSG_JSON_INVALID_VALUE>();
    342    return nullptr;
    343  }
    344  Nullable<fetch::ResponseBodyInit> body;
    345  body.SetValue().SetAsUSVString().ShareOrDependUpon(serializedValue);
    346  return CreateAndInitializeAResponse(aGlobal, body, "application/json"_ns,
    347                                      aInit, aRv);
    348 }
    349 
    350 /*static*/
    351 already_AddRefed<Response> Response::Constructor(
    352    const GlobalObject& aGlobal, const Nullable<fetch::ResponseBodyInit>& aBody,
    353    const ResponseInit& aInit, ErrorResult& aRv) {
    354  return CreateAndInitializeAResponse(aGlobal, aBody, VoidCString(), aInit,
    355                                      aRv);
    356 }
    357 
    358 already_AddRefed<Response> Response::Clone(JSContext* aCx, ErrorResult& aRv) {
    359  bool bodyUsed = BodyUsed();
    360 
    361  if (!bodyUsed && mReadableStreamBody) {
    362    bool locked = mReadableStreamBody->Locked();
    363    bodyUsed = locked;
    364  }
    365 
    366  if (bodyUsed) {
    367    aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
    368    return nullptr;
    369  }
    370 
    371  RefPtr<FetchStreamReader> streamReader;
    372  nsCOMPtr<nsIInputStream> inputStream;
    373 
    374  RefPtr<ReadableStream> body;
    375  MaybeTeeReadableStreamBody(aCx, getter_AddRefs(body),
    376                             getter_AddRefs(streamReader),
    377                             getter_AddRefs(inputStream), aRv);
    378  if (NS_WARN_IF(aRv.Failed())) {
    379    return nullptr;
    380  }
    381 
    382  MOZ_ASSERT_IF(body, streamReader);
    383  MOZ_ASSERT_IF(body, inputStream);
    384 
    385  SafeRefPtr<InternalResponse> ir =
    386      mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
    387                                    : InternalResponse::eCloneInputStream);
    388 
    389  RefPtr<Response> response =
    390      new Response(mOwner, ir.clonePtr(), GetSignalImpl());
    391 
    392  if (body) {
    393    // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
    394    // if this body is a native stream.   In this case the InternalResponse will
    395    // have a clone of the native body and the ReadableStream will be created
    396    // lazily if needed.
    397    response->SetReadableStreamBody(aCx, body);
    398    response->mFetchStreamReader = streamReader;
    399    ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
    400  }
    401 
    402  return response.forget();
    403 }
    404 
    405 already_AddRefed<Response> Response::CloneUnfiltered(JSContext* aCx,
    406                                                     ErrorResult& aRv) {
    407  if (BodyUsed()) {
    408    aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
    409    return nullptr;
    410  }
    411 
    412  RefPtr<FetchStreamReader> streamReader;
    413  nsCOMPtr<nsIInputStream> inputStream;
    414 
    415  RefPtr<ReadableStream> body;
    416  MaybeTeeReadableStreamBody(aCx, getter_AddRefs(body),
    417                             getter_AddRefs(streamReader),
    418                             getter_AddRefs(inputStream), aRv);
    419  if (NS_WARN_IF(aRv.Failed())) {
    420    return nullptr;
    421  }
    422 
    423  MOZ_ASSERT_IF(body, streamReader);
    424  MOZ_ASSERT_IF(body, inputStream);
    425 
    426  SafeRefPtr<InternalResponse> clone =
    427      mInternalResponse->Clone(body ? InternalResponse::eDontCloneInputStream
    428                                    : InternalResponse::eCloneInputStream);
    429 
    430  SafeRefPtr<InternalResponse> ir = clone->Unfiltered();
    431  RefPtr<Response> ref = new Response(mOwner, ir.clonePtr(), GetSignalImpl());
    432 
    433  if (body) {
    434    // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
    435    // if this body is a native stream.   In this case the InternalResponse will
    436    // have a clone of the native body and the ReadableStream will be created
    437    // lazily if needed.
    438    ref->SetReadableStreamBody(aCx, body);
    439    ref->mFetchStreamReader = streamReader;
    440    ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
    441  }
    442 
    443  return ref.forget();
    444 }
    445 
    446 void Response::SetBody(nsIInputStream* aBody, int64_t aBodySize) {
    447  MOZ_ASSERT(!BodyUsed());
    448  mInternalResponse->SetBody(aBody, aBodySize);
    449 }
    450 
    451 SafeRefPtr<InternalResponse> Response::GetInternalResponse() const {
    452  return mInternalResponse.clonePtr();
    453 }
    454 
    455 Headers* Response::Headers_() {
    456  if (!mHeaders) {
    457    mHeaders = new Headers(mOwner, mInternalResponse->Headers());
    458  }
    459 
    460  return mHeaders;
    461 }
    462 
    463 }  // namespace mozilla::dom