blob: fea4e0e02121d296cdde431d2cb6706a825069ce [file] [log] [blame]
/*
* Copyright (C) 2004, 2006, 2008, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2009 Google Inc. All rights reserved.
* Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "FormData.h"
#include "BlobRegistryImpl.h"
#include "BlobURL.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "DOMFormData.h"
#include "File.h"
#include "FormDataBuilder.h"
#include "Page.h"
#include "SharedBuffer.h"
#include "TextEncoding.h"
#include "ThreadableBlobRegistry.h"
#include <wtf/FileSystem.h>
#include <wtf/text/LineEnding.h>
namespace WebCore {
inline FormData::FormData()
{
}
inline FormData::FormData(const FormData& data)
: RefCounted<FormData>()
, m_elements(data.m_elements)
, m_identifier(data.m_identifier)
, m_alwaysStream(false)
, m_containsPasswordData(data.m_containsPasswordData)
{
}
FormData::~FormData()
{
}
Ref<FormData> FormData::create()
{
return adoptRef(*new FormData);
}
Ref<FormData> FormData::create(const void* data, size_t size)
{
auto result = create();
result->appendData(data, size);
return result;
}
Ref<FormData> FormData::create(const CString& string)
{
return create(string.data(), string.length());
}
Ref<FormData> FormData::create(const Vector<char>& vector)
{
return create(vector.data(), vector.size());
}
Ref<FormData> FormData::create(Vector<char>&& vector)
{
auto data = create();
data->m_elements.append(WTFMove(vector));
return data;
}
Ref<FormData> FormData::create(const Vector<uint8_t>& vector)
{
return create(vector.data(), vector.size());
}
Ref<FormData> FormData::create(const DOMFormData& formData, EncodingType encodingType)
{
auto result = create();
result->appendNonMultiPartKeyValuePairItems(formData, encodingType);
return result;
}
Ref<FormData> FormData::createMultiPart(const DOMFormData& formData)
{
auto result = create();
result->appendMultiPartKeyValuePairItems(formData);
return result;
}
Ref<FormData> FormData::copy() const
{
return adoptRef(*new FormData(*this));
}
Ref<FormData> FormData::isolatedCopy() const
{
// FIXME: isolatedCopy() does not copy m_identifier, m_boundary, or m_containsPasswordData.
// Is all of that correct and intentional?
auto formData = create();
formData->m_alwaysStream = m_alwaysStream;
formData->m_elements.reserveInitialCapacity(m_elements.size());
for (auto& element : m_elements)
formData->m_elements.uncheckedAppend(element.isolatedCopy());
return formData;
}
static inline uint64_t computeLengthInBytes(const FormDataElement& element, const Function<uint64_t(const URL&)>& blobSize)
{
return switchOn(element.data,
[] (const Vector<char>& bytes) {
return static_cast<uint64_t>(bytes.size());
}, [] (const FormDataElement::EncodedFileData& fileData) {
if (fileData.fileLength != BlobDataItem::toEndOfFile)
return static_cast<uint64_t>(fileData.fileLength);
long long fileSize;
if (FileSystem::getFileSize(fileData.filename, fileSize))
return static_cast<uint64_t>(fileSize);
return static_cast<uint64_t>(0);
}, [&blobSize] (const FormDataElement::EncodedBlobData& blobData) {
return blobSize(blobData.url);
}
);
}
uint64_t FormDataElement::lengthInBytes(BlobRegistryImpl* blobRegistry) const
{
return computeLengthInBytes(*this, [&](auto& url) {
return blobRegistry ? blobRegistry->blobSize(url) : 0;
});
}
uint64_t FormDataElement::lengthInBytes() const
{
return computeLengthInBytes(*this, [](auto& url) {
return blobRegistry().blobSize(url);
});
}
FormDataElement FormDataElement::isolatedCopy() const
{
return switchOn(data,
[] (const Vector<char>& bytes) {
Vector<char> copy;
copy.append(bytes.data(), bytes.size());
return FormDataElement(WTFMove(copy));
}, [] (const FormDataElement::EncodedFileData& fileData) {
return FormDataElement(fileData.isolatedCopy());
}, [] (const FormDataElement::EncodedBlobData& blobData) {
return FormDataElement(blobData.url.isolatedCopy());
}
);
}
void FormData::appendData(const void* data, size_t size)
{
m_lengthInBytes = WTF::nullopt;
if (!m_elements.isEmpty()) {
if (auto* vector = WTF::get_if<Vector<char>>(m_elements.last().data)) {
vector->append(reinterpret_cast<const char*>(data), size);
return;
}
}
Vector<char> vector;
vector.append(reinterpret_cast<const char*>(data), size);
m_elements.append(WTFMove(vector));
}
void FormData::appendFile(const String& filename)
{
m_elements.append(FormDataElement(filename, 0, BlobDataItem::toEndOfFile, WTF::nullopt));
m_lengthInBytes = WTF::nullopt;
}
void FormData::appendFileRange(const String& filename, long long start, long long length, Optional<WallTime> expectedModificationTime)
{
m_elements.append(FormDataElement(filename, start, length, expectedModificationTime));
m_lengthInBytes = WTF::nullopt;
}
void FormData::appendBlob(const URL& blobURL)
{
m_elements.append(FormDataElement(blobURL));
m_lengthInBytes = WTF::nullopt;
}
static Vector<uint8_t> normalizeStringData(TextEncoding& encoding, const String& value)
{
return normalizeLineEndingsToCRLF(encoding.encode(value, UnencodableHandling::Entities));
}
void FormData::appendMultiPartFileValue(const File& file, Vector<char>& header, TextEncoding& encoding)
{
auto name = file.name();
// We have to include the filename=".." part in the header, even if the filename is empty
FormDataBuilder::addFilenameToMultiPartHeader(header, encoding, name);
// Add the content type if available, or "application/octet-stream" otherwise (RFC 1867).
auto contentType = file.type();
if (contentType.isEmpty())
contentType = "application/octet-stream"_s;
ASSERT(Blob::isNormalizedContentType(contentType));
FormDataBuilder::addContentTypeToMultiPartHeader(header, contentType.ascii());
FormDataBuilder::finishMultiPartHeader(header);
appendData(header.data(), header.size());
if (!file.path().isEmpty())
appendFile(file.path());
else if (file.size())
appendBlob(file.url());
}
void FormData::appendMultiPartStringValue(const String& string, Vector<char>& header, TextEncoding& encoding)
{
FormDataBuilder::finishMultiPartHeader(header);
appendData(header.data(), header.size());
auto normalizedStringData = normalizeStringData(encoding, string);
appendData(normalizedStringData.data(), normalizedStringData.size());
}
void FormData::appendMultiPartKeyValuePairItems(const DOMFormData& formData)
{
m_boundary = FormDataBuilder::generateUniqueBoundaryString();
auto encoding = formData.encoding();
Vector<char> encodedData;
for (auto& item : formData.items()) {
auto normalizedName = normalizeStringData(encoding, item.name);
Vector<char> header;
FormDataBuilder::beginMultiPartHeader(header, m_boundary.data(), normalizedName);
if (WTF::holds_alternative<RefPtr<File>>(item.data))
appendMultiPartFileValue(*WTF::get<RefPtr<File>>(item.data), header, encoding);
else
appendMultiPartStringValue(WTF::get<String>(item.data), header, encoding);
appendData("\r\n", 2);
}
FormDataBuilder::addBoundaryToMultiPartHeader(encodedData, m_boundary.data(), true);
appendData(encodedData.data(), encodedData.size());
}
void FormData::appendNonMultiPartKeyValuePairItems(const DOMFormData& formData, EncodingType encodingType)
{
auto encoding = formData.encoding();
Vector<char> encodedData;
for (auto& item : formData.items()) {
ASSERT(WTF::holds_alternative<String>(item.data));
auto normalizedName = normalizeStringData(encoding, item.name);
auto normalizedStringData = normalizeStringData(encoding, WTF::get<String>(item.data));
FormDataBuilder::addKeyValuePairAsFormData(encodedData, normalizedName, normalizedStringData, encodingType);
}
appendData(encodedData.data(), encodedData.size());
}
Vector<char> FormData::flatten() const
{
// Concatenate all the byte arrays, but omit any files.
Vector<char> data;
for (auto& element : m_elements) {
if (auto* vector = WTF::get_if<Vector<char>>(element.data))
data.append(vector->data(), vector->size());
}
return data;
}
String FormData::flattenToString() const
{
auto bytes = flatten();
return Latin1Encoding().decode(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
static void appendBlobResolved(BlobRegistryImpl* blobRegistry, FormData& formData, const URL& url)
{
if (!blobRegistry) {
LOG_ERROR("Tried to resolve a blob without a usable registry");
return;
}
auto* blobData = blobRegistry->getBlobDataFromURL(url);
if (!blobData) {
LOG_ERROR("Could not get blob data from a registry");
return;
}
for (const auto& blobItem : blobData->items()) {
if (blobItem.type() == BlobDataItem::Type::Data) {
ASSERT(blobItem.data().data());
formData.appendData(blobItem.data().data()->data() + static_cast<int>(blobItem.offset()), static_cast<int>(blobItem.length()));
} else if (blobItem.type() == BlobDataItem::Type::File)
formData.appendFileRange(blobItem.file()->path(), blobItem.offset(), blobItem.length(), blobItem.file()->expectedModificationTime());
else
ASSERT_NOT_REACHED();
}
}
Ref<FormData> FormData::resolveBlobReferences(BlobRegistryImpl* blobRegistry)
{
// First check if any blobs needs to be resolved, or we can take the fast path.
bool hasBlob = false;
for (auto& element : m_elements) {
if (WTF::holds_alternative<FormDataElement::EncodedBlobData>(element.data)) {
hasBlob = true;
break;
}
}
if (!hasBlob)
return *this;
// Create a copy to append the result into.
auto newFormData = FormData::create();
newFormData->setAlwaysStream(alwaysStream());
newFormData->setIdentifier(identifier());
for (auto& element : m_elements) {
switchOn(element.data,
[&] (const Vector<char>& bytes) {
newFormData->appendData(bytes.data(), bytes.size());
}, [&] (const FormDataElement::EncodedFileData& fileData) {
newFormData->appendFileRange(fileData.filename, fileData.fileStart, fileData.fileLength, fileData.expectedFileModificationTime);
}, [&] (const FormDataElement::EncodedBlobData& blobData) {
appendBlobResolved(blobRegistry, newFormData.get(), blobData.url);
}
);
}
return newFormData;
}
FormDataForUpload FormData::prepareForUpload()
{
Vector<String> generatedFiles;
for (auto& element : m_elements) {
auto* fileData = WTF::get_if<FormDataElement::EncodedFileData>(element.data);
if (!fileData)
continue;
if (!FileSystem::fileIsDirectory(fileData->filename, FileSystem::ShouldFollowSymbolicLinks::Yes))
continue;
if (fileData->fileStart || fileData->fileLength != BlobDataItem::toEndOfFile)
continue;
if (!fileData->fileModificationTimeMatchesExpectation())
continue;
auto generatedFilename = FileSystem::createTemporaryZipArchive(fileData->filename);
if (!generatedFilename)
continue;
fileData->filename = generatedFilename;
generatedFiles.append(WTFMove(generatedFilename));
}
return { *this, WTFMove(generatedFiles) };
}
FormDataForUpload::FormDataForUpload(FormData& data, Vector<String>&& temporaryZipFiles)
: m_data(data)
, m_temporaryZipFiles(WTFMove(temporaryZipFiles))
{
}
FormDataForUpload::~FormDataForUpload()
{
ASSERT(isMainThread());
for (auto& file : m_temporaryZipFiles)
FileSystem::deleteFile(file);
}
uint64_t FormData::lengthInBytes() const
{
if (!m_lengthInBytes) {
uint64_t length = 0;
for (auto& element : m_elements)
length += element.lengthInBytes();
m_lengthInBytes = length;
}
return *m_lengthInBytes;
}
RefPtr<SharedBuffer> FormData::asSharedBuffer() const
{
for (auto& element : m_elements) {
if (!WTF::holds_alternative<Vector<char>>(element.data))
return nullptr;
}
return SharedBuffer::create(flatten());
}
URL FormData::asBlobURL() const
{
if (m_elements.size() != 1)
return { };
if (auto* blobData = WTF::get_if<FormDataElement::EncodedBlobData>(m_elements.first().data))
return blobData->url;
return { };
}
bool FormDataElement::EncodedFileData::fileModificationTimeMatchesExpectation() const
{
if (!expectedFileModificationTime)
return true;
auto fileModificationTime = FileSystem::getFileModificationTime(filename);
if (!fileModificationTime)
return false;
if (fileModificationTime->secondsSinceEpoch().secondsAs<time_t>() != expectedFileModificationTime->secondsSinceEpoch().secondsAs<time_t>())
return false;
return true;
}
} // namespace WebCore