1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
21 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include "config.h"
31#include "NetworkResourcesData.h"
32
33#include "CachedResource.h"
34#include "InspectorNetworkAgent.h"
35#include "ResourceResponse.h"
36#include "SharedBuffer.h"
37#include "TextResourceDecoder.h"
38#include <wtf/text/Base64.h>
39
40namespace WebCore {
41
42using namespace Inspector;
43
44static const size_t maximumResourcesContentSize = 200 * 1000 * 1000; // 200MB
45static const size_t maximumSingleResourceContentSize = 50 * 1000 * 1000; // 50MB
46
47NetworkResourcesData::ResourceData::ResourceData(const String& requestId, const String& loaderId)
48 : m_requestId(requestId)
49 , m_loaderId(loaderId)
50{
51}
52
53void NetworkResourcesData::ResourceData::setContent(const String& content, bool base64Encoded)
54{
55 ASSERT(!hasData());
56 ASSERT(!hasContent());
57 m_content = content;
58 m_base64Encoded = base64Encoded;
59}
60
61static size_t contentSizeInBytes(const String& content)
62{
63 return content.isNull() ? 0 : content.impl()->sizeInBytes();
64}
65
66unsigned NetworkResourcesData::ResourceData::removeContent()
67{
68 unsigned result = 0;
69 if (hasData()) {
70 ASSERT(!hasContent());
71 result = m_dataBuffer->size();
72 m_dataBuffer = nullptr;
73 }
74
75 if (hasContent()) {
76 ASSERT(!hasData());
77 result = contentSizeInBytes(m_content);
78 m_content = String();
79 }
80 return result;
81}
82
83unsigned NetworkResourcesData::ResourceData::evictContent()
84{
85 m_isContentEvicted = true;
86 return removeContent();
87}
88
89size_t NetworkResourcesData::ResourceData::dataLength() const
90{
91 return m_dataBuffer ? m_dataBuffer->size() : 0;
92}
93
94void NetworkResourcesData::ResourceData::appendData(const char* data, size_t dataLength)
95{
96 ASSERT(!hasContent());
97 if (!m_dataBuffer)
98 m_dataBuffer = SharedBuffer::create(data, dataLength);
99 else
100 m_dataBuffer->append(data, dataLength);
101}
102
103size_t NetworkResourcesData::ResourceData::decodeDataToContent()
104{
105 ASSERT(!hasContent());
106
107 size_t dataLength = m_dataBuffer->size();
108
109 if (m_decoder) {
110 m_base64Encoded = false;
111 m_content = m_decoder->decodeAndFlush(m_dataBuffer->data(), dataLength);
112 } else {
113 m_base64Encoded = true;
114 m_content = base64Encode(m_dataBuffer->data(), dataLength);
115 }
116
117 m_dataBuffer = nullptr;
118
119 size_t decodedLength = contentSizeInBytes(m_content);
120 ASSERT(decodedLength >= dataLength);
121 return decodedLength - dataLength;
122}
123
124NetworkResourcesData::NetworkResourcesData()
125 : m_maximumResourcesContentSize(maximumResourcesContentSize)
126 , m_maximumSingleResourceContentSize(maximumSingleResourceContentSize)
127{
128}
129
130NetworkResourcesData::~NetworkResourcesData()
131{
132 clear();
133}
134
135void NetworkResourcesData::resourceCreated(const String& requestId, const String& loaderId, InspectorPageAgent::ResourceType type)
136{
137 ensureNoDataForRequestId(requestId);
138
139 auto resourceData = std::make_unique<ResourceData>(requestId, loaderId);
140 resourceData->setType(type);
141 m_requestIdToResourceDataMap.set(requestId, WTFMove(resourceData));
142}
143
144void NetworkResourcesData::resourceCreated(const String& requestId, const String& loaderId, CachedResource& cachedResource)
145{
146 ensureNoDataForRequestId(requestId);
147
148 auto resourceData = std::make_unique<ResourceData>(requestId, loaderId);
149 resourceData->setCachedResource(&cachedResource);
150 m_requestIdToResourceDataMap.set(requestId, WTFMove(resourceData));
151}
152
153void NetworkResourcesData::responseReceived(const String& requestId, const String& frameId, const ResourceResponse& response, InspectorPageAgent::ResourceType type, bool forceBufferData)
154{
155 ResourceData* resourceData = resourceDataForRequestId(requestId);
156 if (!resourceData)
157 return;
158
159 resourceData->setFrameId(frameId);
160 resourceData->setURL(response.url());
161 resourceData->setHTTPStatusCode(response.httpStatusCode());
162 resourceData->setType(type);
163 resourceData->setForceBufferData(forceBufferData);
164
165 if (InspectorNetworkAgent::shouldTreatAsText(response.mimeType()))
166 resourceData->setDecoder(InspectorNetworkAgent::createTextDecoder(response.mimeType(), response.textEncodingName()));
167
168 if (auto& certificateInfo = response.certificateInfo())
169 resourceData->setCertificateInfo(certificateInfo);
170}
171
172void NetworkResourcesData::setResourceType(const String& requestId, InspectorPageAgent::ResourceType type)
173{
174 ResourceData* resourceData = resourceDataForRequestId(requestId);
175 if (!resourceData)
176 return;
177 resourceData->setType(type);
178}
179
180InspectorPageAgent::ResourceType NetworkResourcesData::resourceType(const String& requestId)
181{
182 ResourceData* resourceData = resourceDataForRequestId(requestId);
183 if (!resourceData)
184 return InspectorPageAgent::OtherResource;
185 return resourceData->type();
186}
187
188void NetworkResourcesData::setResourceContent(const String& requestId, const String& content, bool base64Encoded)
189{
190 if (content.isNull())
191 return;
192
193 ResourceData* resourceData = resourceDataForRequestId(requestId);
194 if (!resourceData)
195 return;
196
197 size_t dataLength = contentSizeInBytes(content);
198 if (dataLength > m_maximumSingleResourceContentSize)
199 return;
200 if (resourceData->isContentEvicted())
201 return;
202
203 if (ensureFreeSpace(dataLength) && !resourceData->isContentEvicted()) {
204 // We can not be sure that we didn't try to save this request data while it was loading, so remove it, if any.
205 if (resourceData->hasContent() || resourceData->hasData())
206 m_contentSize -= resourceData->removeContent();
207 m_requestIdsDeque.append(requestId);
208 resourceData->setContent(content, base64Encoded);
209 m_contentSize += dataLength;
210 }
211}
212
213static bool shouldBufferResourceData(const NetworkResourcesData::ResourceData& resourceData)
214{
215 if (resourceData.forceBufferData())
216 return true;
217
218 if (resourceData.decoder())
219 return true;
220
221 // Buffer data for Web Inspector when the rest of the system would not normally buffer.
222 if (resourceData.cachedResource() && resourceData.cachedResource()->dataBufferingPolicy() == DataBufferingPolicy::DoNotBufferData)
223 return true;
224
225 return false;
226}
227
228NetworkResourcesData::ResourceData const* NetworkResourcesData::maybeAddResourceData(const String& requestId, const char* data, size_t dataLength)
229{
230 ResourceData* resourceData = resourceDataForRequestId(requestId);
231 if (!resourceData)
232 return nullptr;
233
234 if (!shouldBufferResourceData(*resourceData))
235 return resourceData;
236
237 if (resourceData->dataLength() + dataLength > m_maximumSingleResourceContentSize)
238 m_contentSize -= resourceData->evictContent();
239 if (resourceData->isContentEvicted())
240 return resourceData;
241
242 if (ensureFreeSpace(dataLength) && !resourceData->isContentEvicted()) {
243 m_requestIdsDeque.append(requestId);
244 resourceData->appendData(data, dataLength);
245 m_contentSize += dataLength;
246 }
247
248 return resourceData;
249}
250
251void NetworkResourcesData::maybeDecodeDataToContent(const String& requestId)
252{
253 ResourceData* resourceData = resourceDataForRequestId(requestId);
254 if (!resourceData)
255 return;
256
257 if (!resourceData->hasData())
258 return;
259
260 m_contentSize += resourceData->decodeDataToContent();
261 size_t dataLength = contentSizeInBytes(resourceData->content());
262 if (dataLength > m_maximumSingleResourceContentSize)
263 m_contentSize -= resourceData->evictContent();
264}
265
266void NetworkResourcesData::addCachedResource(const String& requestId, CachedResource* cachedResource)
267{
268 ResourceData* resourceData = resourceDataForRequestId(requestId);
269 if (!resourceData)
270 return;
271 resourceData->setCachedResource(cachedResource);
272}
273
274void NetworkResourcesData::addResourceSharedBuffer(const String& requestId, RefPtr<SharedBuffer>&& buffer, const String& textEncodingName)
275{
276 ResourceData* resourceData = resourceDataForRequestId(requestId);
277 if (!resourceData)
278 return;
279 resourceData->setBuffer(WTFMove(buffer));
280 resourceData->setTextEncodingName(textEncodingName);
281}
282
283NetworkResourcesData::ResourceData const* NetworkResourcesData::data(const String& requestId)
284{
285 return resourceDataForRequestId(requestId);
286}
287
288Vector<String> NetworkResourcesData::removeCachedResource(CachedResource* cachedResource)
289{
290 Vector<String> result;
291 for (auto& entry : m_requestIdToResourceDataMap) {
292 ResourceData* resourceData = entry.value.get();
293 if (resourceData->cachedResource() == cachedResource) {
294 resourceData->setCachedResource(nullptr);
295 result.append(entry.key);
296 }
297 }
298
299 return result;
300}
301
302void NetworkResourcesData::clear(Optional<String> preservedLoaderId)
303{
304 m_requestIdsDeque.clear();
305 m_contentSize = 0;
306
307 if (!preservedLoaderId)
308 m_requestIdToResourceDataMap.clear();
309 else {
310 m_requestIdToResourceDataMap.removeIf([loaderId = *preservedLoaderId] (auto& entry) {
311 return entry.value->loaderId() != loaderId;
312 });
313 }
314}
315
316Vector<NetworkResourcesData::ResourceData*> NetworkResourcesData::resources()
317{
318 return WTF::map(m_requestIdToResourceDataMap.values(), [] (const auto& v) { return v.get(); });
319}
320
321NetworkResourcesData::ResourceData* NetworkResourcesData::resourceDataForRequestId(const String& requestId)
322{
323 if (requestId.isNull())
324 return nullptr;
325 return m_requestIdToResourceDataMap.get(requestId);
326}
327
328void NetworkResourcesData::ensureNoDataForRequestId(const String& requestId)
329{
330 auto result = m_requestIdToResourceDataMap.take(requestId);
331 if (!result)
332 return;
333
334 ResourceData* resourceData = result.get();
335 if (resourceData->hasContent() || resourceData->hasData())
336 m_contentSize -= resourceData->evictContent();
337}
338
339bool NetworkResourcesData::ensureFreeSpace(size_t size)
340{
341 if (size > m_maximumResourcesContentSize)
342 return false;
343
344 while (size > m_maximumResourcesContentSize - m_contentSize) {
345 String requestId = m_requestIdsDeque.takeFirst();
346 ResourceData* resourceData = resourceDataForRequestId(requestId);
347 if (resourceData)
348 m_contentSize -= resourceData->evictContent();
349 }
350 return true;
351}
352
353} // namespace WebCore
354