1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla File API.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Kyle Huey <me@kylehuey.com>
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsDOMBlobBuilder.h"
39 : #include "jstypedarray.h"
40 : #include "nsAutoPtr.h"
41 : #include "nsDOMClassInfoID.h"
42 : #include "nsIMultiplexInputStream.h"
43 : #include "nsStringStream.h"
44 : #include "nsTArray.h"
45 : #include "nsJSUtils.h"
46 : #include "nsContentUtils.h"
47 : #include "DictionaryHelpers.h"
48 :
49 : using namespace mozilla;
50 :
51 0 : NS_IMPL_ISUPPORTS_INHERITED1(nsDOMMultipartFile, nsDOMFileBase,
52 : nsIJSNativeInitializer)
53 :
54 : NS_IMETHODIMP
55 0 : nsDOMMultipartFile::GetSize(PRUint64* aLength)
56 : {
57 0 : if (mLength == UINT64_MAX) {
58 0 : CheckedUint64 length = 0;
59 :
60 : PRUint32 i;
61 0 : PRUint32 len = mBlobs.Length();
62 0 : for (i = 0; i < len; i++) {
63 0 : nsIDOMBlob* blob = mBlobs.ElementAt(i).get();
64 0 : PRUint64 l = 0;
65 :
66 0 : nsresult rv = blob->GetSize(&l);
67 0 : NS_ENSURE_SUCCESS(rv, rv);
68 :
69 0 : length += l;
70 : }
71 :
72 0 : NS_ENSURE_TRUE(length.valid(), NS_ERROR_FAILURE);
73 :
74 0 : mLength = length.value();
75 : }
76 :
77 0 : *aLength = mLength;
78 0 : return NS_OK;
79 : }
80 :
81 : NS_IMETHODIMP
82 0 : nsDOMMultipartFile::GetInternalStream(nsIInputStream** aStream)
83 : {
84 : nsresult rv;
85 0 : *aStream = nsnull;
86 :
87 : nsCOMPtr<nsIMultiplexInputStream> stream =
88 0 : do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
89 0 : NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
90 :
91 : PRUint32 i;
92 0 : for (i = 0; i < mBlobs.Length(); i++) {
93 0 : nsCOMPtr<nsIInputStream> scratchStream;
94 0 : nsIDOMBlob* blob = mBlobs.ElementAt(i).get();
95 :
96 0 : rv = blob->GetInternalStream(getter_AddRefs(scratchStream));
97 0 : NS_ENSURE_SUCCESS(rv, rv);
98 :
99 0 : rv = stream->AppendStream(scratchStream);
100 0 : NS_ENSURE_SUCCESS(rv, rv);
101 : }
102 :
103 0 : return CallQueryInterface(stream, aStream);
104 : }
105 :
106 : already_AddRefed<nsIDOMBlob>
107 0 : nsDOMMultipartFile::CreateSlice(PRUint64 aStart, PRUint64 aLength,
108 : const nsAString& aContentType)
109 : {
110 : // If we clamped to nothing we create an empty blob
111 0 : nsTArray<nsCOMPtr<nsIDOMBlob> > blobs;
112 :
113 0 : PRUint64 length = aLength;
114 0 : PRUint64 skipStart = aStart;
115 :
116 : // Prune the list of blobs if we can
117 : PRUint32 i;
118 0 : for (i = 0; length && skipStart && i < mBlobs.Length(); i++) {
119 0 : nsIDOMBlob* blob = mBlobs[i].get();
120 :
121 : PRUint64 l;
122 0 : nsresult rv = blob->GetSize(&l);
123 0 : NS_ENSURE_SUCCESS(rv, nsnull);
124 :
125 0 : if (skipStart < l) {
126 0 : PRUint64 upperBound = NS_MIN<PRUint64>(l - skipStart, length);
127 :
128 0 : nsCOMPtr<nsIDOMBlob> firstBlob;
129 : rv = blob->Slice(skipStart, skipStart + upperBound,
130 : aContentType, 3,
131 0 : getter_AddRefs(firstBlob));
132 0 : NS_ENSURE_SUCCESS(rv, nsnull);
133 :
134 : // Avoid wrapping a single blob inside an nsDOMMultipartFile
135 0 : if (length == upperBound) {
136 0 : return firstBlob.forget();
137 : }
138 :
139 0 : blobs.AppendElement(firstBlob);
140 0 : length -= upperBound;
141 0 : i++;
142 0 : break;
143 : }
144 0 : skipStart -= l;
145 : }
146 :
147 : // Now append enough blobs until we're done
148 0 : for (; length && i < mBlobs.Length(); i++) {
149 0 : nsIDOMBlob* blob = mBlobs[i].get();
150 :
151 : PRUint64 l;
152 0 : nsresult rv = blob->GetSize(&l);
153 0 : NS_ENSURE_SUCCESS(rv, nsnull);
154 :
155 0 : if (length < l) {
156 0 : nsCOMPtr<nsIDOMBlob> lastBlob;
157 : rv = blob->Slice(0, length, aContentType, 3,
158 0 : getter_AddRefs(lastBlob));
159 0 : NS_ENSURE_SUCCESS(rv, nsnull);
160 :
161 0 : blobs.AppendElement(lastBlob);
162 : } else {
163 0 : blobs.AppendElement(blob);
164 : }
165 0 : length -= NS_MIN<PRUint64>(l, length);
166 : }
167 :
168 : // we can create our blob now
169 0 : nsCOMPtr<nsIDOMBlob> blob = new nsDOMMultipartFile(blobs, aContentType);
170 0 : return blob.forget();
171 : }
172 :
173 : /* static */ nsresult
174 0 : nsDOMMultipartFile::NewBlob(nsISupports* *aNewObject)
175 : {
176 0 : nsCOMPtr<nsISupports> file = do_QueryObject(new nsDOMMultipartFile());
177 0 : file.forget(aNewObject);
178 0 : return NS_OK;
179 : }
180 :
181 : NS_IMETHODIMP
182 0 : nsDOMMultipartFile::Initialize(nsISupports* aOwner,
183 : JSContext* aCx,
184 : JSObject* aObj,
185 : PRUint32 aArgc,
186 : jsval* aArgv)
187 : {
188 0 : bool nativeEOL = false;
189 0 : if (aArgc > 1) {
190 0 : mozilla::dom::BlobPropertyBag d;
191 0 : nsresult rv = d.Init(aCx, &aArgv[1]);
192 0 : NS_ENSURE_SUCCESS(rv, rv);
193 0 : mContentType = d.type;
194 0 : if (d.endings.EqualsLiteral("native")) {
195 0 : nativeEOL = true;
196 0 : } else if (!d.endings.EqualsLiteral("transparent")) {
197 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
198 : }
199 : }
200 :
201 0 : if (aArgc > 0) {
202 0 : if (!aArgv[0].isObject()) {
203 0 : return NS_ERROR_INVALID_ARG; // We're not interested
204 : }
205 :
206 0 : JSObject& obj = aArgv[0].toObject();
207 :
208 0 : if (!JS_IsArrayObject(aCx, &obj)) {
209 0 : return NS_ERROR_INVALID_ARG; // We're not interested
210 : }
211 :
212 0 : BlobSet blobSet;
213 :
214 : uint32_t length;
215 0 : JS_ALWAYS_TRUE(JS_GetArrayLength(aCx, &obj, &length));
216 0 : for (uint32_t i = 0; i < length; ++i) {
217 : jsval element;
218 0 : if (!JS_GetElement(aCx, &obj, i, &element))
219 0 : return NS_ERROR_INVALID_ARG;
220 :
221 0 : if (element.isObject()) {
222 0 : JSObject& obj = element.toObject();
223 : nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(
224 0 : nsContentUtils::XPConnect()->GetNativeOfWrapper(aCx, &obj));
225 0 : if (blob) {
226 : // Flatten so that multipart blobs will never nest
227 : nsDOMFileBase* file = static_cast<nsDOMFileBase*>(
228 0 : static_cast<nsIDOMBlob*>(blob));
229 : const nsTArray<nsCOMPtr<nsIDOMBlob> >*
230 0 : subBlobs = file->GetSubBlobs();
231 0 : if (subBlobs) {
232 0 : blobSet.AppendBlobs(*subBlobs);
233 : } else {
234 0 : blobSet.AppendBlob(blob);
235 : }
236 0 : } else if (js_IsArrayBuffer(&obj)) {
237 0 : JSObject* buffer = js::ArrayBuffer::getArrayBuffer(&obj);
238 0 : if (!buffer)
239 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
240 0 : blobSet.AppendArrayBuffer(buffer);
241 : } else {
242 : // neither arraybuffer nor blob
243 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
244 : }
245 0 : } else if (element.isString()) {
246 0 : blobSet.AppendString(element.toString(), nativeEOL, aCx);
247 : } else {
248 : // neither object nor string
249 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
250 : }
251 : }
252 :
253 0 : mBlobs = blobSet.GetBlobs();
254 : }
255 :
256 0 : return NS_OK;
257 : }
258 :
259 : nsresult
260 0 : BlobSet::AppendVoidPtr(const void* aData, PRUint32 aLength)
261 : {
262 0 : NS_ENSURE_ARG_POINTER(aData);
263 :
264 0 : PRUint64 offset = mDataLen;
265 :
266 0 : if (!ExpandBufferSize(aLength))
267 0 : return NS_ERROR_OUT_OF_MEMORY;
268 :
269 0 : memcpy((char*)mData + offset, aData, aLength);
270 0 : return NS_OK;
271 : }
272 :
273 : nsresult
274 0 : BlobSet::AppendString(JSString* aString, bool nativeEOL, JSContext* aCx)
275 : {
276 0 : nsDependentJSString xpcomStr;
277 0 : if (!xpcomStr.init(aCx, aString)) {
278 0 : return NS_ERROR_XPC_BAD_CONVERT_JS;
279 : }
280 :
281 0 : nsCString utf8Str = NS_ConvertUTF16toUTF8(xpcomStr);
282 :
283 0 : if (nativeEOL) {
284 0 : if (utf8Str.FindChar('\r') != kNotFound) {
285 0 : utf8Str.ReplaceSubstring("\r\n", "\n");
286 0 : utf8Str.ReplaceSubstring("\r", "\n");
287 : }
288 : #ifdef XP_WIN
289 : utf8Str.ReplaceSubstring("\n", "\r\n");
290 : #endif
291 : }
292 :
293 0 : return AppendVoidPtr((void*)utf8Str.Data(),
294 0 : utf8Str.Length());
295 : }
296 :
297 : nsresult
298 0 : BlobSet::AppendBlob(nsIDOMBlob* aBlob)
299 : {
300 0 : NS_ENSURE_ARG_POINTER(aBlob);
301 :
302 0 : Flush();
303 0 : mBlobs.AppendElement(aBlob);
304 :
305 0 : return NS_OK;
306 : }
307 :
308 : nsresult
309 0 : BlobSet::AppendBlobs(const nsTArray<nsCOMPtr<nsIDOMBlob> >& aBlob)
310 : {
311 0 : Flush();
312 0 : mBlobs.AppendElements(aBlob);
313 :
314 0 : return NS_OK;
315 : }
316 :
317 : nsresult
318 0 : BlobSet::AppendArrayBuffer(JSObject* aBuffer)
319 : {
320 0 : return AppendVoidPtr(JS_GetArrayBufferData(aBuffer), JS_GetArrayBufferByteLength(aBuffer));
321 : }
322 :
323 : DOMCI_DATA(MozBlobBuilder, nsDOMBlobBuilder)
324 :
325 0 : NS_IMPL_ADDREF(nsDOMBlobBuilder)
326 0 : NS_IMPL_RELEASE(nsDOMBlobBuilder)
327 0 : NS_INTERFACE_MAP_BEGIN(nsDOMBlobBuilder)
328 0 : NS_INTERFACE_MAP_ENTRY(nsIDOMMozBlobBuilder)
329 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
330 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozBlobBuilder)
331 0 : NS_INTERFACE_MAP_END
332 :
333 : /* nsIDOMBlob getBlob ([optional] in DOMString contentType); */
334 : NS_IMETHODIMP
335 0 : nsDOMBlobBuilder::GetBlob(const nsAString& aContentType,
336 : nsIDOMBlob** aBlob)
337 : {
338 0 : return GetBlobInternal(aContentType, true, aBlob);
339 : }
340 :
341 : nsresult
342 0 : nsDOMBlobBuilder::GetBlobInternal(const nsAString& aContentType,
343 : bool aClearBuffer,
344 : nsIDOMBlob** aBlob)
345 : {
346 0 : NS_ENSURE_ARG(aBlob);
347 :
348 0 : nsTArray<nsCOMPtr<nsIDOMBlob> >& blobs = mBlobSet.GetBlobs();
349 :
350 : nsCOMPtr<nsIDOMBlob> blob = new nsDOMMultipartFile(blobs,
351 0 : aContentType);
352 0 : blob.forget(aBlob);
353 :
354 : // NB: This is a willful violation of the spec. The spec says that
355 : // the existing contents of the BlobBuilder should be included
356 : // in the next blob produced. This seems silly and has been raised
357 : // on the WHATWG listserv.
358 0 : if (aClearBuffer) {
359 0 : blobs.Clear();
360 : }
361 :
362 0 : return NS_OK;
363 : }
364 :
365 : /* nsIDOMBlob getFile (in DOMString name, [optional] in DOMString contentType); */
366 : NS_IMETHODIMP
367 0 : nsDOMBlobBuilder::GetFile(const nsAString& aName,
368 : const nsAString& aContentType,
369 : nsIDOMFile** aFile)
370 : {
371 0 : NS_ENSURE_ARG(aFile);
372 :
373 0 : nsTArray<nsCOMPtr<nsIDOMBlob> >& blobs = mBlobSet.GetBlobs();
374 :
375 : nsCOMPtr<nsIDOMFile> file = new nsDOMMultipartFile(blobs,
376 : aName,
377 0 : aContentType);
378 0 : file.forget(aFile);
379 :
380 : // NB: This is a willful violation of the spec. The spec says that
381 : // the existing contents of the BlobBuilder should be included
382 : // in the next blob produced. This seems silly and has been raised
383 : // on the WHATWG listserv.
384 0 : blobs.Clear();
385 :
386 0 : return NS_OK;
387 : }
388 :
389 : /* [implicit_jscontext] void append (in jsval data,
390 : [optional] in DOMString endings); */
391 : NS_IMETHODIMP
392 0 : nsDOMBlobBuilder::Append(const jsval& aData,
393 : const nsAString& aEndings, JSContext* aCx)
394 : {
395 : // We need to figure out what our jsval is
396 :
397 : // Is it an object?
398 0 : if (JSVAL_IS_OBJECT(aData)) {
399 0 : JSObject* obj = JSVAL_TO_OBJECT(aData);
400 0 : if (!obj) {
401 : // We got passed null. Just do nothing.
402 0 : return NS_OK;
403 : }
404 :
405 : // Is it a Blob?
406 : nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(
407 0 : nsContentUtils::XPConnect()->
408 0 : GetNativeOfWrapper(aCx, obj));
409 0 : if (blob) {
410 : // Flatten so that multipart blobs will never nest
411 : nsDOMFileBase* file = static_cast<nsDOMFileBase*>(
412 0 : static_cast<nsIDOMBlob*>(blob));
413 0 : const nsTArray<nsCOMPtr<nsIDOMBlob> >* subBlobs = file->GetSubBlobs();
414 0 : if (subBlobs) {
415 0 : return mBlobSet.AppendBlobs(*subBlobs);
416 : } else {
417 0 : return mBlobSet.AppendBlob(blob);
418 : }
419 : }
420 :
421 : // Is it an array buffer?
422 0 : if (js_IsArrayBuffer(obj)) {
423 0 : JSObject* buffer = js::ArrayBuffer::getArrayBuffer(obj);
424 0 : if (buffer)
425 0 : return mBlobSet.AppendArrayBuffer(buffer);
426 : }
427 : }
428 :
429 : // If it's not a Blob or an ArrayBuffer, coerce it to a string
430 0 : JSString* str = JS_ValueToString(aCx, aData);
431 0 : NS_ENSURE_TRUE(str, NS_ERROR_FAILURE);
432 :
433 0 : return mBlobSet.AppendString(str, aEndings.EqualsLiteral("native"), aCx);
434 : }
435 :
436 0 : nsresult NS_NewBlobBuilder(nsISupports* *aSupports)
437 : {
438 0 : nsDOMBlobBuilder* builder = new nsDOMBlobBuilder();
439 0 : return CallQueryInterface(builder, aSupports);
440 : }
|