Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FileReaderSync.h"
9 #include "jsfriendapi.h"
10 #include "mozilla/Base64.h"
11 #include "mozilla/dom/EncodingUtils.h"
12 #include "nsContentUtils.h"
13 #include "mozilla/dom/FileReaderSyncBinding.h"
14 #include "nsCExternalHandlerService.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsCOMPtr.h"
17 #include "nsDOMClassInfoID.h"
18 #include "nsError.h"
19 #include "nsIDOMFile.h"
20 #include "nsIConverterInputStream.h"
21 #include "nsIInputStream.h"
22 #include "nsISeekableStream.h"
23 #include "nsISupportsImpl.h"
24 #include "nsNetUtil.h"
25 #include "nsServiceManagerUtils.h"
27 #include "File.h"
28 #include "RuntimeService.h"
30 USING_WORKERS_NAMESPACE
31 using namespace mozilla;
32 using mozilla::dom::Optional;
33 using mozilla::dom::GlobalObject;
35 // static
36 already_AddRefed<FileReaderSync>
37 FileReaderSync::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
38 {
39 nsRefPtr<FileReaderSync> frs = new FileReaderSync();
41 return frs.forget();
42 }
44 JSObject*
45 FileReaderSync::WrapObject(JSContext* aCx)
46 {
47 return FileReaderSyncBinding_workers::Wrap(aCx, this);
48 }
50 void
51 FileReaderSync::ReadAsArrayBuffer(JSContext* aCx,
52 JS::Handle<JSObject*> aScopeObj,
53 JS::Handle<JSObject*> aBlob,
54 JS::MutableHandle<JSObject*> aRetval,
55 ErrorResult& aRv)
56 {
57 nsIDOMBlob* blob = file::GetDOMBlobFromJSObject(aBlob);
58 if (!blob) {
59 aRv.Throw(NS_ERROR_INVALID_ARG);
60 return;
61 }
63 uint64_t blobSize;
64 nsresult rv = blob->GetSize(&blobSize);
65 if (NS_FAILED(rv)) {
66 aRv.Throw(rv);
67 return;
68 }
70 JS::Rooted<JSObject*> jsArrayBuffer(aCx, JS_NewArrayBuffer(aCx, blobSize));
71 if (!jsArrayBuffer) {
72 // XXXkhuey we need a way to indicate to the bindings that the call failed
73 // but there's already a pending exception that we should not clobber.
74 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
75 return;
76 }
78 uint32_t bufferLength = JS_GetArrayBufferByteLength(jsArrayBuffer);
79 uint8_t* arrayBuffer = JS_GetStableArrayBufferData(aCx, jsArrayBuffer);
80 if (!arrayBuffer) {
81 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
82 return;
83 }
85 nsCOMPtr<nsIInputStream> stream;
86 rv = blob->GetInternalStream(getter_AddRefs(stream));
87 if (NS_FAILED(rv)) {
88 aRv.Throw(rv);
89 return;
90 }
92 uint32_t numRead;
93 rv = stream->Read((char*)arrayBuffer, bufferLength, &numRead);
94 if (NS_FAILED(rv)) {
95 aRv.Throw(rv);
96 return;
97 }
98 NS_ASSERTION(numRead == bufferLength, "failed to read data");
100 aRetval.set(jsArrayBuffer);
101 }
103 void
104 FileReaderSync::ReadAsBinaryString(JS::Handle<JSObject*> aBlob,
105 nsAString& aResult,
106 ErrorResult& aRv)
107 {
108 nsIDOMBlob* blob = file::GetDOMBlobFromJSObject(aBlob);
109 if (!blob) {
110 aRv.Throw(NS_ERROR_INVALID_ARG);
111 return;
112 }
114 nsCOMPtr<nsIInputStream> stream;
115 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream));
116 if (NS_FAILED(rv)) {
117 aRv.Throw(rv);
118 return;
119 }
121 uint32_t numRead;
122 do {
123 char readBuf[4096];
124 rv = stream->Read(readBuf, sizeof(readBuf), &numRead);
125 if (NS_FAILED(rv)) {
126 aRv.Throw(rv);
127 return;
128 }
130 uint32_t oldLength = aResult.Length();
131 AppendASCIItoUTF16(Substring(readBuf, readBuf + numRead), aResult);
132 if (aResult.Length() - oldLength != numRead) {
133 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
134 return;
135 }
136 } while (numRead > 0);
137 }
139 void
140 FileReaderSync::ReadAsText(JS::Handle<JSObject*> aBlob,
141 const Optional<nsAString>& aEncoding,
142 nsAString& aResult,
143 ErrorResult& aRv)
144 {
145 nsIDOMBlob* blob = file::GetDOMBlobFromJSObject(aBlob);
146 if (!blob) {
147 aRv.Throw(NS_ERROR_INVALID_ARG);
148 return;
149 }
151 nsCOMPtr<nsIInputStream> stream;
152 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream));
153 if (NS_FAILED(rv)) {
154 aRv.Throw(rv);
155 return;
156 }
158 nsAutoCString encoding;
159 unsigned char sniffBuf[3] = { 0, 0, 0 };
160 uint32_t numRead;
161 rv = stream->Read(reinterpret_cast<char*>(sniffBuf),
162 sizeof(sniffBuf), &numRead);
163 if (NS_FAILED(rv)) {
164 aRv.Throw(rv);
165 return;
166 }
168 // The BOM sniffing is baked into the "decode" part of the Encoding
169 // Standard, which the File API references.
170 if (!nsContentUtils::CheckForBOM(sniffBuf, numRead, encoding)) {
171 // BOM sniffing failed. Try the API argument.
172 if (!aEncoding.WasPassed() ||
173 !EncodingUtils::FindEncodingForLabel(aEncoding.Value(),
174 encoding)) {
175 // API argument failed. Try the type property of the blob.
176 nsAutoString type16;
177 blob->GetType(type16);
178 NS_ConvertUTF16toUTF8 type(type16);
179 nsAutoCString specifiedCharset;
180 bool haveCharset;
181 int32_t charsetStart, charsetEnd;
182 NS_ExtractCharsetFromContentType(type,
183 specifiedCharset,
184 &haveCharset,
185 &charsetStart,
186 &charsetEnd);
187 if (!EncodingUtils::FindEncodingForLabel(specifiedCharset, encoding)) {
188 // Type property failed. Use UTF-8.
189 encoding.AssignLiteral("UTF-8");
190 }
191 }
192 }
194 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream);
195 if (!seekable) {
196 aRv.Throw(NS_ERROR_FAILURE);
197 return;
198 }
200 // Seek to 0 because to undo the BOM sniffing advance. UTF-8 and UTF-16
201 // decoders will swallow the BOM.
202 rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
203 if (NS_FAILED(rv)) {
204 aRv.Throw(rv);
205 return;
206 }
208 rv = ConvertStream(stream, encoding.get(), aResult);
209 if (NS_FAILED(rv)) {
210 aRv.Throw(rv);
211 return;
212 }
213 }
215 void
216 FileReaderSync::ReadAsDataURL(JS::Handle<JSObject*> aBlob, nsAString& aResult,
217 ErrorResult& aRv)
218 {
219 nsIDOMBlob* blob = file::GetDOMBlobFromJSObject(aBlob);
220 if (!blob) {
221 aRv.Throw(NS_ERROR_INVALID_ARG);
222 return;
223 }
225 nsAutoString scratchResult;
226 scratchResult.AssignLiteral("data:");
228 nsString contentType;
229 blob->GetType(contentType);
231 if (contentType.IsEmpty()) {
232 scratchResult.AppendLiteral("application/octet-stream");
233 } else {
234 scratchResult.Append(contentType);
235 }
236 scratchResult.AppendLiteral(";base64,");
238 nsCOMPtr<nsIInputStream> stream;
239 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream));
240 if (NS_FAILED(rv)) {
241 aRv.Throw(rv);
242 return;
243 }
245 uint64_t size;
246 rv = blob->GetSize(&size);
247 if (NS_FAILED(rv)) {
248 aRv.Throw(rv);
249 return;
250 }
252 nsCOMPtr<nsIInputStream> bufferedStream;
253 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, size);
254 if (NS_FAILED(rv)) {
255 aRv.Throw(rv);
256 return;
257 }
259 nsAutoString encodedData;
260 rv = Base64EncodeInputStream(bufferedStream, encodedData, size);
261 if (NS_FAILED(rv)) {
262 aRv.Throw(rv);
263 return;
264 }
266 scratchResult.Append(encodedData);
268 aResult = scratchResult;
269 }
271 nsresult
272 FileReaderSync::ConvertStream(nsIInputStream *aStream,
273 const char *aCharset,
274 nsAString &aResult)
275 {
276 nsCOMPtr<nsIConverterInputStream> converterStream =
277 do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
278 NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE);
280 nsresult rv = converterStream->Init(aStream, aCharset, 8192,
281 nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
282 NS_ENSURE_SUCCESS(rv, rv);
284 nsCOMPtr<nsIUnicharInputStream> unicharStream =
285 do_QueryInterface(converterStream);
286 NS_ENSURE_TRUE(unicharStream, NS_ERROR_FAILURE);
288 uint32_t numChars;
289 nsString result;
290 while (NS_SUCCEEDED(unicharStream->ReadString(8192, result, &numChars)) &&
291 numChars > 0) {
292 uint32_t oldLength = aResult.Length();
293 aResult.Append(result);
294 if (aResult.Length() - oldLength != result.Length()) {
295 return NS_ERROR_OUT_OF_MEMORY;
296 }
297 }
299 return rv;
300 }