|
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/. */ |
|
6 |
|
7 #include "FileReaderSync.h" |
|
8 |
|
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" |
|
26 |
|
27 #include "File.h" |
|
28 #include "RuntimeService.h" |
|
29 |
|
30 USING_WORKERS_NAMESPACE |
|
31 using namespace mozilla; |
|
32 using mozilla::dom::Optional; |
|
33 using mozilla::dom::GlobalObject; |
|
34 |
|
35 // static |
|
36 already_AddRefed<FileReaderSync> |
|
37 FileReaderSync::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) |
|
38 { |
|
39 nsRefPtr<FileReaderSync> frs = new FileReaderSync(); |
|
40 |
|
41 return frs.forget(); |
|
42 } |
|
43 |
|
44 JSObject* |
|
45 FileReaderSync::WrapObject(JSContext* aCx) |
|
46 { |
|
47 return FileReaderSyncBinding_workers::Wrap(aCx, this); |
|
48 } |
|
49 |
|
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 } |
|
62 |
|
63 uint64_t blobSize; |
|
64 nsresult rv = blob->GetSize(&blobSize); |
|
65 if (NS_FAILED(rv)) { |
|
66 aRv.Throw(rv); |
|
67 return; |
|
68 } |
|
69 |
|
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 } |
|
77 |
|
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 } |
|
84 |
|
85 nsCOMPtr<nsIInputStream> stream; |
|
86 rv = blob->GetInternalStream(getter_AddRefs(stream)); |
|
87 if (NS_FAILED(rv)) { |
|
88 aRv.Throw(rv); |
|
89 return; |
|
90 } |
|
91 |
|
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"); |
|
99 |
|
100 aRetval.set(jsArrayBuffer); |
|
101 } |
|
102 |
|
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 } |
|
113 |
|
114 nsCOMPtr<nsIInputStream> stream; |
|
115 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream)); |
|
116 if (NS_FAILED(rv)) { |
|
117 aRv.Throw(rv); |
|
118 return; |
|
119 } |
|
120 |
|
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 } |
|
129 |
|
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 } |
|
138 |
|
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 } |
|
150 |
|
151 nsCOMPtr<nsIInputStream> stream; |
|
152 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream)); |
|
153 if (NS_FAILED(rv)) { |
|
154 aRv.Throw(rv); |
|
155 return; |
|
156 } |
|
157 |
|
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 } |
|
167 |
|
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 } |
|
193 |
|
194 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(stream); |
|
195 if (!seekable) { |
|
196 aRv.Throw(NS_ERROR_FAILURE); |
|
197 return; |
|
198 } |
|
199 |
|
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 } |
|
207 |
|
208 rv = ConvertStream(stream, encoding.get(), aResult); |
|
209 if (NS_FAILED(rv)) { |
|
210 aRv.Throw(rv); |
|
211 return; |
|
212 } |
|
213 } |
|
214 |
|
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 } |
|
224 |
|
225 nsAutoString scratchResult; |
|
226 scratchResult.AssignLiteral("data:"); |
|
227 |
|
228 nsString contentType; |
|
229 blob->GetType(contentType); |
|
230 |
|
231 if (contentType.IsEmpty()) { |
|
232 scratchResult.AppendLiteral("application/octet-stream"); |
|
233 } else { |
|
234 scratchResult.Append(contentType); |
|
235 } |
|
236 scratchResult.AppendLiteral(";base64,"); |
|
237 |
|
238 nsCOMPtr<nsIInputStream> stream; |
|
239 nsresult rv = blob->GetInternalStream(getter_AddRefs(stream)); |
|
240 if (NS_FAILED(rv)) { |
|
241 aRv.Throw(rv); |
|
242 return; |
|
243 } |
|
244 |
|
245 uint64_t size; |
|
246 rv = blob->GetSize(&size); |
|
247 if (NS_FAILED(rv)) { |
|
248 aRv.Throw(rv); |
|
249 return; |
|
250 } |
|
251 |
|
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 } |
|
258 |
|
259 nsAutoString encodedData; |
|
260 rv = Base64EncodeInputStream(bufferedStream, encodedData, size); |
|
261 if (NS_FAILED(rv)) { |
|
262 aRv.Throw(rv); |
|
263 return; |
|
264 } |
|
265 |
|
266 scratchResult.Append(encodedData); |
|
267 |
|
268 aResult = scratchResult; |
|
269 } |
|
270 |
|
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); |
|
279 |
|
280 nsresult rv = converterStream->Init(aStream, aCharset, 8192, |
|
281 nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER); |
|
282 NS_ENSURE_SUCCESS(rv, rv); |
|
283 |
|
284 nsCOMPtr<nsIUnicharInputStream> unicharStream = |
|
285 do_QueryInterface(converterStream); |
|
286 NS_ENSURE_TRUE(unicharStream, NS_ERROR_FAILURE); |
|
287 |
|
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 } |
|
298 |
|
299 return rv; |
|
300 } |
|
301 |