blob: e90620f0f2ca7ff4b7b8f8f7dcfa478ab6e894bd [file] [log] [blame]
/*
* Copyright (C) 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "NetworkCaptureRecorder.h"
#if ENABLE(NETWORK_CAPTURE)
#include "NetworkCaptureLogging.h"
#include "NetworkCaptureManager.h"
#include <WebCore/ResourceResponse.h>
#include <WebCore/SharedBuffer.h>
#define DEBUG_CLASS Recorder
namespace WebKit {
namespace NetworkCapture {
void Recorder::recordRequestSent(const WebCore::ResourceRequest& request)
{
// This class records NetworkLoad's process of loading a network resource.
// NetworkLoad does this by creating a NetworkDataTask and calling resume
// on it. This call to resume can be called immediately after the task has
// been created, or -- if the loading is marked as deferred -- it can be
// called later in NetworkLoad::setDeferred(true). In fact, the latter can
// be called multiple times if the loading is suspended and resumed
// multiple times.
//
// This method is called in both places where resume is called. Our task is
// to catch the call to NetworkDataTask::resume that starts the network
// loading process. We want to ignore the other calls to resume. Our
// approach to knowing which one is the first is to check our collection of
// recorded events. If it is empty, then this is the first call into the
// recorder, and we want to record the event. Otherwise, ignore it.
if (m_events.size())
return;
DEBUG_LOG("Sent request for URL = " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
m_initialRequest = request;
recordEvent(RequestSentEvent(request));
}
void Recorder::recordResponseReceived(const WebCore::ResourceResponse& response)
{
// Called when receiving a response other than a redirect or error.
DEBUG_LOG("Received response from URL = " STRING_SPECIFIER, DEBUG_STR(response.url().string()));
ASSERT(m_events.size());
// TODO: Is there a better response to receiving a multi-part resource?
// Learn more about multi-part resources. Why don't we record these? (Note,
// this decision is based on some NetworkCache code.)
if (!response.isMultipart())
recordEvent(ResponseReceivedEvent(response));
else
m_events.clear();
}
void Recorder::recordRedirectReceived(const WebCore::ResourceRequest& request, const WebCore::ResourceResponse& response)
{
DEBUG_LOG("Received redirect to URL = " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
ASSERT(m_events.size());
recordEvent(RedirectReceivedEvent(request, response));
}
void Recorder::recordRedirectSent(const WebCore::ResourceRequest& request)
{
DEBUG_LOG("Sent redirect for URL = " STRING_SPECIFIER, DEBUG_STR(request.url().string()));
ASSERT(m_events.size());
recordEvent(RedirectSentEvent(request));
}
void Recorder::recordDataReceived(WebCore::SharedBuffer& buffer)
{
DEBUG_LOG("Received %u bytes of data", buffer.size());
if (!m_events.size())
return;
// Prevent memory growth in case of streaming data. TODO: Is there a better
// response to this? If we encounter this condition, all of our recording
// silently goes out the window. Replay will not work, and the user doesn't
// know that.
constexpr size_t kMaximumCacheBufferSize = 10 * 1024 * 1024;
m_dataLength += buffer.size();
if (m_dataLength <= kMaximumCacheBufferSize)
recordEvent(DataReceivedEvent(buffer));
else
m_events.clear();
}
void Recorder::recordFinish(const WebCore::ResourceError& error)
{
DEBUG_LOG("Finished");
if (!m_events.size())
return;
recordEvent(FinishedEvent(error));
writeEvents();
}
void Recorder::writeEvents()
{
auto path = Manager::singleton().requestToPath(m_initialRequest);
auto handle = Manager::singleton().openCacheFile(path, WebCore::FileSystem::FileOpenMode::Write);
if (!handle)
return;
for (auto const& event : m_events) {
auto asString = eventToString(event);
// Write out the JSON string with the terminating NUL. This allows us
// to better find the separate JSON objects that we write to a single
// file. It also works better with JSON parsers that expect to find a
// NUL at the end of their input.
if (handle.write(asString.c_str(), asString.size() + 1) == -1) {
DEBUG_LOG_ERROR("Error trying to write to file for URL = " STRING_SPECIFIER, DEBUG_STR(m_initialRequest.url().string()));
return;
}
}
Manager::singleton().logRecordedResource(m_initialRequest);
}
} // namespace NetworkCapture
} // namespace WebKit
#endif // ENABLE(NETWORK_CAPTURE)