/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"Fetch.h"#include"nsIDocument.h"#include"nsIGlobalObject.h"#include"nsIStreamLoader.h"#include"nsIThreadRetargetableRequest.h"#include"nsIUnicodeDecoder.h"#include"nsIUnicodeEncoder.h"#include"nsCharSeparatedTokenizer.h"#include"nsDOMString.h"#include"nsNetUtil.h"#include"nsReadableUtils.h"#include"nsStreamUtils.h"#include"nsStringStream.h"#include"mozilla/ErrorResult.h"#include"mozilla/dom/EncodingUtils.h"#include"mozilla/dom/Exceptions.h"#include"mozilla/dom/FetchDriver.h"#include"mozilla/dom/File.h"#include"mozilla/dom/Headers.h"#include"mozilla/dom/Promise.h"#include"mozilla/dom/Request.h"#include"mozilla/dom/Response.h"#include"mozilla/dom/ScriptSettings.h"#include"mozilla/dom/URLSearchParams.h"#include"mozilla/Telemetry.h"#include"InternalRequest.h"#include"InternalResponse.h"#include"nsFormData.h"#include"WorkerPrivate.h"#include"WorkerRunnable.h"#include"WorkerScope.h"#include"Workers.h"namespacemozilla{namespacedom{usingnamespaceworkers;classWorkerFetchResolverfinal:publicFetchDriverObserver,publicWorkerFeature{friendclassMainThreadFetchRunnable;friendclassWorkerFetchResponseEndRunnable;friendclassWorkerFetchResponseRunnable;workers::WorkerPrivate*mWorkerPrivate;MutexmCleanUpLock;boolmCleanedUp;// The following are initialized and used exclusively on the worker thread.nsRefPtr<Promise>mFetchPromise;nsRefPtr<Response>mResponse;public:WorkerFetchResolver(workers::WorkerPrivate*aWorkerPrivate,Promise*aPromise):mWorkerPrivate(aWorkerPrivate),mCleanUpLock("WorkerFetchResolver"),mCleanedUp(false),mFetchPromise(aPromise){}voidOnResponseAvailable(InternalResponse*aResponse)override;voidOnResponseEnd()override;boolNotify(JSContext*aCx,StatusaStatus)override{if(aStatus>Running){CleanUp(aCx);}returntrue;}voidCleanUp(JSContext*aCx){MutexAutoLocklock(mCleanUpLock);if(mCleanedUp){return;}MOZ_ASSERT(mWorkerPrivate);mWorkerPrivate->AssertIsOnWorkerThread();MOZ_ASSERT(mWorkerPrivate->GetJSContext()==aCx);mWorkerPrivate->RemoveFeature(aCx,this);CleanUpUnchecked();}voidCleanUpUnchecked(){mResponse=nullptr;if(mFetchPromise){mFetchPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);mFetchPromise=nullptr;}mCleanedUp=true;}workers::WorkerPrivate*GetWorkerPrivate()const{// It's ok to race on |mCleanedUp|, because it will never cause us to fire// the assertion when we should not.MOZ_ASSERT(!mCleanedUp);returnmWorkerPrivate;}private:~WorkerFetchResolver(){MOZ_ASSERT(mCleanedUp);MOZ_ASSERT(!mFetchPromise);}};classMainThreadFetchResolverfinal:publicFetchDriverObserver{nsRefPtr<Promise>mPromise;nsRefPtr<Response>mResponse;NS_DECL_OWNINGTHREADpublic:explicitMainThreadFetchResolver(Promise*aPromise);voidOnResponseAvailable(InternalResponse*aResponse)override;private:~MainThreadFetchResolver();};classMainThreadFetchRunnable:publicnsRunnable{nsRefPtr<WorkerFetchResolver>mResolver;nsRefPtr<InternalRequest>mRequest;public:MainThreadFetchRunnable(WorkerPrivate*aWorkerPrivate,Promise*aPromise,InternalRequest*aRequest):mResolver(newWorkerFetchResolver(aWorkerPrivate,aPromise)),mRequest(aRequest){MOZ_ASSERT(aWorkerPrivate);aWorkerPrivate->AssertIsOnWorkerThread();if(!aWorkerPrivate->AddFeature(aWorkerPrivate->GetJSContext(),mResolver)){NS_WARNING("Could not add WorkerFetchResolver feature to worker");mResolver->CleanUpUnchecked();mResolver=nullptr;}}NS_IMETHODIMPRun(){AssertIsOnMainThread();// AddFeature() call failed, don't bother running.if(!mResolver){returnNS_OK;}nsCOMPtr<nsIPrincipal>principal=mResolver->GetWorkerPrivate()->GetPrincipal();nsCOMPtr<nsILoadGroup>loadGroup=mResolver->GetWorkerPrivate()->GetLoadGroup();nsRefPtr<FetchDriver>fetch=newFetchDriver(mRequest,principal,loadGroup);nsIDocument*doc=mResolver->GetWorkerPrivate()->GetDocument();if(doc){fetch->SetDocument(doc);}nsresultrv=fetch->Fetch(mResolver);// Right now we only support async fetch, which should never directly fail.if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}returnNS_OK;}};already_AddRefed<Promise>FetchRequest(nsIGlobalObject*aGlobal,constRequestOrUSVString&aInput,constRequestInit&aInit,ErrorResult&aRv){nsRefPtr<Promise>p=Promise::Create(aGlobal,aRv);if(NS_WARN_IF(aRv.Failed())){returnnullptr;}AutoJSAPIjsapi;jsapi.Init(aGlobal);JSContext*cx=jsapi.cx();JS::Rooted<JSObject*>jsGlobal(cx,aGlobal->GetGlobalJSObject());GlobalObjectglobal(cx,jsGlobal);nsRefPtr<Request>request=Request::Constructor(global,aInput,aInit,aRv);if(NS_WARN_IF(aRv.Failed())){returnnullptr;}nsRefPtr<InternalRequest>r=request->GetInternalRequest();aRv=UpdateRequestReferrer(aGlobal,r);if(NS_WARN_IF(aRv.Failed())){returnnullptr;}if(NS_IsMainThread()){nsCOMPtr<nsPIDOMWindow>window=do_QueryInterface(aGlobal);nsCOMPtr<nsIDocument>doc;nsCOMPtr<nsILoadGroup>loadGroup;nsIPrincipal*principal;if(window){doc=window->GetExtantDoc();if(!doc){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}principal=doc->NodePrincipal();loadGroup=doc->GetDocumentLoadGroup();}else{principal=aGlobal->PrincipalOrNull();if(NS_WARN_IF(!principal)){aRv.Throw(NS_ERROR_FAILURE);returnnullptr;}nsresultrv=NS_NewLoadGroup(getter_AddRefs(loadGroup),principal);if(NS_WARN_IF(NS_FAILED(rv))){aRv.Throw(rv);returnnullptr;}}Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD,1);nsRefPtr<MainThreadFetchResolver>resolver=newMainThreadFetchResolver(p);nsRefPtr<FetchDriver>fetch=newFetchDriver(r,principal,loadGroup);fetch->SetDocument(doc);aRv=fetch->Fetch(resolver);if(NS_WARN_IF(aRv.Failed())){returnnullptr;}}else{WorkerPrivate*worker=GetCurrentThreadWorkerPrivate();MOZ_ASSERT(worker);Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD,0);if(worker->IsServiceWorker()){r->SetSkipServiceWorker();}nsRefPtr<MainThreadFetchRunnable>run=newMainThreadFetchRunnable(worker,p,r);if(NS_FAILED(NS_DispatchToMainThread(run))){NS_WARNING("MainThreadFetchRunnable dispatch failed!");}}returnp.forget();}MainThreadFetchResolver::MainThreadFetchResolver(Promise*aPromise):mPromise(aPromise){}voidMainThreadFetchResolver::OnResponseAvailable(InternalResponse*aResponse){NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);AssertIsOnMainThread();if(aResponse->Type()!=ResponseType::Error){nsCOMPtr<nsIGlobalObject>go=mPromise->GetParentObject();mResponse=newResponse(go,aResponse);mPromise->MaybeResolve(mResponse);}else{ErrorResultresult;result.ThrowTypeError(MSG_FETCH_FAILED);mPromise->MaybeReject(result);}}MainThreadFetchResolver::~MainThreadFetchResolver(){NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver);}classWorkerFetchResponseRunnablefinal:publicWorkerRunnable{nsRefPtr<WorkerFetchResolver>mResolver;// Passed from main thread to worker thread after being initialized.nsRefPtr<InternalResponse>mInternalResponse;public:WorkerFetchResponseRunnable(WorkerFetchResolver*aResolver,InternalResponse*aResponse):WorkerRunnable(aResolver->GetWorkerPrivate(),WorkerThreadModifyBusyCount),mResolver(aResolver),mInternalResponse(aResponse){}boolWorkerRun(JSContext*aCx,WorkerPrivate*aWorkerPrivate)override{MOZ_ASSERT(aWorkerPrivate);aWorkerPrivate->AssertIsOnWorkerThread();MOZ_ASSERT(aWorkerPrivate==mResolver->GetWorkerPrivate());nsRefPtr<Promise>promise=mResolver->mFetchPromise.forget();if(mInternalResponse->Type()!=ResponseType::Error){nsRefPtr<nsIGlobalObject>global=aWorkerPrivate->GlobalScope();mResolver->mResponse=newResponse(global,mInternalResponse);promise->MaybeResolve(mResolver->mResponse);}else{ErrorResultresult;result.ThrowTypeError(MSG_FETCH_FAILED);promise->MaybeReject(result);}returntrue;}};classWorkerFetchResponseEndRunnablefinal:publicWorkerRunnable{nsRefPtr<WorkerFetchResolver>mResolver;public:explicitWorkerFetchResponseEndRunnable(WorkerFetchResolver*aResolver):WorkerRunnable(aResolver->GetWorkerPrivate(),WorkerThreadModifyBusyCount),mResolver(aResolver){}boolWorkerRun(JSContext*aCx,WorkerPrivate*aWorkerPrivate)override{MOZ_ASSERT(aWorkerPrivate);aWorkerPrivate->AssertIsOnWorkerThread();MOZ_ASSERT(aWorkerPrivate==mResolver->GetWorkerPrivate());mResolver->CleanUp(aCx);returntrue;}};voidWorkerFetchResolver::OnResponseAvailable(InternalResponse*aResponse){AssertIsOnMainThread();MutexAutoLocklock(mCleanUpLock);if(mCleanedUp){return;}nsRefPtr<WorkerFetchResponseRunnable>r=newWorkerFetchResponseRunnable(this,aResponse);AutoSafeJSContextcx;if(!r->Dispatch(cx)){NS_WARNING("Could not dispatch fetch resolve");}}voidWorkerFetchResolver::OnResponseEnd(){AssertIsOnMainThread();MutexAutoLocklock(mCleanUpLock);if(mCleanedUp){return;}nsRefPtr<WorkerFetchResponseEndRunnable>r=newWorkerFetchResponseEndRunnable(this);AutoSafeJSContextcx;if(!r->Dispatch(cx)){NS_WARNING("Could not dispatch fetch resolve end");}}// This method sets the request's referrerURL, as specified by the "determine// request's referrer" steps from Referrer Policy [1].// The actual referrer policy and stripping is dealt with by HttpBaseChannel,// this always sets the full API referrer URL of the relevant global if it is// not already a url or no-referrer.// [1]: https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrernsresultUpdateRequestReferrer(nsIGlobalObject*aGlobal,InternalRequest*aRequest){nsAutoStringoriginalReferrer;aRequest->GetReferrer(originalReferrer);// If it is no-referrer ("") or a URL, don't modify.if(!originalReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)){returnNS_OK;}nsCOMPtr<nsPIDOMWindow>window=do_QueryInterface(aGlobal);if(window){nsCOMPtr<nsIDocument>doc=window->GetExtantDoc();if(doc){nsAutoStringreferrer;doc->GetReferrer(referrer);aRequest->SetReferrer(referrer);}}elseif(NS_IsMainThread()){// Pull the principal from the global for non-worker scripts.nsIPrincipal*principal=aGlobal->PrincipalOrNull();boolisNull;// Only set the referrer if the principal is present,// and the principal is not null or the system principal.if(principal&&NS_SUCCEEDED(principal->GetIsNullPrincipal(&isNull))&&!isNull&&!nsContentUtils::IsSystemPrincipal(principal)){nsCOMPtr<nsIURI>uri;if(NS_SUCCEEDED(principal->GetURI(getter_AddRefs(uri)))&&uri){nsAutoCStringreferrer;if(NS_SUCCEEDED(uri->GetSpec(referrer))){aRequest->SetReferrer(NS_ConvertUTF8toUTF16(referrer));}}}}else{WorkerPrivate*worker=GetCurrentThreadWorkerPrivate();MOZ_ASSERT(worker);worker->AssertIsOnWorkerThread();WorkerPrivate::LocationInfo&info=worker->GetLocationInfo();aRequest->SetReferrer(NS_ConvertUTF8toUTF16(info.mHref));}returnNS_OK;}namespace{nsresultExtractFromArrayBuffer(constArrayBuffer&aBuffer,nsIInputStream**aStream){aBuffer.ComputeLengthAndData();//XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.returnNS_NewByteInputStream(aStream,reinterpret_cast<char*>(aBuffer.Data()),aBuffer.Length(),NS_ASSIGNMENT_COPY);}nsresultExtractFromArrayBufferView(constArrayBufferView&aBuffer,nsIInputStream**aStream){aBuffer.ComputeLengthAndData();//XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.returnNS_NewByteInputStream(aStream,reinterpret_cast<char*>(aBuffer.Data()),aBuffer.Length(),NS_ASSIGNMENT_COPY);}nsresultExtractFromBlob(constBlob&aBlob,nsIInputStream**aStream,nsCString&aContentType){nsRefPtr<BlobImpl>impl=aBlob.Impl();ErrorResultrv;impl->GetInternalStream(aStream,rv);if(NS_WARN_IF(rv.Failed())){returnrv.StealNSResult();}nsAutoStringtype;impl->GetType(type);aContentType=NS_ConvertUTF16toUTF8(type);returnNS_OK;}nsresultExtractFromFormData(nsFormData&aFormData,nsIInputStream**aStream,nsCString&aContentType){uint64_tunusedContentLength;nsAutoCStringunusedCharset;returnaFormData.GetSendInfo(aStream,&unusedContentLength,aContentType,unusedCharset);}nsresultExtractFromUSVString(constnsString&aStr,nsIInputStream**aStream,nsCString&aContentType){nsCOMPtr<nsIUnicodeEncoder>encoder=EncodingUtils::EncoderForEncoding("UTF-8");if(!encoder){returnNS_ERROR_OUT_OF_MEMORY;}int32_tdestBufferLen;nsresultrv=encoder->GetMaxLength(aStr.get(),aStr.Length(),&destBufferLen);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}nsCStringencoded;if(!encoded.SetCapacity(destBufferLen,fallible)){returnNS_ERROR_OUT_OF_MEMORY;}char*destBuffer=encoded.BeginWriting();int32_tsrcLen=(int32_t)aStr.Length();int32_toutLen=destBufferLen;rv=encoder->Convert(aStr.get(),&srcLen,destBuffer,&outLen);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}MOZ_ASSERT(outLen<=destBufferLen);encoded.SetLength(outLen);aContentType=NS_LITERAL_CSTRING("text/plain;charset=UTF-8");returnNS_NewCStringInputStream(aStream,encoded);}nsresultExtractFromURLSearchParams(constURLSearchParams&aParams,nsIInputStream**aStream,nsCString&aContentType){nsAutoStringserialized;aParams.Stringify(serialized);aContentType=NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");returnNS_NewStringInputStream(aStream,serialized);}voidFillFormData(constnsString&aName,constnsString&aValue,void*aFormData){MOZ_ASSERT(aFormData);nsFormData*fd=static_cast<nsFormData*>(aFormData);fd->Append(aName,aValue);}/** * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046. * This does not respect any encoding specified per entry, using UTF-8 * throughout. This is as the Fetch spec states in the consume body algorithm. * Borrows some things from Necko's nsMultiMixedConv, but is simpler since * unlike Necko we do not have to deal with receiving incomplete chunks of data. * * This parser will fail the entire parse on any invalid entry, so it will * never return a partially filled FormData. * The content-disposition header is used to figure out the name and filename * entries. The inclusion of the filename parameter decides if the entry is * inserted into the nsFormData as a string or a File. * * File blobs are copies of the underlying data string since we cannot adopt * char* chunks embedded within the larger body without significant effort. * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and * friends to figure out if Fetch ends up copying big blobs to see if this is * worth optimizing. */classMOZ_STACK_CLASSFormDataParser{private:nsRefPtr<nsFormData>mFormData;nsCStringmMimeType;nsCStringmData;// Entry state, reset in START_PART.nsCStringmName;nsCStringmFilename;nsCStringmContentType;enum{START_PART,PARSE_HEADER,PARSE_BODY,}mState;nsIGlobalObject*mParentObject;// Reads over a boundary and sets start to the position after the end of the// boundary. Returns false if no boundary is found immediately.boolPushOverBoundary(constnsACString&aBoundaryString,nsACString::const_iterator&aStart,nsACString::const_iterator&aEnd){// We copy the end iterator to keep the original pointing to the real end// of the string.nsACString::const_iteratorend(aEnd);constchar*beginning=aStart.get();if(FindInReadable(aBoundaryString,aStart,end)){// We either should find the body immediately, or after 2 chars with the// 2 chars being '-', everything else is failure.if((aStart.get()-beginning)==0){aStart.advance(aBoundaryString.Length());returntrue;}if((aStart.get()-beginning)==2){if(*(--aStart)=='-'&&*(--aStart)=='-'){aStart.advance(aBoundaryString.Length()+2);returntrue;}}}returnfalse;}// Reads over a CRLF and positions start after it.boolPushOverLine(nsACString::const_iterator&aStart){if(*aStart==nsCRT::CR&&(aStart.size_forward()>1)&&*(++aStart)==nsCRT::LF){++aStart;// advance to after CRLFreturntrue;}returnfalse;}boolFindCRLF(nsACString::const_iterator&aStart,nsACString::const_iterator&aEnd){nsACString::const_iteratorend(aEnd);returnFindInReadable(NS_LITERAL_CSTRING("\r\n"),aStart,end);}boolParseHeader(nsACString::const_iterator&aStart,nsACString::const_iterator&aEnd,bool*aWasEmptyHeader){MOZ_ASSERT(aWasEmptyHeader);// Set it to a valid value here so we don't forget later.*aWasEmptyHeader=false;constchar*beginning=aStart.get();nsACString::const_iteratorend(aEnd);if(!FindCRLF(aStart,end)){returnfalse;}if(aStart.get()==beginning){*aWasEmptyHeader=true;returntrue;}nsAutoCStringheader(beginning,aStart.get()-beginning);nsACString::const_iteratorheaderStart,headerEnd;header.BeginReading(headerStart);header.EndReading(headerEnd);if(!FindCharInReadable(':',headerStart,headerEnd)){returnfalse;}nsAutoCStringheaderName(StringHead(header,headerStart.size_backward()));headerName.CompressWhitespace();if(!NS_IsValidHTTPToken(headerName)){returnfalse;}nsAutoCStringheaderValue(Substring(++headerStart,headerEnd));if(!NS_IsReasonableHTTPHeaderValue(headerValue)){returnfalse;}headerValue.CompressWhitespace();if(headerName.LowerCaseEqualsLiteral("content-disposition")){nsCCharSeparatedTokenizertokenizer(headerValue,';');boolseenFormData=false;while(tokenizer.hasMoreTokens()){constnsDependentCSubstring&token=tokenizer.nextToken();if(token.IsEmpty()){continue;}if(token.EqualsLiteral("form-data")){seenFormData=true;continue;}if(seenFormData&&StringBeginsWith(token,NS_LITERAL_CSTRING("name="))){mName=StringTail(token,token.Length()-5);mName.Trim(" \"");continue;}if(seenFormData&&StringBeginsWith(token,NS_LITERAL_CSTRING("filename="))){mFilename=StringTail(token,token.Length()-9);mFilename.Trim(" \"");continue;}}if(mName.IsVoid()){// Could not parse a valid entry name.returnfalse;}}elseif(headerName.LowerCaseEqualsLiteral("content-type")){mContentType=headerValue;}returntrue;}// The end of a body is marked by a CRLF followed by the boundary. So the// CRLF is part of the boundary and not the body, but any prior CRLFs are// part of the body. This will position the iterator at the beginning of the// boundary (after the CRLF).boolParseBody(constnsACString&aBoundaryString,nsACString::const_iterator&aStart,nsACString::const_iterator&aEnd){constchar*beginning=aStart.get();// Find the boundary marking the end of the body.nsACString::const_iteratorend(aEnd);if(!FindInReadable(aBoundaryString,aStart,end)){returnfalse;}// We found a boundary, strip the just prior CRLF, and consider// everything else the body section.if(aStart.get()-beginning<2){// Only the first entry can have a boundary right at the beginning. Even// an empty body will have a CRLF before the boundary. So this is// a failure.returnfalse;}// Check that there is a CRLF right before the boundary.aStart.advance(-2);// Skip optional hyphens.if(*aStart=='-'&&*(aStart.get()+1)=='-'){if(aStart.get()-beginning<2){returnfalse;}aStart.advance(-2);}if(*aStart!=nsCRT::CR||*(aStart.get()+1)!=nsCRT::LF){returnfalse;}nsAutoCStringbody(beginning,aStart.get()-beginning);// Restore iterator to after the \r\n as we promised.// We do not need to handle the extra hyphens case since our boundary// parser in PushOverBoundary()aStart.advance(2);if(!mFormData){mFormData=newnsFormData();}NS_ConvertUTF8toUTF16name(mName);if(mFilename.IsVoid()){mFormData->Append(name,NS_ConvertUTF8toUTF16(body));}else{// Unfortunately we've to copy the data first since all our strings are// going to free it. We also need fallible alloc, so we can't just use// ToNewCString().char*copy=static_cast<char*>(moz_xmalloc(body.Length()));if(!copy){NS_WARNING("Failed to copy File entry body.");returnfalse;}nsCString::const_iteratorbodyIter,bodyEnd;body.BeginReading(bodyIter);body.EndReading(bodyEnd);char*p=copy;while(bodyIter!=bodyEnd){*p++=*bodyIter++;}p=nullptr;nsRefPtr<Blob>file=File::CreateMemoryFile(mParentObject,reinterpret_cast<void*>(copy),body.Length(),NS_ConvertUTF8toUTF16(mFilename),NS_ConvertUTF8toUTF16(mContentType),/* aLastModifiedDate */0);Optional<nsAString>dummy;mFormData->Append(name,*file,dummy);}returntrue;}public:FormDataParser(constnsACString&aMimeType,constnsACString&aData,nsIGlobalObject*aParent):mMimeType(aMimeType),mData(aData),mState(START_PART),mParentObject(aParent){}boolParse(){// Determine boundary from mimetype.constchar*boundaryId=nullptr;boundaryId=strstr(mMimeType.BeginWriting(),"boundary");if(!boundaryId){returnfalse;}boundaryId=strchr(boundaryId,'=');if(!boundaryId){returnfalse;}// Skip over '='.boundaryId++;char*attrib=(char*)strchr(boundaryId,';');if(attrib)*attrib='\0';nsAutoCStringboundaryString(boundaryId);if(attrib)*attrib=';';boundaryString.Trim(" \"");if(boundaryString.Length()==0){returnfalse;}nsACString::const_iteratorstart,end;mData.BeginReading(start);// This should ALWAYS point to the end of data.// Helpers make copies.mData.EndReading(end);while(start!=end){switch(mState){caseSTART_PART:mName.SetIsVoid(true);mFilename.SetIsVoid(true);mContentType=NS_LITERAL_CSTRING("text/plain");// MUST start with boundary.if(!PushOverBoundary(boundaryString,start,end)){returnfalse;}if(start!=end&&*start=='-'){// End of data.if(!mFormData){mFormData=newnsFormData();}returntrue;}if(!PushOverLine(start)){returnfalse;}mState=PARSE_HEADER;break;casePARSE_HEADER:boolemptyHeader;if(!ParseHeader(start,end,&emptyHeader)){returnfalse;}if(!PushOverLine(start)){returnfalse;}mState=emptyHeader?PARSE_BODY:PARSE_HEADER;break;casePARSE_BODY:if(mName.IsVoid()){NS_WARNING("No content-disposition header with a valid name was ""found. Failing at body parse.");returnfalse;}if(!ParseBody(boundaryString,start,end)){returnfalse;}mState=START_PART;break;default:MOZ_CRASH("Invalid case");}}NS_NOTREACHED("Should never reach here.");returnfalse;}already_AddRefed<nsFormData>FormData(){returnmFormData.forget();}};}// anonymous namespacensresultExtractByteStreamFromBody(constOwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams&aBodyInit,nsIInputStream**aStream,nsCString&aContentType){MOZ_ASSERT(aStream);if(aBodyInit.IsArrayBuffer()){constArrayBuffer&buf=aBodyInit.GetAsArrayBuffer();returnExtractFromArrayBuffer(buf,aStream);}elseif(aBodyInit.IsArrayBufferView()){constArrayBufferView&buf=aBodyInit.GetAsArrayBufferView();returnExtractFromArrayBufferView(buf,aStream);}elseif(aBodyInit.IsBlob()){constBlob&blob=aBodyInit.GetAsBlob();returnExtractFromBlob(blob,aStream,aContentType);}elseif(aBodyInit.IsFormData()){nsFormData&form=aBodyInit.GetAsFormData();returnExtractFromFormData(form,aStream,aContentType);}elseif(aBodyInit.IsUSVString()){nsAutoStringstr;str.Assign(aBodyInit.GetAsUSVString());returnExtractFromUSVString(str,aStream,aContentType);}elseif(aBodyInit.IsURLSearchParams()){URLSearchParams¶ms=aBodyInit.GetAsURLSearchParams();returnExtractFromURLSearchParams(params,aStream,aContentType);}NS_NOTREACHED("Should never reach here");returnNS_ERROR_FAILURE;}nsresultExtractByteStreamFromBody(constArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams&aBodyInit,nsIInputStream**aStream,nsCString&aContentType){MOZ_ASSERT(aStream);if(aBodyInit.IsArrayBuffer()){constArrayBuffer&buf=aBodyInit.GetAsArrayBuffer();returnExtractFromArrayBuffer(buf,aStream);}elseif(aBodyInit.IsArrayBufferView()){constArrayBufferView&buf=aBodyInit.GetAsArrayBufferView();returnExtractFromArrayBufferView(buf,aStream);}elseif(aBodyInit.IsBlob()){constBlob&blob=aBodyInit.GetAsBlob();returnExtractFromBlob(blob,aStream,aContentType);}elseif(aBodyInit.IsFormData()){nsFormData&form=aBodyInit.GetAsFormData();returnExtractFromFormData(form,aStream,aContentType);}elseif(aBodyInit.IsUSVString()){nsAutoStringstr;str.Assign(aBodyInit.GetAsUSVString());returnExtractFromUSVString(str,aStream,aContentType);}elseif(aBodyInit.IsURLSearchParams()){URLSearchParams¶ms=aBodyInit.GetAsURLSearchParams();returnExtractFromURLSearchParams(params,aStream,aContentType);}NS_NOTREACHED("Should never reach here");returnNS_ERROR_FAILURE;}namespace{classStreamDecoderfinal{nsCOMPtr<nsIUnicodeDecoder>mDecoder;nsStringmDecoded;public:StreamDecoder():mDecoder(EncodingUtils::DecoderForEncoding("UTF-8")){MOZ_ASSERT(mDecoder);}nsresultAppendText(constchar*aSrcBuffer,uint32_taSrcBufferLen){int32_tdestBufferLen;nsresultrv=mDecoder->GetMaxLength(aSrcBuffer,aSrcBufferLen,&destBufferLen);if(NS_WARN_IF(NS_FAILED(rv))){returnrv;}if(!mDecoded.SetCapacity(mDecoded.Length()+destBufferLen,fallible)){returnNS_ERROR_OUT_OF_MEMORY;}char16_t*destBuffer=mDecoded.BeginWriting()+mDecoded.Length();int32_ttotalChars=mDecoded.Length();int32_tsrcLen=(int32_t)aSrcBufferLen;int32_toutLen=destBufferLen;rv=mDecoder->Convert(aSrcBuffer,&srcLen,destBuffer,&outLen);MOZ_ASSERT(NS_SUCCEEDED(rv));totalChars+=outLen;mDecoded.SetLength(totalChars);returnNS_OK;}nsString&GetText(){returnmDecoded;}};/* * Called on successfully reading the complete stream. */template<classDerived>classContinueConsumeBodyRunnablefinal:publicWorkerRunnable{// This has been addrefed before this runnable is dispatched,// released in WorkerRun().FetchBody<Derived>*mFetchBody;nsresultmStatus;uint32_tmLength;uint8_t*mResult;public:ContinueConsumeBodyRunnable(FetchBody<Derived>*aFetchBody,nsresultaStatus,uint32_taLength,uint8_t*aResult):WorkerRunnable(aFetchBody->mWorkerPrivate,WorkerThreadModifyBusyCount),mFetchBody(aFetchBody),mStatus(aStatus),mLength(aLength),mResult(aResult){MOZ_ASSERT(NS_IsMainThread());}boolWorkerRun(JSContext*aCx,WorkerPrivate*aWorkerPrivate)override{mFetchBody->ContinueConsumeBody(mStatus,mLength,mResult);returntrue;}};// OnStreamComplete always adopts the buffer, utility class to release it in// a couple of places.classMOZ_STACK_CLASSAutoFreeBufferfinal{uint8_t*mBuffer;public:explicitAutoFreeBuffer(uint8_t*aBuffer):mBuffer(aBuffer){}~AutoFreeBuffer(){free(mBuffer);}voidReset(){mBuffer=nullptr;}};template<classDerived>classFailConsumeBodyWorkerRunnable:publicMainThreadWorkerControlRunnable{FetchBody<Derived>*mBody;public:explicitFailConsumeBodyWorkerRunnable(FetchBody<Derived>*aBody):MainThreadWorkerControlRunnable(aBody->mWorkerPrivate),mBody(aBody){AssertIsOnMainThread();}boolWorkerRun(JSContext*aCx,WorkerPrivate*aWorkerPrivate)override{mBody->ContinueConsumeBody(NS_ERROR_FAILURE,0,nullptr);returntrue;}};/* * In case of failure to create a stream pump or dispatch stream completion to * worker, ensure we cleanup properly. Thread agnostic. */template<classDerived>classMOZ_STACK_CLASSAutoFailConsumeBodyfinal{FetchBody<Derived>*mBody;public:explicitAutoFailConsumeBody(FetchBody<Derived>*aBody):mBody(aBody){}~AutoFailConsumeBody(){AssertIsOnMainThread();if(mBody){if(mBody->mWorkerPrivate){nsRefPtr<FailConsumeBodyWorkerRunnable<Derived>>r=newFailConsumeBodyWorkerRunnable<Derived>(mBody);AutoSafeJSContextcx;if(!r->Dispatch(cx)){MOZ_CRASH("We are going to leak");}}else{mBody->ContinueConsumeBody(NS_ERROR_FAILURE,0,nullptr);}}}voidDontFail(){mBody=nullptr;}};template<classDerived>classConsumeBodyDoneObserver:publicnsIStreamLoaderObserver{FetchBody<Derived>*mFetchBody;public:NS_DECL_THREADSAFE_ISUPPORTSexplicitConsumeBodyDoneObserver(FetchBody<Derived>*aFetchBody):mFetchBody(aFetchBody){}NS_IMETHODOnStreamComplete(nsIStreamLoader*aLoader,nsISupports*aCtxt,nsresultaStatus,uint32_taResultLength,constuint8_t*aResult)override{MOZ_ASSERT(NS_IsMainThread());// If the binding requested cancel, we don't need to call// ContinueConsumeBody, since that is the originator.if(aStatus==NS_BINDING_ABORTED){returnNS_OK;}uint8_t*nonconstResult=const_cast<uint8_t*>(aResult);if(mFetchBody->mWorkerPrivate){// This way if the runnable dispatch fails, the body is still released.AutoFailConsumeBody<Derived>autoFail(mFetchBody);nsRefPtr<ContinueConsumeBodyRunnable<Derived>>r=newContinueConsumeBodyRunnable<Derived>(mFetchBody,aStatus,aResultLength,nonconstResult);AutoSafeJSContextcx;if(r->Dispatch(cx)){autoFail.DontFail();}else{NS_WARNING("Could not dispatch ConsumeBodyRunnable");// Return failure so that aResult is freed.returnNS_ERROR_FAILURE;}}else{mFetchBody->ContinueConsumeBody(aStatus,aResultLength,nonconstResult);}// FetchBody is responsible for data.returnNS_SUCCESS_ADOPTED_DATA;}private:virtual~ConsumeBodyDoneObserver(){}};template<classDerived>NS_IMPL_ADDREF(ConsumeBodyDoneObserver<Derived>)template<classDerived>NS_IMPL_RELEASE(ConsumeBodyDoneObserver<Derived>)template<classDerived>NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver<Derived>)NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIStreamLoaderObserver)NS_INTERFACE_MAP_ENDtemplate<classDerived>classBeginConsumeBodyRunnablefinal:publicnsRunnable{FetchBody<Derived>*mFetchBody;public:explicitBeginConsumeBodyRunnable(FetchBody<Derived>*aBody):mFetchBody(aBody){}NS_IMETHODRun()override{mFetchBody->BeginConsumeBodyMainThread();returnNS_OK;}};template<classDerived>classCancelPumpRunnablefinal:publicWorkerMainThreadRunnable{FetchBody<Derived>*mBody;public:explicitCancelPumpRunnable(FetchBody<Derived>*aBody):WorkerMainThreadRunnable(aBody->mWorkerPrivate),mBody(aBody){}boolMainThreadRun()override{mBody->CancelPump();returntrue;}};}// anonymous namespacetemplate<classDerived>classFetchBodyFeaturefinal:publicworkers::WorkerFeature{// This is addrefed before the feature is created, and is released in ContinueConsumeBody()// so we can hold a rawptr.FetchBody<Derived>*mBody;public:explicitFetchBodyFeature(FetchBody<Derived>*aBody):mBody(aBody){}~FetchBodyFeature(){}boolNotify(JSContext*aCx,workers::StatusaStatus)override{MOZ_ASSERT(aStatus>workers::Running);mBody->ContinueConsumeBody(NS_BINDING_ABORTED,0,nullptr);returntrue;}};template<classDerived>FetchBody<Derived>::FetchBody():mFeature(nullptr),mBodyUsed(false),mReadDone(false){if(!NS_IsMainThread()){mWorkerPrivate=GetCurrentThreadWorkerPrivate();MOZ_ASSERT(mWorkerPrivate);}else{mWorkerPrivate=nullptr;}}templateFetchBody<Request>::FetchBody();templateFetchBody<Response>::FetchBody();template<classDerived>FetchBody<Derived>::~FetchBody(){}// Returns true if addref succeeded.// Always succeeds on main thread.// May fail on worker if RegisterFeature() fails. In that case, it will release// the object before returning false.template<classDerived>boolFetchBody<Derived>::AddRefObject(){AssertIsOnTargetThread();DerivedClass()->AddRef();if(mWorkerPrivate&&!mFeature){if(!RegisterFeature()){ReleaseObject();returnfalse;}}returntrue;}template<classDerived>voidFetchBody<Derived>::ReleaseObject(){AssertIsOnTargetThread();if(mWorkerPrivate&&mFeature){UnregisterFeature();}DerivedClass()->Release();}template<classDerived>boolFetchBody<Derived>::RegisterFeature(){MOZ_ASSERT(mWorkerPrivate);mWorkerPrivate->AssertIsOnWorkerThread();MOZ_ASSERT(!mFeature);mFeature=newFetchBodyFeature<Derived>(this);if(!mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(),mFeature)){NS_WARNING("Failed to add feature");mFeature=nullptr;returnfalse;}returntrue;}template<classDerived>voidFetchBody<Derived>::UnregisterFeature(){MOZ_ASSERT(mWorkerPrivate);mWorkerPrivate->AssertIsOnWorkerThread();MOZ_ASSERT(mFeature);mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(),mFeature);mFeature=nullptr;}template<classDerived>voidFetchBody<Derived>::CancelPump(){AssertIsOnMainThread();MOZ_ASSERT(mConsumeBodyPump);mConsumeBodyPump->Cancel(NS_BINDING_ABORTED);}// Return value is used by ConsumeBody to bubble the error code up to WebIDL so// mConsumePromise doesn't have to be rejected on early exit.template<classDerived>nsresultFetchBody<Derived>::BeginConsumeBody(){AssertIsOnTargetThread();MOZ_ASSERT(!mFeature);MOZ_ASSERT(mConsumePromise);// The FetchBody is not thread-safe refcounted. We addref it here and release// it once the stream read is finished.if(!AddRefObject()){returnNS_ERROR_FAILURE;}nsCOMPtr<nsIRunnable>r=newBeginConsumeBodyRunnable<Derived>(this);nsresultrv=NS_DispatchToMainThread(r);if(NS_WARN_IF(NS_FAILED(rv))){ReleaseObject();returnrv;}returnNS_OK;}/* * BeginConsumeBodyMainThread() will automatically reject the consume promise * and clean up on any failures, so there is no need for callers to do so, * reflected in a lack of error return code. */template<classDerived>voidFetchBody<Derived>::BeginConsumeBodyMainThread(){AssertIsOnMainThread();AutoFailConsumeBody<Derived>autoReject(DerivedClass());nsresultrv;nsCOMPtr<nsIInputStream>stream;DerivedClass()->GetBody(getter_AddRefs(stream));if(!stream){rv=NS_NewCStringInputStream(getter_AddRefs(stream),EmptyCString());if(NS_WARN_IF(NS_FAILED(rv))){return;}}nsCOMPtr<nsIInputStreamPump>pump;rv=NS_NewInputStreamPump(getter_AddRefs(pump),stream);if(NS_WARN_IF(NS_FAILED(rv))){return;}nsRefPtr<ConsumeBodyDoneObserver<Derived>>p=newConsumeBodyDoneObserver<Derived>(this);nsCOMPtr<nsIStreamLoader>loader;rv=NS_NewStreamLoader(getter_AddRefs(loader),p);if(NS_WARN_IF(NS_FAILED(rv))){return;}rv=pump->AsyncRead(loader,nullptr);if(NS_WARN_IF(NS_FAILED(rv))){return;}// Now that everything succeeded, we can assign the pump to a pointer that// stays alive for the lifetime of the FetchBody.mConsumeBodyPump=newnsMainThreadPtrHolder<nsIInputStreamPump>(pump);// It is ok for retargeting to fail and reads to happen on the main thread.autoReject.DontFail();// Try to retarget, otherwise fall back to main thread.nsCOMPtr<nsIThreadRetargetableRequest>rr=do_QueryInterface(pump);if(rr){nsCOMPtr<nsIEventTarget>sts=do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);rv=rr->RetargetDeliveryTo(sts);if(NS_WARN_IF(NS_FAILED(rv))){NS_WARNING("Retargeting failed");}}}template<classDerived>voidFetchBody<Derived>::ContinueConsumeBody(nsresultaStatus,uint32_taResultLength,uint8_t*aResult){AssertIsOnTargetThread();// Just a precaution to ensure ContinueConsumeBody is not called out of// sync with a body read.MOZ_ASSERT(mBodyUsed);MOZ_ASSERT(!mReadDone);MOZ_ASSERT_IF(mWorkerPrivate,mFeature);mReadDone=true;AutoFreeBufferautoFree(aResult);MOZ_ASSERT(mConsumePromise);nsRefPtr<Promise>localPromise=mConsumePromise.forget();nsRefPtr<Derived>kungfuDeathGrip=DerivedClass();ReleaseObject();if(NS_WARN_IF(NS_FAILED(aStatus))){localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);// If binding aborted, cancel the pump. We can't assert mConsumeBodyPump.// In the (admittedly rare) situation that BeginConsumeBodyMainThread()// context switches out, and the worker thread gets canceled before the// pump is setup, mConsumeBodyPump will be null.// We've to use the !! form since non-main thread pointer access on// a nsMainThreadPtrHandle is not permitted.if(aStatus==NS_BINDING_ABORTED&&!!mConsumeBodyPump){if(NS_IsMainThread()){CancelPump();}else{MOZ_ASSERT(mWorkerPrivate);// In case of worker thread, we block the worker while the request is// canceled on the main thread. This ensures that OnStreamComplete has// a valid FetchBody around to call CancelPump and we don't release the// FetchBody on the main thread.nsRefPtr<CancelPumpRunnable<Derived>>r=newCancelPumpRunnable<Derived>(this);if(!r->Dispatch(mWorkerPrivate->GetJSContext())){NS_WARNING("Could not dispatch CancelPumpRunnable. Nothing we can do here");}}}}// Release the pump and then early exit if there was an error.// Uses NS_ProxyRelease internally, so this is safe.mConsumeBodyPump=nullptr;// Don't warn here since we warned above.if(NS_FAILED(aStatus)){return;}// Finish successfully consuming body according to type.MOZ_ASSERT(aResult);AutoJSAPIjsapi;jsapi.Init(DerivedClass()->GetParentObject());JSContext*cx=jsapi.cx();switch(mConsumeType){caseCONSUME_ARRAYBUFFER:{JS::Rooted<JSObject*>arrayBuffer(cx);arrayBuffer=JS_NewArrayBufferWithContents(cx,aResultLength,reinterpret_cast<void*>(aResult));if(!arrayBuffer){JS_ClearPendingException(cx);localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);NS_WARNING("OUT OF MEMORY");return;}JS::Rooted<JS::Value>val(cx);val.setObjectOrNull(arrayBuffer);localPromise->MaybeResolve(cx,val);// ArrayBuffer takes over ownership.autoFree.Reset();return;}caseCONSUME_BLOB:{nsRefPtr<dom::Blob>blob=Blob::CreateMemoryBlob(DerivedClass()->GetParentObject(),reinterpret_cast<void*>(aResult),aResultLength,NS_ConvertUTF8toUTF16(mMimeType));if(!blob){localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);return;}localPromise->MaybeResolve(blob);// File takes over ownership.autoFree.Reset();return;}caseCONSUME_FORMDATA:{nsCStringdata;data.Adopt(reinterpret_cast<char*>(aResult),aResultLength);autoFree.Reset();NS_NAMED_LITERAL_CSTRING(formDataMimeType,"multipart/form-data");// Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=// but disallow multipart/form-datafoobar.boolisValidFormDataMimeType=StringBeginsWith(mMimeType,formDataMimeType);if(isValidFormDataMimeType&&mMimeType.Length()>formDataMimeType.Length()){isValidFormDataMimeType=mMimeType[formDataMimeType.Length()]==';';}if(isValidFormDataMimeType){FormDataParserparser(mMimeType,data,DerivedClass()->GetParentObject());if(!parser.Parse()){ErrorResultresult;result.ThrowTypeError(MSG_BAD_FORMDATA);localPromise->MaybeReject(result);return;}nsRefPtr<nsFormData>fd=parser.FormData();MOZ_ASSERT(fd);localPromise->MaybeResolve(fd);}else{NS_NAMED_LITERAL_CSTRING(urlDataMimeType,"application/x-www-form-urlencoded");boolisValidUrlEncodedMimeType=StringBeginsWith(mMimeType,urlDataMimeType);if(isValidUrlEncodedMimeType&&mMimeType.Length()>urlDataMimeType.Length()){isValidUrlEncodedMimeType=mMimeType[urlDataMimeType.Length()]==';';}if(isValidUrlEncodedMimeType){nsRefPtr<URLSearchParams>params=newURLSearchParams();params->ParseInput(data,/* aObserver */nullptr);nsRefPtr<nsFormData>fd=newnsFormData(DerivedClass()->GetParentObject());params->ForEach(FillFormData,static_cast<void*>(fd));localPromise->MaybeResolve(fd);}else{ErrorResultresult;result.ThrowTypeError(MSG_BAD_FORMDATA);localPromise->MaybeReject(result);}}return;}caseCONSUME_TEXT:// fall through handles early exit.caseCONSUME_JSON:{StreamDecoderdecoder;decoder.AppendText(reinterpret_cast<char*>(aResult),aResultLength);nsString&decoded=decoder.GetText();if(mConsumeType==CONSUME_TEXT){localPromise->MaybeResolve(decoded);return;}AutoForceSetExceptionOnContextforceExn(cx);JS::Rooted<JS::Value>json(cx);if(!JS_ParseJSON(cx,decoded.get(),decoded.Length(),&json)){if(!JS_IsExceptionPending(cx)){localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);return;}JS::Rooted<JS::Value>exn(cx);DebugOnly<bool>gotException=JS_GetPendingException(cx,&exn);MOZ_ASSERT(gotException);JS_ClearPendingException(cx);localPromise->MaybeReject(cx,exn);return;}localPromise->MaybeResolve(cx,json);return;}}NS_NOTREACHED("Unexpected consume body type");}template<classDerived>already_AddRefed<Promise>FetchBody<Derived>::ConsumeBody(ConsumeTypeaType,ErrorResult&aRv){mConsumeType=aType;if(BodyUsed()){aRv.ThrowTypeError(MSG_FETCH_BODY_CONSUMED_ERROR);returnnullptr;}SetBodyUsed();mConsumePromise=Promise::Create(DerivedClass()->GetParentObject(),aRv);if(aRv.Failed()){returnnullptr;}aRv=BeginConsumeBody();if(NS_WARN_IF(aRv.Failed())){mConsumePromise=nullptr;returnnullptr;}nsRefPtr<Promise>promise=mConsumePromise;returnpromise.forget();}templatealready_AddRefed<Promise>FetchBody<Request>::ConsumeBody(ConsumeTypeaType,ErrorResult&aRv);templatealready_AddRefed<Promise>FetchBody<Response>::ConsumeBody(ConsumeTypeaType,ErrorResult&aRv);template<classDerived>voidFetchBody<Derived>::SetMimeType(){// Extract mime type.ErrorResultresult;nsTArray<nsCString>contentTypeValues;MOZ_ASSERT(DerivedClass()->GetInternalHeaders());DerivedClass()->GetInternalHeaders()->GetAll(NS_LITERAL_CSTRING("Content-Type"),contentTypeValues,result);MOZ_ALWAYS_TRUE(!result.Failed());// HTTP ABNF states Content-Type may have only one value.// This is from the "parse a header value" of the fetch spec.if(contentTypeValues.Length()==1){mMimeType=contentTypeValues[0];ToLowerCase(mMimeType);}}templatevoidFetchBody<Request>::SetMimeType();templatevoidFetchBody<Response>::SetMimeType();}// namespace dom}// namespace mozilla