1 /*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32
33 #include "core/fileapi/FileReaderLoader.h"
34
35 #include "core/FetchInitiatorTypeNames.h"
36 #include "core/dom/ExecutionContext.h"
37 #include "core/fileapi/Blob.h"
38 #include "core/fileapi/FileReaderLoaderClient.h"
39 #include "core/fileapi/Stream.h"
40 #include "core/html/parser/TextResourceDecoder.h"
41 #include "core/loader/ThreadableLoader.h"
42 #include "platform/blob/BlobRegistry.h"
43 #include "platform/blob/BlobURL.h"
44 #include "platform/network/ResourceRequest.h"
45 #include "platform/network/ResourceResponse.h"
46 #include "wtf/PassOwnPtr.h"
47 #include "wtf/PassRefPtr.h"
48 #include "wtf/RefPtr.h"
49 #include "wtf/Vector.h"
50 #include "wtf/text/Base64.h"
51 #include "wtf/text/StringBuilder.h"
52
53 namespace WebCore {
54
FileReaderLoader(ReadType readType,FileReaderLoaderClient * client)55 FileReaderLoader::FileReaderLoader(ReadType readType, FileReaderLoaderClient* client)
56 : m_readType(readType)
57 , m_client(client)
58 , m_urlForReadingIsStream(false)
59 , m_isRawDataConverted(false)
60 , m_stringResult("")
61 , m_finishedLoading(false)
62 , m_bytesLoaded(0)
63 , m_totalBytes(-1)
64 , m_hasRange(false)
65 , m_rangeStart(0)
66 , m_rangeEnd(0)
67 , m_errorCode(FileError::OK)
68 {
69 }
70
~FileReaderLoader()71 FileReaderLoader::~FileReaderLoader()
72 {
73 terminate();
74 if (!m_urlForReading.isEmpty()) {
75 if (m_urlForReadingIsStream)
76 BlobRegistry::unregisterStreamURL(m_urlForReading);
77 else
78 BlobRegistry::revokePublicBlobURL(m_urlForReading);
79 }
80 }
81
startInternal(ExecutionContext & executionContext,const Stream * stream,PassRefPtr<BlobDataHandle> blobData)82 void FileReaderLoader::startInternal(ExecutionContext& executionContext, const Stream* stream, PassRefPtr<BlobDataHandle> blobData)
83 {
84 // The blob is read by routing through the request handling layer given a temporary public url.
85 m_urlForReading = BlobURL::createPublicURL(executionContext.securityOrigin());
86 if (m_urlForReading.isEmpty()) {
87 failed(FileError::SECURITY_ERR);
88 return;
89 }
90
91 if (blobData) {
92 ASSERT(!stream);
93 BlobRegistry::registerPublicBlobURL(executionContext.securityOrigin(), m_urlForReading, blobData);
94 } else {
95 ASSERT(stream);
96 BlobRegistry::registerStreamURL(executionContext.securityOrigin(), m_urlForReading, stream->url());
97 }
98
99 // Construct and load the request.
100 ResourceRequest request(m_urlForReading);
101 request.setHTTPMethod("GET");
102 if (m_hasRange)
103 request.setHTTPHeaderField("Range", AtomicString(String::format("bytes=%d-%d", m_rangeStart, m_rangeEnd)));
104
105 ThreadableLoaderOptions options;
106 options.preflightPolicy = ConsiderPreflight;
107 options.crossOriginRequestPolicy = DenyCrossOriginRequests;
108 // FIXME: Is there a directive to which this load should be subject?
109 options.contentSecurityPolicyEnforcement = DoNotEnforceContentSecurityPolicy;
110 // Use special initiator to hide the request from the inspector.
111 options.initiator = FetchInitiatorTypeNames::internal;
112
113 ResourceLoaderOptions resourceLoaderOptions;
114 resourceLoaderOptions.allowCredentials = AllowStoredCredentials;
115
116 if (m_client)
117 m_loader = ThreadableLoader::create(executionContext, this, request, options, resourceLoaderOptions);
118 else
119 ThreadableLoader::loadResourceSynchronously(executionContext, request, *this, options, resourceLoaderOptions);
120 }
121
start(ExecutionContext * executionContext,PassRefPtr<BlobDataHandle> blobData)122 void FileReaderLoader::start(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobData)
123 {
124 ASSERT(executionContext);
125 m_urlForReadingIsStream = false;
126 startInternal(*executionContext, 0, blobData);
127 }
128
start(ExecutionContext * executionContext,const Stream & stream,unsigned readSize)129 void FileReaderLoader::start(ExecutionContext* executionContext, const Stream& stream, unsigned readSize)
130 {
131 ASSERT(executionContext);
132 if (readSize > 0) {
133 m_hasRange = true;
134 m_rangeStart = 0;
135 m_rangeEnd = readSize - 1; // End is inclusive so (0,0) is a 1-byte read.
136 }
137
138 m_urlForReadingIsStream = true;
139 startInternal(*executionContext, &stream, nullptr);
140 }
141
cancel()142 void FileReaderLoader::cancel()
143 {
144 m_errorCode = FileError::ABORT_ERR;
145 terminate();
146 }
147
terminate()148 void FileReaderLoader::terminate()
149 {
150 if (m_loader) {
151 m_loader->cancel();
152 cleanup();
153 }
154 }
155
cleanup()156 void FileReaderLoader::cleanup()
157 {
158 m_loader = nullptr;
159
160 // If we get any error, we do not need to keep a buffer around.
161 if (m_errorCode) {
162 m_rawData.clear();
163 m_stringResult = "";
164 m_isRawDataConverted = true;
165 m_decoder.clear();
166 }
167 }
168
didReceiveResponse(unsigned long,const ResourceResponse & response)169 void FileReaderLoader::didReceiveResponse(unsigned long, const ResourceResponse& response)
170 {
171 if (response.httpStatusCode() != 200) {
172 failed(httpStatusCodeToErrorCode(response.httpStatusCode()));
173 return;
174 }
175
176 // A negative value means that the content length wasn't specified.
177 m_totalBytes = response.expectedContentLength();
178
179 long long initialBufferLength = -1;
180
181 if (m_totalBytes >= 0) {
182 initialBufferLength = m_totalBytes;
183 } else if (m_hasRange) {
184 // Set m_totalBytes and allocate a buffer based on the specified range.
185 m_totalBytes = 1LL + m_rangeEnd - m_rangeStart;
186 initialBufferLength = m_totalBytes;
187 } else {
188 // Nothing is known about the size of the resource. Normalize
189 // m_totalBytes to -1 and initialize the buffer for receiving with the
190 // default size.
191 m_totalBytes = -1;
192 }
193
194 ASSERT(!m_rawData);
195
196 if (m_readType != ReadByClient) {
197 // Check that we can cast to unsigned since we have to do
198 // so to call ArrayBuffer's create function.
199 // FIXME: Support reading more than the current size limit of ArrayBuffer.
200 if (initialBufferLength > std::numeric_limits<unsigned>::max()) {
201 failed(FileError::NOT_READABLE_ERR);
202 return;
203 }
204
205 if (initialBufferLength < 0)
206 m_rawData = adoptPtr(new ArrayBufferBuilder());
207 else
208 m_rawData = adoptPtr(new ArrayBufferBuilder(static_cast<unsigned>(initialBufferLength)));
209
210 if (!m_rawData || !m_rawData->isValid()) {
211 failed(FileError::NOT_READABLE_ERR);
212 return;
213 }
214
215 if (initialBufferLength >= 0) {
216 // Total size is known. Set m_rawData to ignore overflowed data.
217 m_rawData->setVariableCapacity(false);
218 }
219 }
220
221 if (m_client)
222 m_client->didStartLoading();
223 }
224
didReceiveData(const char * data,int dataLength)225 void FileReaderLoader::didReceiveData(const char* data, int dataLength)
226 {
227 ASSERT(data);
228 ASSERT(dataLength > 0);
229
230 // Bail out if we already encountered an error.
231 if (m_errorCode)
232 return;
233
234 if (m_readType == ReadByClient) {
235 m_bytesLoaded += dataLength;
236
237 if (m_client)
238 m_client->didReceiveDataForClient(data, dataLength);
239 return;
240 }
241
242 unsigned bytesAppended = m_rawData->append(data, static_cast<unsigned>(dataLength));
243 if (!bytesAppended) {
244 m_rawData.clear();
245 m_bytesLoaded = 0;
246 failed(FileError::NOT_READABLE_ERR);
247 return;
248 }
249 m_bytesLoaded += bytesAppended;
250 m_isRawDataConverted = false;
251
252 if (m_client)
253 m_client->didReceiveData();
254 }
255
didFinishLoading(unsigned long,double)256 void FileReaderLoader::didFinishLoading(unsigned long, double)
257 {
258 if (m_readType != ReadByClient && m_rawData) {
259 m_rawData->shrinkToFit();
260 m_isRawDataConverted = false;
261 }
262
263 if (m_totalBytes == -1) {
264 // Update m_totalBytes only when in dynamic buffer grow mode.
265 m_totalBytes = m_bytesLoaded;
266 }
267
268 m_finishedLoading = true;
269
270 cleanup();
271 if (m_client)
272 m_client->didFinishLoading();
273 }
274
didFail(const ResourceError &)275 void FileReaderLoader::didFail(const ResourceError&)
276 {
277 // If we're aborting, do not proceed with normal error handling since it is covered in aborting code.
278 if (m_errorCode == FileError::ABORT_ERR)
279 return;
280
281 failed(FileError::NOT_READABLE_ERR);
282 }
283
failed(FileError::ErrorCode errorCode)284 void FileReaderLoader::failed(FileError::ErrorCode errorCode)
285 {
286 m_errorCode = errorCode;
287 cleanup();
288 if (m_client)
289 m_client->didFail(m_errorCode);
290 }
291
httpStatusCodeToErrorCode(int httpStatusCode)292 FileError::ErrorCode FileReaderLoader::httpStatusCodeToErrorCode(int httpStatusCode)
293 {
294 switch (httpStatusCode) {
295 case 403:
296 return FileError::SECURITY_ERR;
297 case 404:
298 return FileError::NOT_FOUND_ERR;
299 default:
300 return FileError::NOT_READABLE_ERR;
301 }
302 }
303
arrayBufferResult() const304 PassRefPtr<ArrayBuffer> FileReaderLoader::arrayBufferResult() const
305 {
306 ASSERT(m_readType == ReadAsArrayBuffer);
307
308 // If the loading is not started or an error occurs, return an empty result.
309 if (!m_rawData || m_errorCode)
310 return nullptr;
311
312 return m_rawData->toArrayBuffer();
313 }
314
stringResult()315 String FileReaderLoader::stringResult()
316 {
317 ASSERT(m_readType != ReadAsArrayBuffer && m_readType != ReadAsBlob && m_readType != ReadByClient);
318
319 // If the loading is not started or an error occurs, return an empty result.
320 if (!m_rawData || m_errorCode)
321 return m_stringResult;
322
323 // If already converted from the raw data, return the result now.
324 if (m_isRawDataConverted)
325 return m_stringResult;
326
327 switch (m_readType) {
328 case ReadAsArrayBuffer:
329 // No conversion is needed.
330 break;
331 case ReadAsBinaryString:
332 m_stringResult = m_rawData->toString();
333 m_isRawDataConverted = true;
334 break;
335 case ReadAsText:
336 convertToText();
337 break;
338 case ReadAsDataURL:
339 // Partial data is not supported when reading as data URL.
340 if (m_finishedLoading)
341 convertToDataURL();
342 break;
343 default:
344 ASSERT_NOT_REACHED();
345 }
346
347 return m_stringResult;
348 }
349
convertToText()350 void FileReaderLoader::convertToText()
351 {
352 m_isRawDataConverted = true;
353
354 if (!m_bytesLoaded) {
355 m_stringResult = "";
356 return;
357 }
358
359 // Decode the data.
360 // The File API spec says that we should use the supplied encoding if it is valid. However, we choose to ignore this
361 // requirement in order to be consistent with how WebKit decodes the web content: always has the BOM override the
362 // provided encoding.
363 // FIXME: consider supporting incremental decoding to improve the perf.
364 StringBuilder builder;
365 if (!m_decoder)
366 m_decoder = TextResourceDecoder::create("text/plain", m_encoding.isValid() ? m_encoding : UTF8Encoding());
367 builder.append(m_decoder->decode(static_cast<const char*>(m_rawData->data()), m_rawData->byteLength()));
368
369 if (m_finishedLoading)
370 builder.append(m_decoder->flush());
371
372 m_stringResult = builder.toString();
373 }
374
convertToDataURL()375 void FileReaderLoader::convertToDataURL()
376 {
377 m_isRawDataConverted = true;
378
379 StringBuilder builder;
380 builder.append("data:");
381
382 if (!m_bytesLoaded) {
383 m_stringResult = builder.toString();
384 return;
385 }
386
387 builder.append(m_dataType);
388 builder.append(";base64,");
389
390 Vector<char> out;
391 base64Encode(static_cast<const char*>(m_rawData->data()), m_rawData->byteLength(), out);
392 out.append('\0');
393 builder.append(out.data());
394
395 m_stringResult = builder.toString();
396 }
397
setEncoding(const String & encoding)398 void FileReaderLoader::setEncoding(const String& encoding)
399 {
400 if (!encoding.isEmpty())
401 m_encoding = WTF::TextEncoding(encoding);
402 }
403
404 } // namespace WebCore
405