Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsDOMFileReader.h"
8 #include "nsContentCID.h"
9 #include "nsContentUtils.h"
10 #include "nsDOMClassInfoID.h"
11 #include "nsDOMFile.h"
12 #include "nsError.h"
13 #include "nsIConverterInputStream.h"
14 #include "nsIDocument.h"
15 #include "nsIFile.h"
16 #include "nsIFileStreams.h"
17 #include "nsIInputStream.h"
18 #include "nsIMIMEService.h"
19 #include "nsIUnicodeDecoder.h"
20 #include "nsNetCID.h"
21 #include "nsNetUtil.h"
23 #include "nsLayoutCID.h"
24 #include "nsXPIDLString.h"
25 #include "nsReadableUtils.h"
26 #include "nsIURI.h"
27 #include "nsStreamUtils.h"
28 #include "nsXPCOM.h"
29 #include "nsIDOMEventListener.h"
30 #include "nsJSEnvironment.h"
31 #include "nsIScriptGlobalObject.h"
32 #include "nsCExternalHandlerService.h"
33 #include "nsIStreamConverterService.h"
34 #include "nsCycleCollectionParticipant.h"
35 #include "nsIScriptObjectPrincipal.h"
36 #include "nsHostObjectProtocolHandler.h"
37 #include "mozilla/Base64.h"
38 #include "mozilla/DOMEventTargetHelper.h"
39 #include "mozilla/Preferences.h"
40 #include "mozilla/dom/EncodingUtils.h"
41 #include "mozilla/dom/FileReaderBinding.h"
42 #include "xpcpublic.h"
43 #include "nsIScriptSecurityManager.h"
44 #include "nsDOMJSUtils.h"
46 #include "jsfriendapi.h"
48 using namespace mozilla;
49 using namespace mozilla::dom;
51 #define LOAD_STR "load"
52 #define LOADSTART_STR "loadstart"
53 #define LOADEND_STR "loadend"
55 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMFileReader)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMFileReader,
58 FileIOObject)
59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFile)
60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMFileReader,
64 FileIOObject)
65 tmp->mResultArrayBuffer = nullptr;
66 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFile)
67 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
68 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
71 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsDOMFileReader,
72 DOMEventTargetHelper)
73 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
74 NS_IMPL_CYCLE_COLLECTION_TRACE_END
76 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMFileReader)
77 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
78 NS_INTERFACE_MAP_ENTRY(nsIDOMFileReader)
79 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
80 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
81 NS_INTERFACE_MAP_END_INHERITING(FileIOObject)
83 NS_IMPL_ADDREF_INHERITED(nsDOMFileReader, FileIOObject)
84 NS_IMPL_RELEASE_INHERITED(nsDOMFileReader, FileIOObject)
86 NS_IMPL_EVENT_HANDLER(nsDOMFileReader, load)
87 NS_IMPL_EVENT_HANDLER(nsDOMFileReader, loadend)
88 NS_IMPL_EVENT_HANDLER(nsDOMFileReader, loadstart)
89 NS_IMPL_FORWARD_EVENT_HANDLER(nsDOMFileReader, abort, FileIOObject)
90 NS_IMPL_FORWARD_EVENT_HANDLER(nsDOMFileReader, progress, FileIOObject)
91 NS_IMPL_FORWARD_EVENT_HANDLER(nsDOMFileReader, error, FileIOObject)
93 void
94 nsDOMFileReader::RootResultArrayBuffer()
95 {
96 mozilla::HoldJSObjects(this);
97 }
99 //nsDOMFileReader constructors/initializers
101 nsDOMFileReader::nsDOMFileReader()
102 : mFileData(nullptr),
103 mDataLen(0), mDataFormat(FILE_AS_BINARY),
104 mResultArrayBuffer(nullptr)
105 {
106 SetDOMStringToNull(mResult);
107 SetIsDOMBinding();
108 }
110 nsDOMFileReader::~nsDOMFileReader()
111 {
112 FreeFileData();
113 mResultArrayBuffer = nullptr;
114 mozilla::DropJSObjects(this);
115 }
118 /**
119 * This Init method is called from the factory constructor.
120 */
121 nsresult
122 nsDOMFileReader::Init()
123 {
124 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
125 nsCOMPtr<nsIPrincipal> principal;
126 if (secMan) {
127 secMan->GetSystemPrincipal(getter_AddRefs(principal));
128 }
129 NS_ENSURE_STATE(principal);
130 mPrincipal.swap(principal);
132 // Instead of grabbing some random global from the context stack,
133 // let's use the default one (junk scope) for now.
134 // We should move away from this Init...
135 nsCOMPtr<nsIGlobalObject> global = xpc::GetJunkScopeGlobal();
136 NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
137 BindToOwner(global);
138 return NS_OK;
139 }
141 /* static */ already_AddRefed<nsDOMFileReader>
142 nsDOMFileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
143 {
144 nsRefPtr<nsDOMFileReader> fileReader = new nsDOMFileReader();
146 nsCOMPtr<nsPIDOMWindow> owner = do_QueryInterface(aGlobal.GetAsSupports());
147 if (!owner) {
148 NS_WARNING("Unexpected nsIJSNativeInitializer owner");
149 aRv.Throw(NS_ERROR_FAILURE);
150 return nullptr;
151 }
153 fileReader->BindToOwner(owner);
155 // This object is bound to a |window|,
156 // so reset the principal.
157 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal = do_QueryInterface(owner);
158 if (!scriptPrincipal) {
159 aRv.Throw(NS_ERROR_FAILURE);
160 return nullptr;
161 }
162 fileReader->mPrincipal = scriptPrincipal->GetPrincipal();
163 return fileReader.forget();
164 }
166 // nsIInterfaceRequestor
168 NS_IMETHODIMP
169 nsDOMFileReader::GetInterface(const nsIID & aIID, void **aResult)
170 {
171 return QueryInterface(aIID, aResult);
172 }
174 // nsIDOMFileReader
176 NS_IMETHODIMP
177 nsDOMFileReader::GetReadyState(uint16_t *aReadyState)
178 {
179 *aReadyState = ReadyState();
180 return NS_OK;
181 }
183 void
184 nsDOMFileReader::GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
185 ErrorResult& aRv)
186 {
187 aRv = GetResult(aCx, aResult);
188 }
190 NS_IMETHODIMP
191 nsDOMFileReader::GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult)
192 {
193 JS::Rooted<JS::Value> result(aCx);
194 if (mDataFormat == FILE_AS_ARRAYBUFFER) {
195 if (mReadyState == nsIDOMFileReader::DONE && mResultArrayBuffer) {
196 result.setObject(*mResultArrayBuffer);
197 } else {
198 result.setNull();
199 }
200 if (!JS_WrapValue(aCx, &result)) {
201 return NS_ERROR_FAILURE;
202 }
203 aResult.set(result);
204 return NS_OK;
205 }
207 nsString tmpResult = mResult;
208 if (!xpc::StringToJsval(aCx, tmpResult, aResult)) {
209 return NS_ERROR_FAILURE;
210 }
211 return NS_OK;
212 }
214 NS_IMETHODIMP
215 nsDOMFileReader::GetError(nsISupports** aError)
216 {
217 NS_IF_ADDREF(*aError = GetError());
218 return NS_OK;
219 }
221 NS_IMETHODIMP
222 nsDOMFileReader::ReadAsArrayBuffer(nsIDOMBlob* aFile, JSContext* aCx)
223 {
224 NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER);
225 ErrorResult rv;
226 ReadAsArrayBuffer(aCx, aFile, rv);
227 return rv.ErrorCode();
228 }
230 NS_IMETHODIMP
231 nsDOMFileReader::ReadAsBinaryString(nsIDOMBlob* aFile)
232 {
233 NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER);
234 ErrorResult rv;
235 ReadAsBinaryString(aFile, rv);
236 return rv.ErrorCode();
237 }
239 NS_IMETHODIMP
240 nsDOMFileReader::ReadAsText(nsIDOMBlob* aFile,
241 const nsAString &aCharset)
242 {
243 NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER);
244 ErrorResult rv;
245 ReadAsText(aFile, aCharset, rv);
246 return rv.ErrorCode();
247 }
249 NS_IMETHODIMP
250 nsDOMFileReader::ReadAsDataURL(nsIDOMBlob* aFile)
251 {
252 NS_ENSURE_TRUE(aFile, NS_ERROR_NULL_POINTER);
253 ErrorResult rv;
254 ReadAsDataURL(aFile, rv);
255 return rv.ErrorCode();
256 }
258 NS_IMETHODIMP
259 nsDOMFileReader::Abort()
260 {
261 ErrorResult rv;
262 FileIOObject::Abort(rv);
263 return rv.ErrorCode();
264 }
266 /* virtual */ void
267 nsDOMFileReader::DoAbort(nsAString& aEvent)
268 {
269 // Revert status and result attributes
270 SetDOMStringToNull(mResult);
271 mResultArrayBuffer = nullptr;
273 // Non-null channel indicates a read is currently active
274 if (mChannel) {
275 // Cancel request requires an error status
276 mChannel->Cancel(NS_ERROR_FAILURE);
277 mChannel = nullptr;
278 }
279 mFile = nullptr;
281 //Clean up memory buffer
282 FreeFileData();
284 // Tell the base class which event to dispatch
285 aEvent = NS_LITERAL_STRING(LOADEND_STR);
286 }
288 static
289 NS_METHOD
290 ReadFuncBinaryString(nsIInputStream* in,
291 void* closure,
292 const char* fromRawSegment,
293 uint32_t toOffset,
294 uint32_t count,
295 uint32_t *writeCount)
296 {
297 char16_t* dest = static_cast<char16_t*>(closure) + toOffset;
298 char16_t* end = dest + count;
299 const unsigned char* source = (const unsigned char*)fromRawSegment;
300 while (dest != end) {
301 *dest = *source;
302 ++dest;
303 ++source;
304 }
305 *writeCount = count;
307 return NS_OK;
308 }
310 nsresult
311 nsDOMFileReader::DoOnDataAvailable(nsIRequest *aRequest,
312 nsISupports *aContext,
313 nsIInputStream *aInputStream,
314 uint64_t aOffset,
315 uint32_t aCount)
316 {
317 if (mDataFormat == FILE_AS_BINARY) {
318 //Continuously update our binary string as data comes in
319 NS_ASSERTION(mResult.Length() == aOffset,
320 "unexpected mResult length");
321 uint32_t oldLen = mResult.Length();
322 if (uint64_t(oldLen) + aCount > UINT32_MAX)
323 return NS_ERROR_OUT_OF_MEMORY;
325 char16_t *buf = nullptr;
326 mResult.GetMutableData(&buf, oldLen + aCount, fallible_t());
327 NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
329 uint32_t bytesRead = 0;
330 aInputStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
331 &bytesRead);
332 NS_ASSERTION(bytesRead == aCount, "failed to read data");
333 }
334 else if (mDataFormat == FILE_AS_ARRAYBUFFER) {
335 uint32_t bytesRead = 0;
336 aInputStream->Read((char*)JS_GetArrayBufferData(mResultArrayBuffer) + aOffset,
337 aCount, &bytesRead);
338 NS_ASSERTION(bytesRead == aCount, "failed to read data");
339 }
340 else {
341 //Update memory buffer to reflect the contents of the file
342 if (aOffset + aCount > UINT32_MAX) {
343 // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
344 return NS_ERROR_OUT_OF_MEMORY;
345 }
346 mFileData = (char *)moz_realloc(mFileData, aOffset + aCount);
347 NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
349 uint32_t bytesRead = 0;
350 aInputStream->Read(mFileData + aOffset, aCount, &bytesRead);
351 NS_ASSERTION(bytesRead == aCount, "failed to read data");
353 mDataLen += aCount;
354 }
356 return NS_OK;
357 }
359 nsresult
360 nsDOMFileReader::DoOnStopRequest(nsIRequest *aRequest,
361 nsISupports *aContext,
362 nsresult aStatus,
363 nsAString& aSuccessEvent,
364 nsAString& aTerminationEvent)
365 {
366 // Make sure we drop all the objects that could hold files open now.
367 nsCOMPtr<nsIChannel> channel;
368 mChannel.swap(channel);
370 nsCOMPtr<nsIDOMBlob> file;
371 mFile.swap(file);
373 aSuccessEvent = NS_LITERAL_STRING(LOAD_STR);
374 aTerminationEvent = NS_LITERAL_STRING(LOADEND_STR);
376 // Clear out the data if necessary
377 if (NS_FAILED(aStatus)) {
378 FreeFileData();
379 return NS_OK;
380 }
382 nsresult rv = NS_OK;
383 switch (mDataFormat) {
384 case FILE_AS_ARRAYBUFFER:
385 break; //Already accumulated mResultArrayBuffer
386 case FILE_AS_BINARY:
387 break; //Already accumulated mResult
388 case FILE_AS_TEXT:
389 if (!mFileData) {
390 if (mDataLen) {
391 rv = NS_ERROR_OUT_OF_MEMORY;
392 break;
393 }
394 rv = GetAsText(file, mCharset, "", mDataLen, mResult);
395 break;
396 }
397 rv = GetAsText(file, mCharset, mFileData, mDataLen, mResult);
398 break;
399 case FILE_AS_DATAURL:
400 rv = GetAsDataURL(file, mFileData, mDataLen, mResult);
401 break;
402 }
404 mResult.SetIsVoid(false);
406 FreeFileData();
408 return rv;
409 }
411 // Helper methods
413 void
414 nsDOMFileReader::ReadFileContent(JSContext* aCx,
415 nsIDOMBlob* aFile,
416 const nsAString &aCharset,
417 eDataFormat aDataFormat,
418 ErrorResult& aRv)
419 {
420 MOZ_ASSERT(aFile);
422 //Implicit abort to clear any other activity going on
423 Abort();
424 mError = nullptr;
425 SetDOMStringToNull(mResult);
426 mTransferred = 0;
427 mTotal = 0;
428 mReadyState = nsIDOMFileReader::EMPTY;
429 FreeFileData();
431 mFile = aFile;
432 mDataFormat = aDataFormat;
433 CopyUTF16toUTF8(aCharset, mCharset);
435 //Establish a channel with our file
436 {
437 // Hold the internal URL alive only as long as necessary
438 // After the channel is created it will own whatever is backing
439 // the DOMFile.
440 nsDOMFileInternalUrlHolder urlHolder(mFile, mPrincipal);
442 nsCOMPtr<nsIURI> uri;
443 aRv = NS_NewURI(getter_AddRefs(uri), urlHolder.mUrl);
444 NS_ENSURE_SUCCESS_VOID(aRv.ErrorCode());
446 nsCOMPtr<nsILoadGroup> loadGroup;
447 if (HasOrHasHadOwner()) {
448 if (!GetOwner()) {
449 aRv.Throw(NS_ERROR_FAILURE);
450 return;
451 }
452 nsIDocument* doc = GetOwner()->GetExtantDoc();
453 if (doc) {
454 loadGroup = doc->GetDocumentLoadGroup();
455 }
456 }
458 aRv = NS_NewChannel(getter_AddRefs(mChannel), uri, nullptr, loadGroup,
459 nullptr, nsIRequest::LOAD_BACKGROUND);
460 NS_ENSURE_SUCCESS_VOID(aRv.ErrorCode());
461 }
463 //Obtain the total size of the file before reading
464 mTotal = mozilla::dom::kUnknownSize;
465 mFile->GetSize(&mTotal);
467 aRv = mChannel->AsyncOpen(this, nullptr);
468 NS_ENSURE_SUCCESS_VOID(aRv.ErrorCode());
470 //FileReader should be in loading state here
471 mReadyState = nsIDOMFileReader::LOADING;
472 DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
474 if (mDataFormat == FILE_AS_ARRAYBUFFER) {
475 RootResultArrayBuffer();
476 mResultArrayBuffer = JS_NewArrayBuffer(aCx, mTotal);
477 if (!mResultArrayBuffer) {
478 NS_WARNING("Failed to create JS array buffer");
479 aRv.Throw(NS_ERROR_FAILURE);
480 }
481 }
482 }
484 nsresult
485 nsDOMFileReader::GetAsText(nsIDOMBlob *aFile,
486 const nsACString &aCharset,
487 const char *aFileData,
488 uint32_t aDataLen,
489 nsAString& aResult)
490 {
491 // The BOM sniffing is baked into the "decode" part of the Encoding
492 // Standard, which the File API references.
493 nsAutoCString encoding;
494 if (!nsContentUtils::CheckForBOM(
495 reinterpret_cast<const unsigned char *>(aFileData),
496 aDataLen,
497 encoding)) {
498 // BOM sniffing failed. Try the API argument.
499 if (!EncodingUtils::FindEncodingForLabel(aCharset,
500 encoding)) {
501 // API argument failed. Try the type property of the blob.
502 nsAutoString type16;
503 aFile->GetType(type16);
504 NS_ConvertUTF16toUTF8 type(type16);
505 nsAutoCString specifiedCharset;
506 bool haveCharset;
507 int32_t charsetStart, charsetEnd;
508 NS_ExtractCharsetFromContentType(type,
509 specifiedCharset,
510 &haveCharset,
511 &charsetStart,
512 &charsetEnd);
513 if (!EncodingUtils::FindEncodingForLabel(specifiedCharset, encoding)) {
514 // Type property failed. Use UTF-8.
515 encoding.AssignLiteral("UTF-8");
516 }
517 }
518 }
520 nsDependentCSubstring data(aFileData, aDataLen);
521 return nsContentUtils::ConvertStringFromEncoding(encoding, data, aResult);
522 }
524 nsresult
525 nsDOMFileReader::GetAsDataURL(nsIDOMBlob *aFile,
526 const char *aFileData,
527 uint32_t aDataLen,
528 nsAString& aResult)
529 {
530 aResult.AssignLiteral("data:");
532 nsresult rv;
533 nsString contentType;
534 rv = aFile->GetType(contentType);
535 if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) {
536 aResult.Append(contentType);
537 } else {
538 aResult.AppendLiteral("application/octet-stream");
539 }
540 aResult.AppendLiteral(";base64,");
542 nsCString encodedData;
543 rv = Base64Encode(Substring(aFileData, aDataLen), encodedData);
544 NS_ENSURE_SUCCESS(rv, rv);
546 if (!AppendASCIItoUTF16(encodedData, aResult, fallible_t())) {
547 return NS_ERROR_OUT_OF_MEMORY;
548 }
550 return NS_OK;
551 }
553 /* virtual */ JSObject*
554 nsDOMFileReader::WrapObject(JSContext* aCx)
555 {
556 return FileReaderBinding::Wrap(aCx, this);
557 }