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/html/parser/TextResourceDecoder.h"
40 #include "core/loader/ThreadableLoader.h"
41 #include "core/streams/Stream.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 "public/platform/WebURLRequest.h"
47 #include "wtf/PassOwnPtr.h"
48 #include "wtf/PassRefPtr.h"
49 #include "wtf/RefPtr.h"
50 #include "wtf/Vector.h"
51 #include "wtf/text/Base64.h"
52 #include "wtf/text/StringBuilder.h"
53
54 namespace blink {
55
FileReaderLoader(ReadType readType,FileReaderLoaderClient * client)56 FileReaderLoader::FileReaderLoader(ReadType readType, FileReaderLoaderClient* client)
57 : m_readType(readType)
58 , m_client(client)
59 , m_urlForReadingIsStream(false)
60 , m_isRawDataConverted(false)
61 , m_stringResult("")
62 , m_finishedLoading(false)
63 , m_bytesLoaded(0)
64 , m_totalBytes(-1)
65 , m_hasRange(false)
66 , m_rangeStart(0)
67 , m_rangeEnd(0)
68 , m_errorCode(FileError::OK)
69 {
70 }
71
~FileReaderLoader()72 FileReaderLoader::~FileReaderLoader()
73 {
74 terminate();
75 if (!m_urlForReading.isEmpty()) {
76 if (m_urlForReadingIsStream)
77 BlobRegistry::unregisterStreamURL(m_urlForReading);
78 else
79 BlobRegistry::revokePublicBlobURL(m_urlForReading);
80 }
81 }
82
startInternal(ExecutionContext & executionContext,const Stream * stream,PassRefPtr<BlobDataHandle> blobData)83 void FileReaderLoader::startInternal(ExecutionContext& executionContext, const Stream* stream, PassRefPtr<BlobDataHandle> blobData)
84 {
85 // The blob is read by routing through the request handling layer given a temporary public url.
86 m_urlForReading = BlobURL::createPublicURL(executionContext.securityOrigin());
87 if (m_urlForReading.isEmpty()) {
88 failed(FileError::SECURITY_ERR);
89 return;
90 }
91
92 if (blobData) {
93 ASSERT(!stream);
94 BlobRegistry::registerPublicBlobURL(executionContext.securityOrigin(), m_urlForReading, blobData);
95 } else {
96 ASSERT(stream);
97 BlobRegistry::registerStreamURL(executionContext.securityOrigin(), m_urlForReading, stream->url());
98 }
99
100 // Construct and load the request.
101 ResourceRequest request(m_urlForReading);
102
103 // FIXME: Should this really be 'internal'? Do we know anything about the actual request that generated this fetch?
104 request.setRequestContext(WebURLRequest::RequestContextInternal);
105
106 request.setHTTPMethod("GET");
107 if (m_hasRange)
108 request.setHTTPHeaderField("Range", AtomicString(String::format("bytes=%d-%d", m_rangeStart, m_rangeEnd)));
109
110 ThreadableLoaderOptions options;
111 options.preflightPolicy = ConsiderPreflight;
112 options.crossOriginRequestPolicy = DenyCrossOriginRequests;
113 // FIXME: Is there a directive to which this load should be subject?
114 options.contentSecurityPolicyEnforcement = DoNotEnforceContentSecurityPolicy;
115 // Use special initiator to hide the request from the inspector.
116 options.initiator = FetchInitiatorTypeNames::internal;
117
118 ResourceLoaderOptions resourceLoaderOptions;
119 resourceLoaderOptions.allowCredentials = AllowStoredCredentials;
120
121 if (m_client)
122 m_loader = ThreadableLoader::create(executionContext, this, request, options, resourceLoaderOptions);
123 else
124 ThreadableLoader::loadResourceSynchronously(executionContext, request, *this, options, resourceLoaderOptions);
125 }
126
start(ExecutionContext * executionContext,PassRefPtr<BlobDataHandle> blobData)127 void FileReaderLoader::start(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobData)
128 {
129 ASSERT(executionContext);
130 m_urlForReadingIsStream = false;
131 startInternal(*executionContext, 0, blobData);
132 }
133
start(ExecutionContext * executionContext,const Stream & stream,unsigned readSize)134 void FileReaderLoader::start(ExecutionContext* executionContext, const Stream& stream, unsigned readSize)
135 {
136 ASSERT(executionContext);
137 if (readSize > 0) {
138 m_hasRange = true;
139 m_rangeStart = 0;
140 m_rangeEnd = readSize - 1; // End is inclusive so (0,0) is a 1-byte read.
141 }
142
143 m_urlForReadingIsStream = true;
144 startInternal(*executionContext, &stream, nullptr);
145 }
146
cancel()147 void FileReaderLoader::cancel()
148 {
149 m_errorCode = FileError::ABORT_ERR;
150 terminate();
151 }
152
terminate()153 void FileReaderLoader::terminate()
154 {
155 if (m_loader) {
156 m_loader->cancel();
157 cleanup();
158 }
159 }
160
cleanup()161 void FileReaderLoader::cleanup()
162 {
163 m_loader = nullptr;
164
165 // If we get any error, we do not need to keep a buffer around.
166 if (m_errorCode) {
167 m_rawData.clear();
168 m_stringResult = "";
169 m_isRawDataConverted = true;
170 m_decoder.clear();
171 }
172 }
173
didReceiveResponse(unsigned long,const ResourceResponse & response)174 void FileReaderLoader::didReceiveResponse(unsigned long, const ResourceResponse& response)
175 {
176 if (response.httpStatusCode() != 200) {
177 failed(httpStatusCodeToErrorCode(response.httpStatusCode()));
178 return;
179 }
180
181 // A negative value means that the content length wasn't specified.
182 m_totalBytes = response.expectedContentLength();
183
184 long long initialBufferLength = -1;
185
186 if (m_totalBytes >= 0) {
187 initialBufferLength = m_totalBytes;
188 } else if (m_hasRange) {
189 // Set m_totalBytes and allocate a buffer based on the specified range.
190 m_totalBytes = 1LL + m_rangeEnd - m_rangeStart;
191 initialBufferLength = m_totalBytes;
192 } else {
193 // Nothing is known about the size of the resource. Normalize
194 // m_totalBytes to -1 and initialize the buffer for receiving with the
195 // default size.
196 m_totalBytes = -1;
197 }
198
199 ASSERT(!m_rawData);
200
201 if (m_readType != ReadByClient) {
202 // Check that we can cast to unsigned since we have to do
203 // so to call ArrayBuffer's create function.
204 // FIXME: Support reading more than the current size limit of ArrayBuffer.
205 if (initialBufferLength > std::numeric_limits<unsigned>::max()) {
206 failed(FileError::NOT_READABLE_ERR);
207 return;
208 }
209
210 if (initialBufferLength < 0)
211 m_rawData = adoptPtr(new ArrayBufferBuilder());
212 else
213 m_rawData = adoptPtr(new ArrayBufferBuilder(static_cast<unsigned>(initialBufferLength)));
214
215 if (!m_rawData || !m_rawData->isValid()) {
216 failed(FileError::NOT_READABLE_ERR);
217 return;
218 }
219
220 if (initialBufferLength >= 0) {
221 // Total size is known. Set m_rawData to ignore overflowed data.
222 m_rawData->setVariableCapacity(false);
223 }
224 }
225
226 if (m_client)
227 m_client->didStartLoading();
228 }
229
didReceiveData(const char * data,int dataLength)230 void FileReaderLoader::didReceiveData(const char* data, int dataLength)
231 {
232 ASSERT(data);
233 ASSERT(dataLength > 0);
234
235 // Bail out if we already encountered an error.
236 if (m_errorCode)
237 return;
238
239 if (m_readType == ReadByClient) {
240 m_bytesLoaded += dataLength;
241
242 if (m_client)
243 m_client->didReceiveDataForClient(data, dataLength);
244 return;
245 }
246
247 unsigned bytesAppended = m_rawData->append(data, static_cast<unsigned>(dataLength));
248 if (!bytesAppended) {
249 m_rawData.clear();
250 m_bytesLoaded = 0;
251 failed(FileError::NOT_READABLE_ERR);
252 return;
253 }
254 m_bytesLoaded += bytesAppended;
255 m_isRawDataConverted = false;
256
257 if (m_client)
258 m_client->didReceiveData();
259 }
260
didFinishLoading(unsigned long,double)261 void FileReaderLoader::didFinishLoading(unsigned long, double)
262 {
263 if (m_readType != ReadByClient && m_rawData) {
264 m_rawData->shrinkToFit();
265 m_isRawDataConverted = false;
266 }
267
268 if (m_totalBytes == -1) {
269 // Update m_totalBytes only when in dynamic buffer grow mode.
270 m_totalBytes = m_bytesLoaded;
271 }
272
273 m_finishedLoading = true;
274
275 cleanup();
276 if (m_client)
277 m_client->didFinishLoading();
278 }
279
didFail(const ResourceError &)280 void FileReaderLoader::didFail(const ResourceError&)
281 {
282 // If we're aborting, do not proceed with normal error handling since it is covered in aborting code.
283 if (m_errorCode == FileError::ABORT_ERR)
284 return;
285
286 failed(FileError::NOT_READABLE_ERR);
287 }
288
failed(FileError::ErrorCode errorCode)289 void FileReaderLoader::failed(FileError::ErrorCode errorCode)
290 {
291 m_errorCode = errorCode;
292 cleanup();
293 if (m_client)
294 m_client->didFail(m_errorCode);
295 }
296
httpStatusCodeToErrorCode(int httpStatusCode)297 FileError::ErrorCode FileReaderLoader::httpStatusCodeToErrorCode(int httpStatusCode)
298 {
299 switch (httpStatusCode) {
300 case 403:
301 return FileError::SECURITY_ERR;
302 case 404:
303 return FileError::NOT_FOUND_ERR;
304 default:
305 return FileError::NOT_READABLE_ERR;
306 }
307 }
308
arrayBufferResult() const309 PassRefPtr<ArrayBuffer> FileReaderLoader::arrayBufferResult() const
310 {
311 ASSERT(m_readType == ReadAsArrayBuffer);
312
313 // If the loading is not started or an error occurs, return an empty result.
314 if (!m_rawData || m_errorCode)
315 return nullptr;
316
317 return m_rawData->toArrayBuffer();
318 }
319
stringResult()320 String FileReaderLoader::stringResult()
321 {
322 ASSERT(m_readType != ReadAsArrayBuffer && m_readType != ReadAsBlob && m_readType != ReadByClient);
323
324 // If the loading is not started or an error occurs, return an empty result.
325 if (!m_rawData || m_errorCode)
326 return m_stringResult;
327
328 // If already converted from the raw data, return the result now.
329 if (m_isRawDataConverted)
330 return m_stringResult;
331
332 switch (m_readType) {
333 case ReadAsArrayBuffer:
334 // No conversion is needed.
335 break;
336 case ReadAsBinaryString:
337 m_stringResult = m_rawData->toString();
338 m_isRawDataConverted = true;
339 break;
340 case ReadAsText:
341 convertToText();
342 break;
343 case ReadAsDataURL:
344 // Partial data is not supported when reading as data URL.
345 if (m_finishedLoading)
346 convertToDataURL();
347 break;
348 default:
349 ASSERT_NOT_REACHED();
350 }
351
352 return m_stringResult;
353 }
354
convertToText()355 void FileReaderLoader::convertToText()
356 {
357 m_isRawDataConverted = true;
358
359 if (!m_bytesLoaded) {
360 m_stringResult = "";
361 return;
362 }
363
364 // Decode the data.
365 // The File API spec says that we should use the supplied encoding if it is valid. However, we choose to ignore this
366 // requirement in order to be consistent with how WebKit decodes the web content: always has the BOM override the
367 // provided encoding.
368 // FIXME: consider supporting incremental decoding to improve the perf.
369 StringBuilder builder;
370 if (!m_decoder)
371 m_decoder = TextResourceDecoder::create("text/plain", m_encoding.isValid() ? m_encoding : UTF8Encoding());
372 builder.append(m_decoder->decode(static_cast<const char*>(m_rawData->data()), m_rawData->byteLength()));
373
374 if (m_finishedLoading)
375 builder.append(m_decoder->flush());
376
377 m_stringResult = builder.toString();
378 }
379
convertToDataURL()380 void FileReaderLoader::convertToDataURL()
381 {
382 m_isRawDataConverted = true;
383
384 StringBuilder builder;
385 builder.appendLiteral("data:");
386
387 if (!m_bytesLoaded) {
388 m_stringResult = builder.toString();
389 return;
390 }
391
392 builder.append(m_dataType);
393 builder.appendLiteral(";base64,");
394
395 Vector<char> out;
396 base64Encode(static_cast<const char*>(m_rawData->data()), m_rawData->byteLength(), out);
397 out.append('\0');
398 builder.append(out.data());
399
400 m_stringResult = builder.toString();
401 }
402
setEncoding(const String & encoding)403 void FileReaderLoader::setEncoding(const String& encoding)
404 {
405 if (!encoding.isEmpty())
406 m_encoding = WTF::TextEncoding(encoding);
407 }
408
409 } // namespace blink
410