blob: 174b8b72699c0efa3bc776d3328b50666b6ca339 [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 "Document.h"
#include "File.h"
#include "FileSystem.h"
#include "FormDataBuilder.h"
#include "Page.h"
#include "TextEncoding.h"
#include "ThreadableBlobRegistry.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)
{
// We shouldn't be copying FormData that hasn't already removed its generated files
// but just in case, make sure the new FormData is ready to generate its own files.
for (auto& element : m_elements) {
if (element.m_type == FormDataElement::Type::EncodedFile) {
element.m_generatedFilename = String();
element.m_ownsGeneratedFile = false;
}
}
}
FormData::~FormData()
{
// This cleanup should've happened when the form submission finished.
// Just in case, let's assert, and do the cleanup anyway in release builds.
ASSERT(!hasOwnedGeneratedFiles());
removeGeneratedFilesIfNeeded();
}
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(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, Document* document)
{
auto result = create();
result->appendMultiPartKeyValuePairItems(formData, document);
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;
}
uint64_t FormDataElement::lengthInBytes() const
{
switch (m_type) {
case Type::Data:
return m_data.size();
case Type::EncodedFile: {
if (m_fileLength != BlobDataItem::toEndOfFile)
return m_fileLength;
long long fileSize;
if (FileSystem::getFileSize(m_shouldGenerateFile ? m_generatedFilename : m_filename, fileSize))
return fileSize;
return 0;
}
case Type::EncodedBlob:
return ThreadableBlobRegistry::blobSize(m_url);
}
ASSERT_NOT_REACHED();
return 0;
}
FormDataElement FormDataElement::isolatedCopy() const
{
switch (m_type) {
case Type::Data:
return FormDataElement(m_data);
case Type::EncodedFile:
return FormDataElement(m_filename.isolatedCopy(), m_fileStart, m_fileLength, m_expectedFileModificationTime, m_shouldGenerateFile);
case Type::EncodedBlob:
return FormDataElement(m_url.isolatedCopy());
}
RELEASE_ASSERT_NOT_REACHED();
}
void FormData::appendData(const void* data, size_t size)
{
memcpy(expandDataStore(size), data, size);
}
void FormData::appendFile(const String& filename, bool shouldGenerateFile)
{
m_elements.append(FormDataElement(filename, 0, BlobDataItem::toEndOfFile, FileSystem::invalidFileTime(), shouldGenerateFile));
m_lengthInBytes = std::nullopt;
}
void FormData::appendFileRange(const String& filename, long long start, long long length, double expectedModificationTime, bool shouldGenerateFile)
{
m_elements.append(FormDataElement(filename, start, length, expectedModificationTime, shouldGenerateFile));
m_lengthInBytes = std::nullopt;
}
void FormData::appendBlob(const URL& blobURL)
{
m_elements.append(FormDataElement(blobURL));
m_lengthInBytes = std::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, Document* document)
{
auto name = file.name();
// Let the application specify a filename if it's going to generate a replacement file for the upload.
bool shouldGenerateFile = false;
auto& path = file.path();
if (!path.isEmpty()) {
if (Page* page = document->page()) {
String generatedFileName;
shouldGenerateFile = page->chrome().client().shouldReplaceWithGeneratedFileForUpload(path, generatedFileName);
if (shouldGenerateFile)
name = generatedFileName;
}
}
// 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 = ASCIILiteral("application/octet-stream");
ASSERT(Blob::isNormalizedContentType(contentType));
FormDataBuilder::addContentTypeToMultiPartHeader(header, contentType.ascii());
FormDataBuilder::finishMultiPartHeader(header);
appendData(header.data(), header.size());
if (!file.path().isEmpty())
appendFile(file.path(), shouldGenerateFile);
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, Document* document)
{
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, document);
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());
}
char* FormData::expandDataStore(size_t size)
{
m_lengthInBytes = std::nullopt;
if (m_elements.isEmpty() || m_elements.last().m_type != FormDataElement::Type::Data)
m_elements.append({ });
auto& lastElement = m_elements.last();
size_t oldSize = lastElement.m_data.size();
auto newSize = Checked<size_t>(oldSize) + size;
lastElement.m_data.grow(newSize.unsafeGet());
return lastElement.m_data.data() + oldSize;
}
Vector<char> FormData::flatten() const
{
// Concatenate all the byte arrays, but omit any files.
Vector<char> data;
for (auto& element : m_elements) {
if (element.m_type == FormDataElement::Type::Data)
data.append(element.m_data.data(), static_cast<size_t>(element.m_data.size()));
}
return data;
}
String FormData::flattenToString() const
{
auto bytes = flatten();
return Latin1Encoding().decode(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
static void appendBlobResolved(FormData* formData, const URL& url)
{
if (!blobRegistry().isBlobRegistryImpl()) {
LOG_ERROR("Tried to resolve a blob without a usable registry");
return;
}
BlobData* blobData = static_cast<BlobRegistryImpl&>(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()
{
// 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 (element.m_type == FormDataElement::Type::EncodedBlob) {
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) {
if (element.m_type == FormDataElement::Type::Data)
newFormData->appendData(element.m_data.data(), element.m_data.size());
else if (element.m_type == FormDataElement::Type::EncodedFile)
newFormData->appendFileRange(element.m_filename, element.m_fileStart, element.m_fileLength, element.m_expectedFileModificationTime, element.m_shouldGenerateFile);
else if (element.m_type == FormDataElement::Type::EncodedBlob)
appendBlobResolved(newFormData.ptr(), element.m_url);
else
ASSERT_NOT_REACHED();
}
return newFormData;
}
void FormData::generateFiles(Document* document)
{
Page* page = document->page();
if (!page)
return;
for (auto& element : m_elements) {
if (element.m_type == FormDataElement::Type::EncodedFile && element.m_shouldGenerateFile) {
ASSERT(!element.m_ownsGeneratedFile);
ASSERT(element.m_generatedFilename.isEmpty());
if (!element.m_generatedFilename.isEmpty())
continue;
element.m_generatedFilename = page->chrome().client().generateReplacementFile(element.m_filename);
if (!element.m_generatedFilename.isEmpty())
element.m_ownsGeneratedFile = true;
}
}
}
bool FormData::hasGeneratedFiles() const
{
for (auto& element : m_elements) {
if (element.m_type == FormDataElement::Type::EncodedFile && !element.m_generatedFilename.isEmpty())
return true;
}
return false;
}
bool FormData::hasOwnedGeneratedFiles() const
{
for (auto& element : m_elements) {
if (element.m_type == FormDataElement::Type::EncodedFile && element.m_ownsGeneratedFile) {
ASSERT(!element.m_generatedFilename.isEmpty());
return true;
}
}
return false;
}
void FormData::removeGeneratedFilesIfNeeded()
{
for (auto& element : m_elements) {
if (element.m_type == FormDataElement::Type::EncodedFile && element.m_ownsGeneratedFile) {
ASSERT(!element.m_generatedFilename.isEmpty());
ASSERT(element.m_shouldGenerateFile);
String directory = FileSystem::directoryName(element.m_generatedFilename);
FileSystem::deleteFile(element.m_generatedFilename);
FileSystem::deleteEmptyDirectory(directory);
element.m_generatedFilename = String();
element.m_ownsGeneratedFile = false;
}
}
}
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 (element.m_type != FormDataElement::Type::Data)
return nullptr;
}
return SharedBuffer::create(flatten());
}
URL FormData::asBlobURL() const
{
if (m_elements.size() != 1)
return { };
ASSERT(m_elements.first().m_type == FormDataElement::Type::EncodedBlob || m_elements.first().m_url.isNull());
return m_elements.first().m_url;
}
} // namespace WebCore