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 "nsDOMBlobBuilder.h" michael@0: #include "jsfriendapi.h" michael@0: #include "mozilla/dom/BlobBinding.h" michael@0: #include "mozilla/dom/FileBinding.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "nsIMultiplexInputStream.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsTArray.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIXPConnect.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsDOMMultipartFile, nsDOMFile, michael@0: nsIJSNativeInitializer) michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMMultipartFile::GetSize(uint64_t* aLength) michael@0: { michael@0: if (mLength == UINT64_MAX) { michael@0: CheckedUint64 length = 0; michael@0: michael@0: uint32_t i; michael@0: uint32_t len = mBlobs.Length(); michael@0: for (i = 0; i < len; i++) { michael@0: nsIDOMBlob* blob = mBlobs.ElementAt(i).get(); michael@0: uint64_t l = 0; michael@0: michael@0: nsresult rv = blob->GetSize(&l); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: length += l; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(length.isValid(), NS_ERROR_FAILURE); michael@0: michael@0: mLength = length.value(); michael@0: } michael@0: michael@0: *aLength = mLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMMultipartFile::GetInternalStream(nsIInputStream** aStream) michael@0: { michael@0: nsresult rv; michael@0: *aStream = nullptr; michael@0: michael@0: nsCOMPtr stream = michael@0: do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); michael@0: NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); michael@0: michael@0: uint32_t i; michael@0: for (i = 0; i < mBlobs.Length(); i++) { michael@0: nsCOMPtr scratchStream; michael@0: nsIDOMBlob* blob = mBlobs.ElementAt(i).get(); michael@0: michael@0: rv = blob->GetInternalStream(getter_AddRefs(scratchStream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stream->AppendStream(scratchStream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return CallQueryInterface(stream, aStream); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMMultipartFile::CreateSlice(uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) michael@0: { michael@0: // If we clamped to nothing we create an empty blob michael@0: nsTArray > blobs; michael@0: michael@0: uint64_t length = aLength; michael@0: uint64_t skipStart = aStart; michael@0: michael@0: // Prune the list of blobs if we can michael@0: uint32_t i; michael@0: for (i = 0; length && skipStart && i < mBlobs.Length(); i++) { michael@0: nsIDOMBlob* blob = mBlobs[i].get(); michael@0: michael@0: uint64_t l; michael@0: nsresult rv = blob->GetSize(&l); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: if (skipStart < l) { michael@0: uint64_t upperBound = std::min(l - skipStart, length); michael@0: michael@0: nsCOMPtr firstBlob; michael@0: rv = blob->Slice(skipStart, skipStart + upperBound, michael@0: aContentType, 3, michael@0: getter_AddRefs(firstBlob)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: // Avoid wrapping a single blob inside an nsDOMMultipartFile michael@0: if (length == upperBound) { michael@0: return firstBlob.forget(); michael@0: } michael@0: michael@0: blobs.AppendElement(firstBlob); michael@0: length -= upperBound; michael@0: i++; michael@0: break; michael@0: } michael@0: skipStart -= l; michael@0: } michael@0: michael@0: // Now append enough blobs until we're done michael@0: for (; length && i < mBlobs.Length(); i++) { michael@0: nsIDOMBlob* blob = mBlobs[i].get(); michael@0: michael@0: uint64_t l; michael@0: nsresult rv = blob->GetSize(&l); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: if (length < l) { michael@0: nsCOMPtr lastBlob; michael@0: rv = blob->Slice(0, length, aContentType, 3, michael@0: getter_AddRefs(lastBlob)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: blobs.AppendElement(lastBlob); michael@0: } else { michael@0: blobs.AppendElement(blob); michael@0: } michael@0: length -= std::min(l, length); michael@0: } michael@0: michael@0: // we can create our blob now michael@0: nsCOMPtr blob = new nsDOMMultipartFile(blobs, aContentType); michael@0: return blob.forget(); michael@0: } michael@0: michael@0: /* static */ nsresult michael@0: nsDOMMultipartFile::NewFile(const nsAString& aName, nsISupports* *aNewObject) michael@0: { michael@0: nsCOMPtr file = michael@0: do_QueryObject(new nsDOMMultipartFile(aName)); michael@0: file.forget(aNewObject); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ nsresult michael@0: nsDOMMultipartFile::NewBlob(nsISupports* *aNewObject) michael@0: { michael@0: nsCOMPtr file = do_QueryObject(new nsDOMMultipartFile()); michael@0: file.forget(aNewObject); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsIDOMBlob* michael@0: GetXPConnectNative(JSContext* aCx, JSObject* aObj) { michael@0: nsCOMPtr blob = do_QueryInterface( michael@0: nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, aObj)); michael@0: return blob; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMMultipartFile::Initialize(nsISupports* aOwner, michael@0: JSContext* aCx, michael@0: JSObject* aObj, michael@0: const JS::CallArgs& aArgs) michael@0: { michael@0: if (!mIsFile) { michael@0: return InitBlob(aCx, aArgs.length(), aArgs.array(), GetXPConnectNative); michael@0: } michael@0: michael@0: if (!nsContentUtils::IsCallerChrome()) { michael@0: return InitFile(aCx, aArgs.length(), aArgs.array()); michael@0: } michael@0: michael@0: if (aArgs.length() > 0) { michael@0: JS::Value* argv = aArgs.array(); michael@0: if (argv[0].isObject()) { michael@0: JS::Rooted obj(aCx, &argv[0].toObject()); michael@0: if (JS_IsArrayObject(aCx, obj)) { michael@0: return InitFile(aCx, aArgs.length(), aArgs.array()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return InitChromeFile(aCx, aArgs.length(), aArgs.array()); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMMultipartFile::InitBlob(JSContext* aCx, michael@0: uint32_t aArgc, michael@0: JS::Value* aArgv, michael@0: UnwrapFuncPtr aUnwrapFunc) michael@0: { michael@0: bool nativeEOL = false; michael@0: if (aArgc > 1) { michael@0: BlobPropertyBag d; michael@0: if (!d.Init(aCx, JS::Handle::fromMarkedLocation(&aArgv[1]))) { michael@0: return NS_ERROR_TYPE_ERR; michael@0: } michael@0: mContentType = d.mType; michael@0: nativeEOL = d.mEndings == EndingTypes::Native; michael@0: } michael@0: michael@0: if (aArgc > 0) { michael@0: return ParseBlobArrayArgument(aCx, aArgv[0], nativeEOL, aUnwrapFunc); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMMultipartFile::ParseBlobArrayArgument(JSContext* aCx, JS::Value& aValue, michael@0: bool aNativeEOL, michael@0: UnwrapFuncPtr aUnwrapFunc) michael@0: { michael@0: if (!aValue.isObject()) { michael@0: return NS_ERROR_TYPE_ERR; // We're not interested michael@0: } michael@0: michael@0: JS::Rooted obj(aCx, &aValue.toObject()); michael@0: if (!JS_IsArrayObject(aCx, obj)) { michael@0: return NS_ERROR_TYPE_ERR; // We're not interested michael@0: } michael@0: michael@0: BlobSet blobSet; michael@0: michael@0: uint32_t length; michael@0: MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, obj, &length)); michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: JS::Rooted element(aCx); michael@0: if (!JS_GetElement(aCx, obj, i, &element)) michael@0: return NS_ERROR_TYPE_ERR; michael@0: michael@0: if (element.isObject()) { michael@0: JS::Rooted obj(aCx, &element.toObject()); michael@0: nsCOMPtr blob = aUnwrapFunc(aCx, obj); michael@0: if (blob) { michael@0: // Flatten so that multipart blobs will never nest michael@0: nsDOMFileBase* file = static_cast( michael@0: static_cast(blob)); michael@0: const nsTArray >* michael@0: subBlobs = file->GetSubBlobs(); michael@0: if (subBlobs) { michael@0: blobSet.AppendBlobs(*subBlobs); michael@0: } else { michael@0: blobSet.AppendBlob(blob); michael@0: } michael@0: continue; michael@0: } michael@0: if (JS_IsArrayBufferViewObject(obj)) { michael@0: nsresult rv = blobSet.AppendVoidPtr( michael@0: JS_GetArrayBufferViewData(obj), michael@0: JS_GetArrayBufferViewByteLength(obj)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: continue; michael@0: } michael@0: if (JS_IsArrayBufferObject(obj)) { michael@0: nsresult rv = blobSet.AppendArrayBuffer(obj); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // coerce it to a string michael@0: JSString* str = JS::ToString(aCx, element); michael@0: NS_ENSURE_TRUE(str, NS_ERROR_TYPE_ERR); michael@0: michael@0: nsresult rv = blobSet.AppendString(str, aNativeEOL, aCx); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mBlobs = blobSet.GetBlobs(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMMultipartFile::GetMozFullPathInternal(nsAString &aFilename) michael@0: { michael@0: if (!mIsFromNsiFile || mBlobs.Length() == 0) { michael@0: return nsDOMFile::GetMozFullPathInternal(aFilename); michael@0: } michael@0: michael@0: nsIDOMBlob* blob = mBlobs.ElementAt(0).get(); michael@0: nsDOMFileFile* file = static_cast(blob); michael@0: if (!file) { michael@0: return nsDOMFile::GetMozFullPathInternal(aFilename); michael@0: } michael@0: michael@0: return file->GetMozFullPathInternal(aFilename); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMMultipartFile::InitChromeFile(JSContext* aCx, michael@0: uint32_t aArgc, michael@0: JS::Value* aArgv) michael@0: { michael@0: nsresult rv; michael@0: michael@0: NS_ASSERTION(!mImmutable, "Something went wrong ..."); michael@0: NS_ENSURE_TRUE(!mImmutable, NS_ERROR_UNEXPECTED); michael@0: MOZ_ASSERT(nsContentUtils::IsCallerChrome()); michael@0: NS_ENSURE_TRUE(aArgc > 0, NS_ERROR_UNEXPECTED); michael@0: michael@0: if (aArgc > 1) { michael@0: FilePropertyBag d; michael@0: if (!d.Init(aCx, JS::Handle::fromMarkedLocation(&aArgv[1]))) { michael@0: return NS_ERROR_TYPE_ERR; michael@0: } michael@0: mName = d.mName; michael@0: mContentType = d.mType; michael@0: } michael@0: michael@0: michael@0: // We expect to get a path to represent as a File object or michael@0: // Blob object, an nsIFile, or an nsIDOMFile. michael@0: nsCOMPtr file; michael@0: nsCOMPtr blob; michael@0: if (!aArgv[0].isString()) { michael@0: // Lets see if it's an nsIFile michael@0: if (!aArgv[0].isObject()) { michael@0: return NS_ERROR_UNEXPECTED; // We're not interested michael@0: } michael@0: michael@0: JSObject* obj = &aArgv[0].toObject(); michael@0: michael@0: nsISupports* supports = michael@0: nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, obj); michael@0: if (!supports) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: blob = do_QueryInterface(supports); michael@0: file = do_QueryInterface(supports); michael@0: if (!blob && !file) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: mIsFromNsiFile = true; michael@0: } else { michael@0: // It's a string michael@0: JSString* str = JS::ToString(aCx, JS::Handle::fromMarkedLocation(&aArgv[0])); michael@0: NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); michael@0: michael@0: nsDependentJSString xpcomStr; michael@0: if (!xpcomStr.init(aCx, str)) { michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: michael@0: rv = NS_NewLocalFile(xpcomStr, false, getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (file) { michael@0: bool exists; michael@0: rv = file->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(exists, NS_ERROR_FILE_NOT_FOUND); michael@0: michael@0: bool isDir; michael@0: rv = file->IsDirectory(&isDir); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_FALSE(isDir, NS_ERROR_FILE_IS_DIRECTORY); michael@0: michael@0: if (mName.IsEmpty()) { michael@0: file->GetLeafName(mName); michael@0: } michael@0: michael@0: blob = new nsDOMFileFile(file); michael@0: } michael@0: michael@0: // XXXkhuey this is terrible michael@0: if (mContentType.IsEmpty()) { michael@0: blob->GetType(mContentType); michael@0: } michael@0: michael@0: BlobSet blobSet; michael@0: blobSet.AppendBlob(blob); michael@0: mBlobs = blobSet.GetBlobs(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMMultipartFile::InitFile(JSContext* aCx, michael@0: uint32_t aArgc, michael@0: JS::Value* aArgv) michael@0: { michael@0: NS_ASSERTION(!mImmutable, "Something went wrong ..."); michael@0: NS_ENSURE_TRUE(!mImmutable, NS_ERROR_UNEXPECTED); michael@0: michael@0: if (aArgc < 2) { michael@0: return NS_ERROR_TYPE_ERR; michael@0: } michael@0: michael@0: // File name michael@0: JSString* str = JS::ToString(aCx, JS::Handle::fromMarkedLocation(&aArgv[1])); michael@0: NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); michael@0: michael@0: nsDependentJSString xpcomStr; michael@0: if (!xpcomStr.init(aCx, str)) { michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: michael@0: mName = xpcomStr; michael@0: michael@0: // Optional params michael@0: bool nativeEOL = false; michael@0: if (aArgc > 2) { michael@0: BlobPropertyBag d; michael@0: if (!d.Init(aCx, JS::Handle::fromMarkedLocation(&aArgv[2]))) { michael@0: return NS_ERROR_TYPE_ERR; michael@0: } michael@0: mContentType = d.mType; michael@0: nativeEOL = d.mEndings == EndingTypes::Native; michael@0: } michael@0: michael@0: return ParseBlobArrayArgument(aCx, aArgv[0], nativeEOL, GetXPConnectNative); michael@0: } michael@0: michael@0: nsresult michael@0: BlobSet::AppendVoidPtr(const void* aData, uint32_t aLength) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aData); michael@0: michael@0: uint64_t offset = mDataLen; michael@0: michael@0: if (!ExpandBufferSize(aLength)) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: memcpy((char*)mData + offset, aData, aLength); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: BlobSet::AppendString(JSString* aString, bool nativeEOL, JSContext* aCx) michael@0: { michael@0: nsDependentJSString xpcomStr; michael@0: if (!xpcomStr.init(aCx, aString)) { michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: michael@0: nsCString utf8Str = NS_ConvertUTF16toUTF8(xpcomStr); michael@0: michael@0: if (nativeEOL) { michael@0: if (utf8Str.FindChar('\r') != kNotFound) { michael@0: utf8Str.ReplaceSubstring("\r\n", "\n"); michael@0: utf8Str.ReplaceSubstring("\r", "\n"); michael@0: } michael@0: #ifdef XP_WIN michael@0: utf8Str.ReplaceSubstring("\n", "\r\n"); michael@0: #endif michael@0: } michael@0: michael@0: return AppendVoidPtr((void*)utf8Str.Data(), michael@0: utf8Str.Length()); michael@0: } michael@0: michael@0: nsresult michael@0: BlobSet::AppendBlob(nsIDOMBlob* aBlob) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aBlob); michael@0: michael@0: Flush(); michael@0: mBlobs.AppendElement(aBlob); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: BlobSet::AppendBlobs(const nsTArray >& aBlob) michael@0: { michael@0: Flush(); michael@0: mBlobs.AppendElements(aBlob); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: BlobSet::AppendArrayBuffer(JSObject* aBuffer) michael@0: { michael@0: return AppendVoidPtr(JS_GetArrayBufferData(aBuffer), michael@0: JS_GetArrayBufferByteLength(aBuffer)); michael@0: }