|
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/. */ |
|
5 |
|
6 #include "nsDOMBlobBuilder.h" |
|
7 #include "jsfriendapi.h" |
|
8 #include "mozilla/dom/BlobBinding.h" |
|
9 #include "mozilla/dom/FileBinding.h" |
|
10 #include "nsAutoPtr.h" |
|
11 #include "nsDOMClassInfoID.h" |
|
12 #include "nsIMultiplexInputStream.h" |
|
13 #include "nsStringStream.h" |
|
14 #include "nsTArray.h" |
|
15 #include "nsJSUtils.h" |
|
16 #include "nsContentUtils.h" |
|
17 #include "nsIScriptError.h" |
|
18 #include "nsIXPConnect.h" |
|
19 #include <algorithm> |
|
20 |
|
21 using namespace mozilla; |
|
22 using namespace mozilla::dom; |
|
23 |
|
24 NS_IMPL_ISUPPORTS_INHERITED(nsDOMMultipartFile, nsDOMFile, |
|
25 nsIJSNativeInitializer) |
|
26 |
|
27 NS_IMETHODIMP |
|
28 nsDOMMultipartFile::GetSize(uint64_t* aLength) |
|
29 { |
|
30 if (mLength == UINT64_MAX) { |
|
31 CheckedUint64 length = 0; |
|
32 |
|
33 uint32_t i; |
|
34 uint32_t len = mBlobs.Length(); |
|
35 for (i = 0; i < len; i++) { |
|
36 nsIDOMBlob* blob = mBlobs.ElementAt(i).get(); |
|
37 uint64_t l = 0; |
|
38 |
|
39 nsresult rv = blob->GetSize(&l); |
|
40 NS_ENSURE_SUCCESS(rv, rv); |
|
41 |
|
42 length += l; |
|
43 } |
|
44 |
|
45 NS_ENSURE_TRUE(length.isValid(), NS_ERROR_FAILURE); |
|
46 |
|
47 mLength = length.value(); |
|
48 } |
|
49 |
|
50 *aLength = mLength; |
|
51 return NS_OK; |
|
52 } |
|
53 |
|
54 NS_IMETHODIMP |
|
55 nsDOMMultipartFile::GetInternalStream(nsIInputStream** aStream) |
|
56 { |
|
57 nsresult rv; |
|
58 *aStream = nullptr; |
|
59 |
|
60 nsCOMPtr<nsIMultiplexInputStream> stream = |
|
61 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); |
|
62 NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); |
|
63 |
|
64 uint32_t i; |
|
65 for (i = 0; i < mBlobs.Length(); i++) { |
|
66 nsCOMPtr<nsIInputStream> scratchStream; |
|
67 nsIDOMBlob* blob = mBlobs.ElementAt(i).get(); |
|
68 |
|
69 rv = blob->GetInternalStream(getter_AddRefs(scratchStream)); |
|
70 NS_ENSURE_SUCCESS(rv, rv); |
|
71 |
|
72 rv = stream->AppendStream(scratchStream); |
|
73 NS_ENSURE_SUCCESS(rv, rv); |
|
74 } |
|
75 |
|
76 return CallQueryInterface(stream, aStream); |
|
77 } |
|
78 |
|
79 already_AddRefed<nsIDOMBlob> |
|
80 nsDOMMultipartFile::CreateSlice(uint64_t aStart, uint64_t aLength, |
|
81 const nsAString& aContentType) |
|
82 { |
|
83 // If we clamped to nothing we create an empty blob |
|
84 nsTArray<nsCOMPtr<nsIDOMBlob> > blobs; |
|
85 |
|
86 uint64_t length = aLength; |
|
87 uint64_t skipStart = aStart; |
|
88 |
|
89 // Prune the list of blobs if we can |
|
90 uint32_t i; |
|
91 for (i = 0; length && skipStart && i < mBlobs.Length(); i++) { |
|
92 nsIDOMBlob* blob = mBlobs[i].get(); |
|
93 |
|
94 uint64_t l; |
|
95 nsresult rv = blob->GetSize(&l); |
|
96 NS_ENSURE_SUCCESS(rv, nullptr); |
|
97 |
|
98 if (skipStart < l) { |
|
99 uint64_t upperBound = std::min<uint64_t>(l - skipStart, length); |
|
100 |
|
101 nsCOMPtr<nsIDOMBlob> firstBlob; |
|
102 rv = blob->Slice(skipStart, skipStart + upperBound, |
|
103 aContentType, 3, |
|
104 getter_AddRefs(firstBlob)); |
|
105 NS_ENSURE_SUCCESS(rv, nullptr); |
|
106 |
|
107 // Avoid wrapping a single blob inside an nsDOMMultipartFile |
|
108 if (length == upperBound) { |
|
109 return firstBlob.forget(); |
|
110 } |
|
111 |
|
112 blobs.AppendElement(firstBlob); |
|
113 length -= upperBound; |
|
114 i++; |
|
115 break; |
|
116 } |
|
117 skipStart -= l; |
|
118 } |
|
119 |
|
120 // Now append enough blobs until we're done |
|
121 for (; length && i < mBlobs.Length(); i++) { |
|
122 nsIDOMBlob* blob = mBlobs[i].get(); |
|
123 |
|
124 uint64_t l; |
|
125 nsresult rv = blob->GetSize(&l); |
|
126 NS_ENSURE_SUCCESS(rv, nullptr); |
|
127 |
|
128 if (length < l) { |
|
129 nsCOMPtr<nsIDOMBlob> lastBlob; |
|
130 rv = blob->Slice(0, length, aContentType, 3, |
|
131 getter_AddRefs(lastBlob)); |
|
132 NS_ENSURE_SUCCESS(rv, nullptr); |
|
133 |
|
134 blobs.AppendElement(lastBlob); |
|
135 } else { |
|
136 blobs.AppendElement(blob); |
|
137 } |
|
138 length -= std::min<uint64_t>(l, length); |
|
139 } |
|
140 |
|
141 // we can create our blob now |
|
142 nsCOMPtr<nsIDOMBlob> blob = new nsDOMMultipartFile(blobs, aContentType); |
|
143 return blob.forget(); |
|
144 } |
|
145 |
|
146 /* static */ nsresult |
|
147 nsDOMMultipartFile::NewFile(const nsAString& aName, nsISupports* *aNewObject) |
|
148 { |
|
149 nsCOMPtr<nsISupports> file = |
|
150 do_QueryObject(new nsDOMMultipartFile(aName)); |
|
151 file.forget(aNewObject); |
|
152 return NS_OK; |
|
153 } |
|
154 |
|
155 /* static */ nsresult |
|
156 nsDOMMultipartFile::NewBlob(nsISupports* *aNewObject) |
|
157 { |
|
158 nsCOMPtr<nsISupports> file = do_QueryObject(new nsDOMMultipartFile()); |
|
159 file.forget(aNewObject); |
|
160 return NS_OK; |
|
161 } |
|
162 |
|
163 static nsIDOMBlob* |
|
164 GetXPConnectNative(JSContext* aCx, JSObject* aObj) { |
|
165 nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface( |
|
166 nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, aObj)); |
|
167 return blob; |
|
168 } |
|
169 |
|
170 NS_IMETHODIMP |
|
171 nsDOMMultipartFile::Initialize(nsISupports* aOwner, |
|
172 JSContext* aCx, |
|
173 JSObject* aObj, |
|
174 const JS::CallArgs& aArgs) |
|
175 { |
|
176 if (!mIsFile) { |
|
177 return InitBlob(aCx, aArgs.length(), aArgs.array(), GetXPConnectNative); |
|
178 } |
|
179 |
|
180 if (!nsContentUtils::IsCallerChrome()) { |
|
181 return InitFile(aCx, aArgs.length(), aArgs.array()); |
|
182 } |
|
183 |
|
184 if (aArgs.length() > 0) { |
|
185 JS::Value* argv = aArgs.array(); |
|
186 if (argv[0].isObject()) { |
|
187 JS::Rooted<JSObject*> obj(aCx, &argv[0].toObject()); |
|
188 if (JS_IsArrayObject(aCx, obj)) { |
|
189 return InitFile(aCx, aArgs.length(), aArgs.array()); |
|
190 } |
|
191 } |
|
192 } |
|
193 |
|
194 return InitChromeFile(aCx, aArgs.length(), aArgs.array()); |
|
195 } |
|
196 |
|
197 nsresult |
|
198 nsDOMMultipartFile::InitBlob(JSContext* aCx, |
|
199 uint32_t aArgc, |
|
200 JS::Value* aArgv, |
|
201 UnwrapFuncPtr aUnwrapFunc) |
|
202 { |
|
203 bool nativeEOL = false; |
|
204 if (aArgc > 1) { |
|
205 BlobPropertyBag d; |
|
206 if (!d.Init(aCx, JS::Handle<JS::Value>::fromMarkedLocation(&aArgv[1]))) { |
|
207 return NS_ERROR_TYPE_ERR; |
|
208 } |
|
209 mContentType = d.mType; |
|
210 nativeEOL = d.mEndings == EndingTypes::Native; |
|
211 } |
|
212 |
|
213 if (aArgc > 0) { |
|
214 return ParseBlobArrayArgument(aCx, aArgv[0], nativeEOL, aUnwrapFunc); |
|
215 } |
|
216 |
|
217 return NS_OK; |
|
218 } |
|
219 |
|
220 nsresult |
|
221 nsDOMMultipartFile::ParseBlobArrayArgument(JSContext* aCx, JS::Value& aValue, |
|
222 bool aNativeEOL, |
|
223 UnwrapFuncPtr aUnwrapFunc) |
|
224 { |
|
225 if (!aValue.isObject()) { |
|
226 return NS_ERROR_TYPE_ERR; // We're not interested |
|
227 } |
|
228 |
|
229 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject()); |
|
230 if (!JS_IsArrayObject(aCx, obj)) { |
|
231 return NS_ERROR_TYPE_ERR; // We're not interested |
|
232 } |
|
233 |
|
234 BlobSet blobSet; |
|
235 |
|
236 uint32_t length; |
|
237 MOZ_ALWAYS_TRUE(JS_GetArrayLength(aCx, obj, &length)); |
|
238 for (uint32_t i = 0; i < length; ++i) { |
|
239 JS::Rooted<JS::Value> element(aCx); |
|
240 if (!JS_GetElement(aCx, obj, i, &element)) |
|
241 return NS_ERROR_TYPE_ERR; |
|
242 |
|
243 if (element.isObject()) { |
|
244 JS::Rooted<JSObject*> obj(aCx, &element.toObject()); |
|
245 nsCOMPtr<nsIDOMBlob> blob = aUnwrapFunc(aCx, obj); |
|
246 if (blob) { |
|
247 // Flatten so that multipart blobs will never nest |
|
248 nsDOMFileBase* file = static_cast<nsDOMFileBase*>( |
|
249 static_cast<nsIDOMBlob*>(blob)); |
|
250 const nsTArray<nsCOMPtr<nsIDOMBlob> >* |
|
251 subBlobs = file->GetSubBlobs(); |
|
252 if (subBlobs) { |
|
253 blobSet.AppendBlobs(*subBlobs); |
|
254 } else { |
|
255 blobSet.AppendBlob(blob); |
|
256 } |
|
257 continue; |
|
258 } |
|
259 if (JS_IsArrayBufferViewObject(obj)) { |
|
260 nsresult rv = blobSet.AppendVoidPtr( |
|
261 JS_GetArrayBufferViewData(obj), |
|
262 JS_GetArrayBufferViewByteLength(obj)); |
|
263 NS_ENSURE_SUCCESS(rv, rv); |
|
264 continue; |
|
265 } |
|
266 if (JS_IsArrayBufferObject(obj)) { |
|
267 nsresult rv = blobSet.AppendArrayBuffer(obj); |
|
268 NS_ENSURE_SUCCESS(rv, rv); |
|
269 continue; |
|
270 } |
|
271 } |
|
272 |
|
273 // coerce it to a string |
|
274 JSString* str = JS::ToString(aCx, element); |
|
275 NS_ENSURE_TRUE(str, NS_ERROR_TYPE_ERR); |
|
276 |
|
277 nsresult rv = blobSet.AppendString(str, aNativeEOL, aCx); |
|
278 NS_ENSURE_SUCCESS(rv, rv); |
|
279 } |
|
280 |
|
281 mBlobs = blobSet.GetBlobs(); |
|
282 return NS_OK; |
|
283 } |
|
284 |
|
285 NS_IMETHODIMP |
|
286 nsDOMMultipartFile::GetMozFullPathInternal(nsAString &aFilename) |
|
287 { |
|
288 if (!mIsFromNsiFile || mBlobs.Length() == 0) { |
|
289 return nsDOMFile::GetMozFullPathInternal(aFilename); |
|
290 } |
|
291 |
|
292 nsIDOMBlob* blob = mBlobs.ElementAt(0).get(); |
|
293 nsDOMFileFile* file = static_cast<nsDOMFileFile*>(blob); |
|
294 if (!file) { |
|
295 return nsDOMFile::GetMozFullPathInternal(aFilename); |
|
296 } |
|
297 |
|
298 return file->GetMozFullPathInternal(aFilename); |
|
299 } |
|
300 |
|
301 nsresult |
|
302 nsDOMMultipartFile::InitChromeFile(JSContext* aCx, |
|
303 uint32_t aArgc, |
|
304 JS::Value* aArgv) |
|
305 { |
|
306 nsresult rv; |
|
307 |
|
308 NS_ASSERTION(!mImmutable, "Something went wrong ..."); |
|
309 NS_ENSURE_TRUE(!mImmutable, NS_ERROR_UNEXPECTED); |
|
310 MOZ_ASSERT(nsContentUtils::IsCallerChrome()); |
|
311 NS_ENSURE_TRUE(aArgc > 0, NS_ERROR_UNEXPECTED); |
|
312 |
|
313 if (aArgc > 1) { |
|
314 FilePropertyBag d; |
|
315 if (!d.Init(aCx, JS::Handle<JS::Value>::fromMarkedLocation(&aArgv[1]))) { |
|
316 return NS_ERROR_TYPE_ERR; |
|
317 } |
|
318 mName = d.mName; |
|
319 mContentType = d.mType; |
|
320 } |
|
321 |
|
322 |
|
323 // We expect to get a path to represent as a File object or |
|
324 // Blob object, an nsIFile, or an nsIDOMFile. |
|
325 nsCOMPtr<nsIFile> file; |
|
326 nsCOMPtr<nsIDOMBlob> blob; |
|
327 if (!aArgv[0].isString()) { |
|
328 // Lets see if it's an nsIFile |
|
329 if (!aArgv[0].isObject()) { |
|
330 return NS_ERROR_UNEXPECTED; // We're not interested |
|
331 } |
|
332 |
|
333 JSObject* obj = &aArgv[0].toObject(); |
|
334 |
|
335 nsISupports* supports = |
|
336 nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, obj); |
|
337 if (!supports) { |
|
338 return NS_ERROR_UNEXPECTED; |
|
339 } |
|
340 |
|
341 blob = do_QueryInterface(supports); |
|
342 file = do_QueryInterface(supports); |
|
343 if (!blob && !file) { |
|
344 return NS_ERROR_UNEXPECTED; |
|
345 } |
|
346 |
|
347 mIsFromNsiFile = true; |
|
348 } else { |
|
349 // It's a string |
|
350 JSString* str = JS::ToString(aCx, JS::Handle<JS::Value>::fromMarkedLocation(&aArgv[0])); |
|
351 NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); |
|
352 |
|
353 nsDependentJSString xpcomStr; |
|
354 if (!xpcomStr.init(aCx, str)) { |
|
355 return NS_ERROR_XPC_BAD_CONVERT_JS; |
|
356 } |
|
357 |
|
358 rv = NS_NewLocalFile(xpcomStr, false, getter_AddRefs(file)); |
|
359 NS_ENSURE_SUCCESS(rv, rv); |
|
360 } |
|
361 |
|
362 if (file) { |
|
363 bool exists; |
|
364 rv = file->Exists(&exists); |
|
365 NS_ENSURE_SUCCESS(rv, rv); |
|
366 NS_ENSURE_TRUE(exists, NS_ERROR_FILE_NOT_FOUND); |
|
367 |
|
368 bool isDir; |
|
369 rv = file->IsDirectory(&isDir); |
|
370 NS_ENSURE_SUCCESS(rv, rv); |
|
371 NS_ENSURE_FALSE(isDir, NS_ERROR_FILE_IS_DIRECTORY); |
|
372 |
|
373 if (mName.IsEmpty()) { |
|
374 file->GetLeafName(mName); |
|
375 } |
|
376 |
|
377 blob = new nsDOMFileFile(file); |
|
378 } |
|
379 |
|
380 // XXXkhuey this is terrible |
|
381 if (mContentType.IsEmpty()) { |
|
382 blob->GetType(mContentType); |
|
383 } |
|
384 |
|
385 BlobSet blobSet; |
|
386 blobSet.AppendBlob(blob); |
|
387 mBlobs = blobSet.GetBlobs(); |
|
388 |
|
389 return NS_OK; |
|
390 } |
|
391 |
|
392 nsresult |
|
393 nsDOMMultipartFile::InitFile(JSContext* aCx, |
|
394 uint32_t aArgc, |
|
395 JS::Value* aArgv) |
|
396 { |
|
397 NS_ASSERTION(!mImmutable, "Something went wrong ..."); |
|
398 NS_ENSURE_TRUE(!mImmutable, NS_ERROR_UNEXPECTED); |
|
399 |
|
400 if (aArgc < 2) { |
|
401 return NS_ERROR_TYPE_ERR; |
|
402 } |
|
403 |
|
404 // File name |
|
405 JSString* str = JS::ToString(aCx, JS::Handle<JS::Value>::fromMarkedLocation(&aArgv[1])); |
|
406 NS_ENSURE_TRUE(str, NS_ERROR_XPC_BAD_CONVERT_JS); |
|
407 |
|
408 nsDependentJSString xpcomStr; |
|
409 if (!xpcomStr.init(aCx, str)) { |
|
410 return NS_ERROR_XPC_BAD_CONVERT_JS; |
|
411 } |
|
412 |
|
413 mName = xpcomStr; |
|
414 |
|
415 // Optional params |
|
416 bool nativeEOL = false; |
|
417 if (aArgc > 2) { |
|
418 BlobPropertyBag d; |
|
419 if (!d.Init(aCx, JS::Handle<JS::Value>::fromMarkedLocation(&aArgv[2]))) { |
|
420 return NS_ERROR_TYPE_ERR; |
|
421 } |
|
422 mContentType = d.mType; |
|
423 nativeEOL = d.mEndings == EndingTypes::Native; |
|
424 } |
|
425 |
|
426 return ParseBlobArrayArgument(aCx, aArgv[0], nativeEOL, GetXPConnectNative); |
|
427 } |
|
428 |
|
429 nsresult |
|
430 BlobSet::AppendVoidPtr(const void* aData, uint32_t aLength) |
|
431 { |
|
432 NS_ENSURE_ARG_POINTER(aData); |
|
433 |
|
434 uint64_t offset = mDataLen; |
|
435 |
|
436 if (!ExpandBufferSize(aLength)) |
|
437 return NS_ERROR_OUT_OF_MEMORY; |
|
438 |
|
439 memcpy((char*)mData + offset, aData, aLength); |
|
440 return NS_OK; |
|
441 } |
|
442 |
|
443 nsresult |
|
444 BlobSet::AppendString(JSString* aString, bool nativeEOL, JSContext* aCx) |
|
445 { |
|
446 nsDependentJSString xpcomStr; |
|
447 if (!xpcomStr.init(aCx, aString)) { |
|
448 return NS_ERROR_XPC_BAD_CONVERT_JS; |
|
449 } |
|
450 |
|
451 nsCString utf8Str = NS_ConvertUTF16toUTF8(xpcomStr); |
|
452 |
|
453 if (nativeEOL) { |
|
454 if (utf8Str.FindChar('\r') != kNotFound) { |
|
455 utf8Str.ReplaceSubstring("\r\n", "\n"); |
|
456 utf8Str.ReplaceSubstring("\r", "\n"); |
|
457 } |
|
458 #ifdef XP_WIN |
|
459 utf8Str.ReplaceSubstring("\n", "\r\n"); |
|
460 #endif |
|
461 } |
|
462 |
|
463 return AppendVoidPtr((void*)utf8Str.Data(), |
|
464 utf8Str.Length()); |
|
465 } |
|
466 |
|
467 nsresult |
|
468 BlobSet::AppendBlob(nsIDOMBlob* aBlob) |
|
469 { |
|
470 NS_ENSURE_ARG_POINTER(aBlob); |
|
471 |
|
472 Flush(); |
|
473 mBlobs.AppendElement(aBlob); |
|
474 |
|
475 return NS_OK; |
|
476 } |
|
477 |
|
478 nsresult |
|
479 BlobSet::AppendBlobs(const nsTArray<nsCOMPtr<nsIDOMBlob> >& aBlob) |
|
480 { |
|
481 Flush(); |
|
482 mBlobs.AppendElements(aBlob); |
|
483 |
|
484 return NS_OK; |
|
485 } |
|
486 |
|
487 nsresult |
|
488 BlobSet::AppendArrayBuffer(JSObject* aBuffer) |
|
489 { |
|
490 return AppendVoidPtr(JS_GetArrayBufferData(aBuffer), |
|
491 JS_GetArrayBufferByteLength(aBuffer)); |
|
492 } |