michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsDOMFileReader.h" michael@0: michael@0: #include "nsContentCID.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "nsDOMFile.h" michael@0: #include "nsError.h" michael@0: #include "nsIConverterInputStream.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIFileStreams.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIMIMEService.h" michael@0: #include "nsIUnicodeDecoder.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: #include "nsLayoutCID.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsIURI.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsJSEnvironment.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsCExternalHandlerService.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsHostObjectProtocolHandler.h" michael@0: #include "mozilla/Base64.h" michael@0: #include "mozilla/DOMEventTargetHelper.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/EncodingUtils.h" michael@0: #include "mozilla/dom/FileReaderBinding.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsDOMJSUtils.h" michael@0: michael@0: #include "jsfriendapi.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: #define LOAD_STR "load" michael@0: #define LOADSTART_STR "loadstart" michael@0: #define LOADEND_STR "loadend" michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMFileReader) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMFileReader, michael@0: FileIOObject) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFile) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMFileReader, michael@0: FileIOObject) michael@0: tmp->mResultArrayBuffer = nullptr; michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mFile) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsDOMFileReader, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMFileReader) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMFileReader) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END_INHERITING(FileIOObject) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsDOMFileReader, FileIOObject) michael@0: NS_IMPL_RELEASE_INHERITED(nsDOMFileReader, FileIOObject) michael@0: michael@0: NS_IMPL_EVENT_HANDLER(nsDOMFileReader, load) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMFileReader, loadend) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMFileReader, loadstart) michael@0: NS_IMPL_FORWARD_EVENT_HANDLER(nsDOMFileReader, abort, FileIOObject) michael@0: NS_IMPL_FORWARD_EVENT_HANDLER(nsDOMFileReader, progress, FileIOObject) michael@0: NS_IMPL_FORWARD_EVENT_HANDLER(nsDOMFileReader, error, FileIOObject) michael@0: michael@0: void michael@0: nsDOMFileReader::RootResultArrayBuffer() michael@0: { michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: //nsDOMFileReader constructors/initializers michael@0: michael@0: nsDOMFileReader::nsDOMFileReader() michael@0: : mFileData(nullptr), michael@0: mDataLen(0), mDataFormat(FILE_AS_BINARY), michael@0: mResultArrayBuffer(nullptr) michael@0: { michael@0: SetDOMStringToNull(mResult); michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsDOMFileReader::~nsDOMFileReader() michael@0: { michael@0: FreeFileData(); michael@0: mResultArrayBuffer = nullptr; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * This Init method is called from the factory constructor. michael@0: */ michael@0: nsresult michael@0: nsDOMFileReader::Init() michael@0: { michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: nsCOMPtr principal; michael@0: if (secMan) { michael@0: secMan->GetSystemPrincipal(getter_AddRefs(principal)); michael@0: } michael@0: NS_ENSURE_STATE(principal); michael@0: mPrincipal.swap(principal); michael@0: michael@0: // Instead of grabbing some random global from the context stack, michael@0: // let's use the default one (junk scope) for now. michael@0: // We should move away from this Init... michael@0: nsCOMPtr global = xpc::GetJunkScopeGlobal(); michael@0: NS_ENSURE_TRUE(global, NS_ERROR_FAILURE); michael@0: BindToOwner(global); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: nsDOMFileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) michael@0: { michael@0: nsRefPtr fileReader = new nsDOMFileReader(); michael@0: michael@0: nsCOMPtr owner = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!owner) { michael@0: NS_WARNING("Unexpected nsIJSNativeInitializer owner"); michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: michael@0: fileReader->BindToOwner(owner); michael@0: michael@0: // This object is bound to a |window|, michael@0: // so reset the principal. michael@0: nsCOMPtr scriptPrincipal = do_QueryInterface(owner); michael@0: if (!scriptPrincipal) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: fileReader->mPrincipal = scriptPrincipal->GetPrincipal(); michael@0: return fileReader.forget(); michael@0: } michael@0: michael@0: // nsIInterfaceRequestor michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: // nsIDOMFileReader michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::GetReadyState(uint16_t *aReadyState) michael@0: { michael@0: *aReadyState = ReadyState(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsDOMFileReader::GetResult(JSContext* aCx, JS::MutableHandle aResult, michael@0: ErrorResult& aRv) michael@0: { michael@0: aRv = GetResult(aCx, aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::GetResult(JSContext* aCx, JS::MutableHandle aResult) michael@0: { michael@0: JS::Rooted result(aCx); michael@0: if (mDataFormat == FILE_AS_ARRAYBUFFER) { michael@0: if (mReadyState == nsIDOMFileReader::DONE && mResultArrayBuffer) { michael@0: result.setObject(*mResultArrayBuffer); michael@0: } else { michael@0: result.setNull(); michael@0: } michael@0: if (!JS_WrapValue(aCx, &result)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: aResult.set(result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsString tmpResult = mResult; michael@0: if (!xpc::StringToJsval(aCx, tmpResult, aResult)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::GetError(nsISupports** aError) michael@0: { michael@0: NS_IF_ADDREF(*aError = GetError()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::ReadAsArrayBuffer(nsIDOMBlob* aFile, JSContext* aCx) michael@0: { michael@0: NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER); michael@0: ErrorResult rv; michael@0: ReadAsArrayBuffer(aCx, aFile, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::ReadAsBinaryString(nsIDOMBlob* aFile) michael@0: { michael@0: NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER); michael@0: ErrorResult rv; michael@0: ReadAsBinaryString(aFile, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::ReadAsText(nsIDOMBlob* aFile, michael@0: const nsAString &aCharset) michael@0: { michael@0: NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER); michael@0: ErrorResult rv; michael@0: ReadAsText(aFile, aCharset, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::ReadAsDataURL(nsIDOMBlob* aFile) michael@0: { michael@0: NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER); michael@0: ErrorResult rv; michael@0: ReadAsDataURL(aFile, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMFileReader::Abort() michael@0: { michael@0: ErrorResult rv; michael@0: FileIOObject::Abort(rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsDOMFileReader::DoAbort(nsAString& aEvent) michael@0: { michael@0: // Revert status and result attributes michael@0: SetDOMStringToNull(mResult); michael@0: mResultArrayBuffer = nullptr; michael@0: michael@0: // Non-null channel indicates a read is currently active michael@0: if (mChannel) { michael@0: // Cancel request requires an error status michael@0: mChannel->Cancel(NS_ERROR_FAILURE); michael@0: mChannel = nullptr; michael@0: } michael@0: mFile = nullptr; michael@0: michael@0: //Clean up memory buffer michael@0: FreeFileData(); michael@0: michael@0: // Tell the base class which event to dispatch michael@0: aEvent = NS_LITERAL_STRING(LOADEND_STR); michael@0: } michael@0: michael@0: static michael@0: NS_METHOD michael@0: ReadFuncBinaryString(nsIInputStream* in, michael@0: void* closure, michael@0: const char* fromRawSegment, michael@0: uint32_t toOffset, michael@0: uint32_t count, michael@0: uint32_t *writeCount) michael@0: { michael@0: char16_t* dest = static_cast(closure) + toOffset; michael@0: char16_t* end = dest + count; michael@0: const unsigned char* source = (const unsigned char*)fromRawSegment; michael@0: while (dest != end) { michael@0: *dest = *source; michael@0: ++dest; michael@0: ++source; michael@0: } michael@0: *writeCount = count; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMFileReader::DoOnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aInputStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: if (mDataFormat == FILE_AS_BINARY) { michael@0: //Continuously update our binary string as data comes in michael@0: NS_ASSERTION(mResult.Length() == aOffset, michael@0: "unexpected mResult length"); michael@0: uint32_t oldLen = mResult.Length(); michael@0: if (uint64_t(oldLen) + aCount > UINT32_MAX) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: char16_t *buf = nullptr; michael@0: mResult.GetMutableData(&buf, oldLen + aCount, fallible_t()); michael@0: NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: uint32_t bytesRead = 0; michael@0: aInputStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount, michael@0: &bytesRead); michael@0: NS_ASSERTION(bytesRead == aCount, "failed to read data"); michael@0: } michael@0: else if (mDataFormat == FILE_AS_ARRAYBUFFER) { michael@0: uint32_t bytesRead = 0; michael@0: aInputStream->Read((char*)JS_GetArrayBufferData(mResultArrayBuffer) + aOffset, michael@0: aCount, &bytesRead); michael@0: NS_ASSERTION(bytesRead == aCount, "failed to read data"); michael@0: } michael@0: else { michael@0: //Update memory buffer to reflect the contents of the file michael@0: if (aOffset + aCount > UINT32_MAX) { michael@0: // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: mFileData = (char *)moz_realloc(mFileData, aOffset + aCount); michael@0: NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: uint32_t bytesRead = 0; michael@0: aInputStream->Read(mFileData + aOffset, aCount, &bytesRead); michael@0: NS_ASSERTION(bytesRead == aCount, "failed to read data"); michael@0: michael@0: mDataLen += aCount; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMFileReader::DoOnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatus, michael@0: nsAString& aSuccessEvent, michael@0: nsAString& aTerminationEvent) michael@0: { michael@0: // Make sure we drop all the objects that could hold files open now. michael@0: nsCOMPtr channel; michael@0: mChannel.swap(channel); michael@0: michael@0: nsCOMPtr file; michael@0: mFile.swap(file); michael@0: michael@0: aSuccessEvent = NS_LITERAL_STRING(LOAD_STR); michael@0: aTerminationEvent = NS_LITERAL_STRING(LOADEND_STR); michael@0: michael@0: // Clear out the data if necessary michael@0: if (NS_FAILED(aStatus)) { michael@0: FreeFileData(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: switch (mDataFormat) { michael@0: case FILE_AS_ARRAYBUFFER: michael@0: break; //Already accumulated mResultArrayBuffer michael@0: case FILE_AS_BINARY: michael@0: break; //Already accumulated mResult michael@0: case FILE_AS_TEXT: michael@0: if (!mFileData) { michael@0: if (mDataLen) { michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: break; michael@0: } michael@0: rv = GetAsText(file, mCharset, "", mDataLen, mResult); michael@0: break; michael@0: } michael@0: rv = GetAsText(file, mCharset, mFileData, mDataLen, mResult); michael@0: break; michael@0: case FILE_AS_DATAURL: michael@0: rv = GetAsDataURL(file, mFileData, mDataLen, mResult); michael@0: break; michael@0: } michael@0: michael@0: mResult.SetIsVoid(false); michael@0: michael@0: FreeFileData(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Helper methods michael@0: michael@0: void michael@0: nsDOMFileReader::ReadFileContent(JSContext* aCx, michael@0: nsIDOMBlob* aFile, michael@0: const nsAString &aCharset, michael@0: eDataFormat aDataFormat, michael@0: ErrorResult& aRv) michael@0: { michael@0: MOZ_ASSERT(aFile); michael@0: michael@0: //Implicit abort to clear any other activity going on michael@0: Abort(); michael@0: mError = nullptr; michael@0: SetDOMStringToNull(mResult); michael@0: mTransferred = 0; michael@0: mTotal = 0; michael@0: mReadyState = nsIDOMFileReader::EMPTY; michael@0: FreeFileData(); michael@0: michael@0: mFile = aFile; michael@0: mDataFormat = aDataFormat; michael@0: CopyUTF16toUTF8(aCharset, mCharset); michael@0: michael@0: //Establish a channel with our file michael@0: { michael@0: // Hold the internal URL alive only as long as necessary michael@0: // After the channel is created it will own whatever is backing michael@0: // the DOMFile. michael@0: nsDOMFileInternalUrlHolder urlHolder(mFile, mPrincipal); michael@0: michael@0: nsCOMPtr uri; michael@0: aRv = NS_NewURI(getter_AddRefs(uri), urlHolder.mUrl); michael@0: NS_ENSURE_SUCCESS_VOID(aRv.ErrorCode()); michael@0: michael@0: nsCOMPtr loadGroup; michael@0: if (HasOrHasHadOwner()) { michael@0: if (!GetOwner()) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return; michael@0: } michael@0: nsIDocument* doc = GetOwner()->GetExtantDoc(); michael@0: if (doc) { michael@0: loadGroup = doc->GetDocumentLoadGroup(); michael@0: } michael@0: } michael@0: michael@0: aRv = NS_NewChannel(getter_AddRefs(mChannel), uri, nullptr, loadGroup, michael@0: nullptr, nsIRequest::LOAD_BACKGROUND); michael@0: NS_ENSURE_SUCCESS_VOID(aRv.ErrorCode()); michael@0: } michael@0: michael@0: //Obtain the total size of the file before reading michael@0: mTotal = mozilla::dom::kUnknownSize; michael@0: mFile->GetSize(&mTotal); michael@0: michael@0: aRv = mChannel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS_VOID(aRv.ErrorCode()); michael@0: michael@0: //FileReader should be in loading state here michael@0: mReadyState = nsIDOMFileReader::LOADING; michael@0: DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR)); michael@0: michael@0: if (mDataFormat == FILE_AS_ARRAYBUFFER) { michael@0: RootResultArrayBuffer(); michael@0: mResultArrayBuffer = JS_NewArrayBuffer(aCx, mTotal); michael@0: if (!mResultArrayBuffer) { michael@0: NS_WARNING("Failed to create JS array buffer"); michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMFileReader::GetAsText(nsIDOMBlob *aFile, michael@0: const nsACString &aCharset, michael@0: const char *aFileData, michael@0: uint32_t aDataLen, michael@0: nsAString& aResult) michael@0: { michael@0: // The BOM sniffing is baked into the "decode" part of the Encoding michael@0: // Standard, which the File API references. michael@0: nsAutoCString encoding; michael@0: if (!nsContentUtils::CheckForBOM( michael@0: reinterpret_cast(aFileData), michael@0: aDataLen, michael@0: encoding)) { michael@0: // BOM sniffing failed. Try the API argument. michael@0: if (!EncodingUtils::FindEncodingForLabel(aCharset, michael@0: encoding)) { michael@0: // API argument failed. Try the type property of the blob. michael@0: nsAutoString type16; michael@0: aFile->GetType(type16); michael@0: NS_ConvertUTF16toUTF8 type(type16); michael@0: nsAutoCString specifiedCharset; michael@0: bool haveCharset; michael@0: int32_t charsetStart, charsetEnd; michael@0: NS_ExtractCharsetFromContentType(type, michael@0: specifiedCharset, michael@0: &haveCharset, michael@0: &charsetStart, michael@0: &charsetEnd); michael@0: if (!EncodingUtils::FindEncodingForLabel(specifiedCharset, encoding)) { michael@0: // Type property failed. Use UTF-8. michael@0: encoding.AssignLiteral("UTF-8"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsDependentCSubstring data(aFileData, aDataLen); michael@0: return nsContentUtils::ConvertStringFromEncoding(encoding, data, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMFileReader::GetAsDataURL(nsIDOMBlob *aFile, michael@0: const char *aFileData, michael@0: uint32_t aDataLen, michael@0: nsAString& aResult) michael@0: { michael@0: aResult.AssignLiteral("data:"); michael@0: michael@0: nsresult rv; michael@0: nsString contentType; michael@0: rv = aFile->GetType(contentType); michael@0: if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) { michael@0: aResult.Append(contentType); michael@0: } else { michael@0: aResult.AppendLiteral("application/octet-stream"); michael@0: } michael@0: aResult.AppendLiteral(";base64,"); michael@0: michael@0: nsCString encodedData; michael@0: rv = Base64Encode(Substring(aFileData, aDataLen), encodedData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!AppendASCIItoUTF16(encodedData, aResult, fallible_t())) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ JSObject* michael@0: nsDOMFileReader::WrapObject(JSContext* aCx) michael@0: { michael@0: return FileReaderBinding::Wrap(aCx, this); michael@0: }