1/*
2    Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3    Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4    Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5    Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6    Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
7
8    This library is free software; you can redistribute it and/or
9    modify it under the terms of the GNU Library General Public
10    License as published by the Free Software Foundation; either
11    version 2 of the License, or (at your option) any later version.
12
13    This library is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    Library General Public License for more details.
17
18    You should have received a copy of the GNU Library General Public License
19    along with this library; see the file COPYING.LIB.  If not, write to
20    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21    Boston, MA 02110-1301, USA.
22*/
23
24#include "config.h"
25#include "CachedResourceRequest.h"
26
27#include "MemoryCache.h"
28#include "CachedImage.h"
29#include "CachedResource.h"
30#include "CachedResourceLoader.h"
31#include "Frame.h"
32#include "FrameLoader.h"
33#include "Logging.h"
34#include "ResourceHandle.h"
35#include "ResourceLoadScheduler.h"
36#include "ResourceRequest.h"
37#include "ResourceResponse.h"
38#include "SharedBuffer.h"
39#include <wtf/Assertions.h>
40#include <wtf/Vector.h>
41#include <wtf/text/CString.h>
42
43namespace WebCore {
44
45static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type, ResourceLoadPriority priority)
46{
47#if !ENABLE(LINK_PREFETCH)
48    UNUSED_PARAM(priority);
49#endif
50    switch (type) {
51    case CachedResource::CSSStyleSheet:
52#if ENABLE(XSLT)
53    case CachedResource::XSLStyleSheet:
54#endif
55        return ResourceRequest::TargetIsStyleSheet;
56    case CachedResource::Script:
57        return ResourceRequest::TargetIsScript;
58    case CachedResource::FontResource:
59        return ResourceRequest::TargetIsFontResource;
60    case CachedResource::ImageResource:
61        return ResourceRequest::TargetIsImage;
62#if ENABLE(LINK_PREFETCH)
63    case CachedResource::LinkResource:
64        if (priority == ResourceLoadPriorityLowest)
65            return ResourceRequest::TargetIsPrefetch;
66        return ResourceRequest::TargetIsSubresource;
67#endif
68    }
69    ASSERT_NOT_REACHED();
70    return ResourceRequest::TargetIsSubresource;
71}
72
73CachedResourceRequest::CachedResourceRequest(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental)
74    : m_cachedResourceLoader(cachedResourceLoader)
75    , m_resource(resource)
76    , m_incremental(incremental)
77    , m_multipart(false)
78    , m_finishing(false)
79{
80    m_resource->setRequest(this);
81}
82
83CachedResourceRequest::~CachedResourceRequest()
84{
85    m_resource->setRequest(0);
86}
87
88PassRefPtr<CachedResourceRequest> CachedResourceRequest::load(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks)
89{
90    RefPtr<CachedResourceRequest> request = adoptRef(new CachedResourceRequest(cachedResourceLoader, resource, incremental));
91
92    ResourceRequest resourceRequest(resource->url());
93    resourceRequest.setTargetType(cachedResourceTypeToTargetType(resource->type(), resource->loadPriority()));
94
95    if (!resource->accept().isEmpty())
96        resourceRequest.setHTTPAccept(resource->accept());
97
98    if (resource->isCacheValidator()) {
99        CachedResource* resourceToRevalidate = resource->resourceToRevalidate();
100        ASSERT(resourceToRevalidate->canUseCacheValidator());
101        ASSERT(resourceToRevalidate->isLoaded());
102        const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified");
103        const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag");
104        if (!lastModified.isEmpty() || !eTag.isEmpty()) {
105            ASSERT(cachedResourceLoader->cachePolicy() != CachePolicyReload);
106            if (cachedResourceLoader->cachePolicy() == CachePolicyRevalidate)
107                resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0");
108            if (!lastModified.isEmpty())
109                resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified);
110            if (!eTag.isEmpty())
111                resourceRequest.setHTTPHeaderField("If-None-Match", eTag);
112        }
113    }
114
115#if ENABLE(LINK_PREFETCH)
116    if (resource->type() == CachedResource::LinkResource)
117        resourceRequest.setHTTPHeaderField("Purpose", "prefetch");
118#endif
119
120    ResourceLoadPriority priority = resource->loadPriority();
121    resourceRequest.setPriority(priority);
122
123    RefPtr<SubresourceLoader> loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(),
124        request.get(), resourceRequest, priority, securityCheck, sendResourceLoadCallbacks);
125    if (!loader || loader->reachedTerminalState()) {
126        // FIXME: What if resources in other frames were waiting for this revalidation?
127        LOG(ResourceLoading, "Cannot start loading '%s'", resource->url().latin1().data());
128        cachedResourceLoader->decrementRequestCount(resource);
129        cachedResourceLoader->loadFinishing();
130        if (resource->resourceToRevalidate())
131            memoryCache()->revalidationFailed(resource);
132        resource->error(CachedResource::LoadError);
133        cachedResourceLoader->loadDone(0);
134        return 0;
135    }
136    request->m_loader = loader;
137    return request.release();
138}
139
140void CachedResourceRequest::willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&)
141{
142    m_resource->setRequestedFromNetworkingLayer();
143}
144
145void CachedResourceRequest::didFinishLoading(SubresourceLoader* loader, double)
146{
147    if (m_finishing)
148        return;
149
150    ASSERT(loader == m_loader.get());
151    ASSERT(!m_resource->resourceToRevalidate());
152    LOG(ResourceLoading, "Received '%s'.", m_resource->url().latin1().data());
153
154    // Prevent the document from being destroyed before we are done with
155    // the cachedResourceLoader that it will delete when the document gets deleted.
156    RefPtr<Document> protector(m_cachedResourceLoader->document());
157    if (!m_multipart)
158        m_cachedResourceLoader->decrementRequestCount(m_resource);
159    m_finishing = true;
160
161    // If we got a 4xx response, we're pretending to have received a network
162    // error, so we can't send the successful data() and finish() callbacks.
163    if (!m_resource->errorOccurred()) {
164        m_cachedResourceLoader->loadFinishing();
165        m_resource->data(loader->resourceData(), true);
166        if (!m_resource->errorOccurred())
167            m_resource->finish();
168    }
169    m_cachedResourceLoader->loadDone(this);
170}
171
172void CachedResourceRequest::didFail(SubresourceLoader*, const ResourceError&)
173{
174    if (!m_loader)
175        return;
176    didFail();
177}
178
179void CachedResourceRequest::didFail(bool cancelled)
180{
181    if (m_finishing)
182        return;
183
184    LOG(ResourceLoading, "Failed to load '%s' (cancelled=%d).\n", m_resource->url().latin1().data(), cancelled);
185
186    // Prevent the document from being destroyed before we are done with
187    // the cachedResourceLoader that it will delete when the document gets deleted.
188    RefPtr<Document> protector(m_cachedResourceLoader->document());
189    if (!m_multipart)
190        m_cachedResourceLoader->decrementRequestCount(m_resource);
191    m_finishing = true;
192    m_loader->clearClient();
193
194    if (m_resource->resourceToRevalidate())
195        memoryCache()->revalidationFailed(m_resource);
196
197    if (!cancelled) {
198        m_cachedResourceLoader->loadFinishing();
199        m_resource->error(CachedResource::LoadError);
200    }
201
202    if (cancelled || !m_resource->isPreloaded())
203        memoryCache()->remove(m_resource);
204
205    m_cachedResourceLoader->loadDone(this);
206}
207
208void CachedResourceRequest::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response)
209{
210    ASSERT(loader == m_loader.get());
211    if (m_resource->isCacheValidator()) {
212        if (response.httpStatusCode() == 304) {
213            // 304 Not modified / Use local copy
214            loader->clearClient();
215            RefPtr<Document> protector(m_cachedResourceLoader->document());
216            m_cachedResourceLoader->decrementRequestCount(m_resource);
217            m_finishing = true;
218
219            // Existing resource is ok, just use it updating the expiration time.
220            memoryCache()->revalidationSucceeded(m_resource, response);
221
222            if (m_cachedResourceLoader->frame())
223                m_cachedResourceLoader->frame()->loader()->checkCompleted();
224
225            m_cachedResourceLoader->loadDone(this);
226            return;
227        }
228        // Did not get 304 response, continue as a regular resource load.
229        memoryCache()->revalidationFailed(m_resource);
230    }
231
232    m_resource->setResponse(response);
233
234    String encoding = response.textEncodingName();
235    if (!encoding.isNull())
236        m_resource->setEncoding(encoding);
237
238    if (m_multipart) {
239        ASSERT(m_resource->isImage());
240        static_cast<CachedImage*>(m_resource)->clear();
241        if (m_cachedResourceLoader->frame())
242            m_cachedResourceLoader->frame()->loader()->checkCompleted();
243    } else if (response.isMultipart()) {
244        m_multipart = true;
245
246        // We don't count multiParts in a CachedResourceLoader's request count
247        m_cachedResourceLoader->decrementRequestCount(m_resource);
248
249        // If we get a multipart response, we must have a handle
250        ASSERT(loader->handle());
251        if (!m_resource->isImage())
252            loader->handle()->cancel();
253    }
254}
255
256void CachedResourceRequest::didReceiveData(SubresourceLoader* loader, const char* data, int size)
257{
258    ASSERT(loader == m_loader.get());
259    ASSERT(!m_resource->isCacheValidator());
260
261    if (m_resource->errorOccurred())
262        return;
263
264    if (m_resource->response().httpStatusCode() >= 400) {
265        if (!m_resource->shouldIgnoreHTTPStatusCodeErrors())
266            m_resource->error(CachedResource::LoadError);
267        return;
268    }
269
270    // Set the data.
271    if (m_multipart) {
272        // The loader delivers the data in a multipart section all at once, send eof.
273        // The resource data will change as the next part is loaded, so we need to make a copy.
274        RefPtr<SharedBuffer> copiedData = SharedBuffer::create(data, size);
275        m_resource->data(copiedData.release(), true);
276    } else if (m_incremental)
277        m_resource->data(loader->resourceData(), false);
278}
279
280void CachedResourceRequest::didReceiveCachedMetadata(SubresourceLoader*, const char* data, int size)
281{
282    ASSERT(!m_resource->isCacheValidator());
283    m_resource->setSerializedCachedMetadata(data, size);
284}
285
286} //namespace WebCore
287