1/*
2 * Copyright (C) 2006, 2007, 2009 Apple 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
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "SubresourceLoader.h"
31
32#include "CachedResourceLoader.h"
33#include "Document.h"
34#include "DocumentLoader.h"
35#include "Frame.h"
36#include "FrameLoader.h"
37#include "Logging.h"
38#include "MemoryCache.h"
39#include "Page.h"
40#include "PageActivityAssertionToken.h"
41#include "ResourceBuffer.h"
42#include <wtf/Ref.h>
43#include <wtf/RefCountedLeakCounter.h>
44#include <wtf/StdLibExtras.h>
45#include <wtf/text/CString.h>
46
47#if PLATFORM(IOS)
48#include <RuntimeApplicationChecksIOS.h>
49#endif
50
51namespace WebCore {
52
53DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, subresourceLoaderCounter, ("SubresourceLoader"));
54
55SubresourceLoader::RequestCountTracker::RequestCountTracker(CachedResourceLoader* cachedResourceLoader, CachedResource* resource)
56    : m_cachedResourceLoader(cachedResourceLoader)
57    , m_resource(resource)
58{
59    m_cachedResourceLoader->incrementRequestCount(m_resource);
60}
61
62SubresourceLoader::RequestCountTracker::~RequestCountTracker()
63{
64    m_cachedResourceLoader->decrementRequestCount(m_resource);
65}
66
67SubresourceLoader::SubresourceLoader(Frame* frame, CachedResource* resource, const ResourceLoaderOptions& options)
68    : ResourceLoader(frame, options)
69    , m_resource(resource)
70    , m_loadingMultipartContent(false)
71    , m_state(Uninitialized)
72    , m_requestCountTracker(adoptPtr(new RequestCountTracker(frame->document()->cachedResourceLoader(), resource)))
73{
74#ifndef NDEBUG
75    subresourceLoaderCounter.increment();
76#endif
77}
78
79SubresourceLoader::~SubresourceLoader()
80{
81    ASSERT(m_state != Initialized);
82    ASSERT(reachedTerminalState());
83#ifndef NDEBUG
84    subresourceLoaderCounter.decrement();
85#endif
86}
87
88PassRefPtr<SubresourceLoader> SubresourceLoader::create(Frame* frame, CachedResource* resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
89{
90    RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, resource, options)));
91#if PLATFORM(IOS)
92    if (!applicationIsWebProcess()) {
93        // On iOS, do not invoke synchronous resource load delegates while resource load scheduling
94        // is disabled to avoid re-entering style selection from a different thread (see <rdar://problem/9121719>).
95        // FIXME: This should be fixed for all ports in <https://bugs.webkit.org/show_bug.cgi?id=56647>.
96        subloader->m_iOSOriginalRequest = request;
97        return subloader.release();
98    }
99#endif
100    if (!subloader->init(request))
101        return nullptr;
102    return subloader.release();
103}
104
105#if PLATFORM(IOS)
106bool SubresourceLoader::startLoading()
107{
108    ASSERT(!applicationIsWebProcess());
109    if (!init(m_iOSOriginalRequest))
110        return false;
111    m_iOSOriginalRequest = ResourceRequest();
112    start();
113    return true;
114}
115#endif
116
117CachedResource* SubresourceLoader::cachedResource()
118{
119    return m_resource;
120}
121
122void SubresourceLoader::cancelIfNotFinishing()
123{
124    if (m_state != Initialized)
125        return;
126
127    ResourceLoader::cancel();
128}
129
130bool SubresourceLoader::init(const ResourceRequest& request)
131{
132    if (!ResourceLoader::init(request))
133        return false;
134
135    ASSERT(!reachedTerminalState());
136    m_state = Initialized;
137    m_documentLoader->addSubresourceLoader(this);
138    return true;
139}
140
141bool SubresourceLoader::isSubresourceLoader()
142{
143    return true;
144}
145
146void SubresourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
147{
148    // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it.
149    URL previousURL = request().url();
150    Ref<SubresourceLoader> protect(*this);
151
152    ASSERT(!newRequest.isNull());
153    if (!redirectResponse.isNull()) {
154        // CachedResources are keyed off their original request URL.
155        // Requesting the same original URL a second time can redirect to a unique second resource.
156        // Therefore, if a redirect to a different destination URL occurs, we should no longer consider this a revalidation of the first resource.
157        // Doing so would have us reusing the resource from the first request if the second request's revalidation succeeds.
158        if (newRequest.isConditional() && m_resource->resourceToRevalidate() && newRequest.url() != m_resource->resourceToRevalidate()->response().url()) {
159            newRequest.makeUnconditional();
160            memoryCache()->revalidationFailed(m_resource);
161        }
162
163        if (!m_documentLoader->cachedResourceLoader().canRequest(m_resource->type(), newRequest.url(), options())) {
164            cancel();
165            return;
166        }
167        if (m_resource->isImage() && m_documentLoader->cachedResourceLoader().shouldDeferImageLoad(newRequest.url())) {
168            cancel();
169            return;
170        }
171        m_resource->willSendRequest(newRequest, redirectResponse);
172    }
173
174    if (newRequest.isNull() || reachedTerminalState())
175        return;
176
177    ResourceLoader::willSendRequest(newRequest, redirectResponse);
178    if (newRequest.isNull())
179        cancel();
180}
181
182void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
183{
184    ASSERT(m_state == Initialized);
185    Ref<SubresourceLoader> protect(*this);
186    m_resource->didSendData(bytesSent, totalBytesToBeSent);
187}
188
189void SubresourceLoader::didReceiveResponse(const ResourceResponse& response)
190{
191    ASSERT(!response.isNull());
192    ASSERT(m_state == Initialized);
193
194    // Reference the object in this method since the additional processing can do
195    // anything including removing the last reference to this object; one example of this is 3266216.
196    Ref<SubresourceLoader> protect(*this);
197
198    if (m_resource->resourceToRevalidate()) {
199        if (response.httpStatusCode() == 304) {
200            // 304 Not modified / Use local copy
201            // Existing resource is ok, just use it updating the expiration time.
202            m_resource->setResponse(response);
203            memoryCache()->revalidationSucceeded(m_resource, response);
204            if (!reachedTerminalState())
205                ResourceLoader::didReceiveResponse(response);
206            return;
207        }
208        // Did not get 304 response, continue as a regular resource load.
209        memoryCache()->revalidationFailed(m_resource);
210    }
211
212    m_resource->responseReceived(response);
213    if (reachedTerminalState())
214        return;
215
216    ResourceLoader::didReceiveResponse(response);
217    if (reachedTerminalState())
218        return;
219
220    // FIXME: Main resources have a different set of rules for multipart than images do.
221    // Hopefully we can merge those 2 paths.
222    if (response.isMultipart() && m_resource->type() != CachedResource::MainResource) {
223        m_loadingMultipartContent = true;
224
225        // We don't count multiParts in a CachedResourceLoader's request count
226        m_requestCountTracker.clear();
227        if (!m_resource->isImage()) {
228            cancel();
229            return;
230        }
231    }
232
233    RefPtr<ResourceBuffer> buffer = resourceData();
234    if (m_loadingMultipartContent && buffer && buffer->size()) {
235        // The resource data will change as the next part is loaded, so we need to make a copy.
236        RefPtr<ResourceBuffer> copiedData = ResourceBuffer::create(buffer->data(), buffer->size());
237        m_resource->finishLoading(copiedData.get());
238        clearResourceData();
239        // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
240        // After the first multipart section is complete, signal to delegates that this load is "finished"
241        m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this);
242        didFinishLoadingOnePart(0);
243    }
244
245    checkForHTTPStatusCodeError();
246}
247
248void SubresourceLoader::didReceiveData(const char* data, unsigned length, long long encodedDataLength, DataPayloadType dataPayloadType)
249{
250    didReceiveDataOrBuffer(data, length, 0, encodedDataLength, dataPayloadType);
251}
252
253void SubresourceLoader::didReceiveBuffer(PassRefPtr<SharedBuffer> buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
254{
255    didReceiveDataOrBuffer(0, 0, buffer, encodedDataLength, dataPayloadType);
256}
257
258void SubresourceLoader::didReceiveDataOrBuffer(const char* data, int length, PassRefPtr<SharedBuffer> prpBuffer, long long encodedDataLength, DataPayloadType dataPayloadType)
259{
260    if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
261        return;
262    ASSERT(!m_resource->resourceToRevalidate());
263    ASSERT(!m_resource->errorOccurred());
264    ASSERT(m_state == Initialized);
265    // Reference the object in this method since the additional processing can do
266    // anything including removing the last reference to this object; one example of this is 3266216.
267    Ref<SubresourceLoader> protect(*this);
268    RefPtr<SharedBuffer> buffer = prpBuffer;
269
270    ResourceLoader::didReceiveDataOrBuffer(data, length, buffer, encodedDataLength, dataPayloadType);
271
272    if (!m_loadingMultipartContent) {
273        if (ResourceBuffer* resourceData = this->resourceData())
274            m_resource->addDataBuffer(resourceData);
275        else
276            m_resource->addData(buffer ? buffer->data() : data, buffer ? buffer->size() : length);
277    }
278}
279
280bool SubresourceLoader::checkForHTTPStatusCodeError()
281{
282    if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
283        return false;
284
285    m_state = Finishing;
286    m_resource->error(CachedResource::LoadError);
287    cancel();
288    return true;
289}
290
291void SubresourceLoader::didFinishLoading(double finishTime)
292{
293    if (m_state != Initialized)
294        return;
295    ASSERT(!reachedTerminalState());
296    ASSERT(!m_resource->resourceToRevalidate());
297    // FIXME (129394): We should cancel the load when a decode error occurs instead of continuing the load to completion.
298    ASSERT(!m_resource->errorOccurred() || m_resource->status() == CachedResource::DecodeError);
299    LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
300
301    Ref<SubresourceLoader> protect(*this);
302
303#if PLATFORM(IOS)
304    if (resourceData())
305        resourceData()->setShouldUsePurgeableMemory(true);
306#endif
307    CachedResourceHandle<CachedResource> protectResource(m_resource);
308    m_state = Finishing;
309    m_resource->setLoadFinishTime(finishTime);
310    m_resource->finishLoading(resourceData());
311
312    if (wasCancelled())
313        return;
314    m_resource->finish();
315    ASSERT(!reachedTerminalState());
316    didFinishLoadingOnePart(finishTime);
317    notifyDone();
318    if (reachedTerminalState())
319        return;
320    releaseResources();
321}
322
323void SubresourceLoader::didFail(const ResourceError& error)
324{
325    if (m_state != Initialized)
326        return;
327    ASSERT(!reachedTerminalState());
328    LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
329
330    Ref<SubresourceLoader> protect(*this);
331    CachedResourceHandle<CachedResource> protectResource(m_resource);
332    m_state = Finishing;
333    if (m_resource->resourceToRevalidate())
334        memoryCache()->revalidationFailed(m_resource);
335    m_resource->setResourceError(error);
336    if (!m_resource->isPreloaded())
337        memoryCache()->remove(m_resource);
338    m_resource->error(CachedResource::LoadError);
339    cleanupForError(error);
340    notifyDone();
341    if (reachedTerminalState())
342        return;
343    releaseResources();
344}
345
346void SubresourceLoader::willCancel(const ResourceError& error)
347{
348#if PLATFORM(IOS)
349    // Since we defer initialization to scheduling time on iOS but
350    // CachedResourceLoader stores resources in the memory cache immediately,
351    // m_resource might be cached despite its loader not being initialized.
352    if (m_state != Initialized && m_state != Uninitialized)
353#else
354    if (m_state != Initialized)
355#endif
356        return;
357    ASSERT(!reachedTerminalState());
358    LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
359
360    Ref<SubresourceLoader> protect(*this);
361#if PLATFORM(IOS)
362    m_state = m_state == Uninitialized ? CancelledWhileInitializing : Finishing;
363#else
364    m_state = Finishing;
365#endif
366    if (m_resource->resourceToRevalidate())
367        memoryCache()->revalidationFailed(m_resource);
368    m_resource->setResourceError(error);
369    memoryCache()->remove(m_resource);
370}
371
372void SubresourceLoader::didCancel(const ResourceError&)
373{
374    if (m_state == Uninitialized)
375        return;
376
377    m_resource->cancelLoad();
378    notifyDone();
379}
380
381void SubresourceLoader::notifyDone()
382{
383    if (reachedTerminalState())
384        return;
385
386    m_requestCountTracker.clear();
387#if PLATFORM(IOS)
388    m_documentLoader->cachedResourceLoader().loadDone(m_resource, m_state != CancelledWhileInitializing);
389#else
390    m_documentLoader->cachedResourceLoader().loadDone(m_resource);
391#endif
392    if (reachedTerminalState())
393        return;
394    m_documentLoader->removeSubresourceLoader(this);
395}
396
397void SubresourceLoader::releaseResources()
398{
399    ASSERT(!reachedTerminalState());
400#if PLATFORM(IOS)
401    if (m_state != Uninitialized && m_state != CancelledWhileInitializing)
402#else
403    if (m_state != Uninitialized)
404#endif
405        m_resource->clearLoader();
406    m_resource = 0;
407    ResourceLoader::releaseResources();
408}
409
410}
411