blob: d0e03f56cb063e27f11340a2713d4f961b5ae653 [file] [log] [blame]
/*
* Copyright (C) 2006-2017 Apple Inc. All rights reserved.
* Copyright (C) 2010, 2011, 2012 Google Inc. All rights reserved.
*
* 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 "FormController.h"
#include "HTMLFormElement.h"
#include "HTMLInputElement.h"
#include "ScriptDisallowedScope.h"
#include <wtf/NeverDestroyed.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringConcatenateNumbers.h>
namespace WebCore {
using namespace HTMLNames;
static inline HTMLFormElement* ownerFormForState(const HTMLFormControlElementWithState& control)
{
// Assume controls with form attribute have no owners because we restore
// state during parsing and form owners of such controls might be
// indeterminate.
return control.hasAttributeWithoutSynchronization(formAttr) ? 0 : control.form();
}
// ----------------------------------------------------------------------------
// Serilized form of FormControlState:
// (',' means strings around it are separated in stateVector.)
//
// SerializedControlState ::= SkipState | RestoreState
// SkipState ::= '0'
// RestoreState ::= UnsignedNumber, ControlValue+
// UnsignedNumber ::= [0-9]+
// ControlValue ::= arbitrary string
//
// RestoreState has a sequence of ControlValues. The length of the
// sequence is represented by UnsignedNumber.
static inline void serializeFormControlStateTo(const FormControlState& formControlState, Vector<String>& stateVector)
{
stateVector.append(String::number(formControlState.size()));
for (auto& value : formControlState)
stateVector.append(value.isNull() ? emptyString() : value);
}
static inline Optional<FormControlState> deserializeFormControlState(const Vector<String>& stateVector, size_t& index)
{
if (index >= stateVector.size())
return WTF::nullopt;
size_t size = stateVector[index++].toUInt();
if (index + size > stateVector.size())
return WTF::nullopt;
Vector<String> subvector;
subvector.reserveInitialCapacity(size);
for (size_t i = 0; i < size; ++i)
subvector.uncheckedAppend(stateVector[index++]);
return subvector;
}
// ----------------------------------------------------------------------------
class FormElementKey {
public:
explicit FormElementKey(AtomStringImpl* = nullptr, AtomStringImpl* = nullptr);
~FormElementKey();
FormElementKey(const FormElementKey&);
FormElementKey& operator=(const FormElementKey&);
AtomStringImpl* name() const { return m_name; }
AtomStringImpl* type() const { return m_type; }
// Hash table deleted values, which are only constructed and never copied or destroyed.
FormElementKey(WTF::HashTableDeletedValueType) : m_name(hashTableDeletedValue()) { }
bool isHashTableDeletedValue() const { return m_name == hashTableDeletedValue(); }
private:
void ref() const;
void deref() const;
static AtomStringImpl* hashTableDeletedValue() { return reinterpret_cast<AtomStringImpl*>(-1); }
AtomStringImpl* m_name;
AtomStringImpl* m_type;
};
FormElementKey::FormElementKey(AtomStringImpl* name, AtomStringImpl* type)
: m_name(name)
, m_type(type)
{
ref();
}
FormElementKey::~FormElementKey()
{
deref();
}
FormElementKey::FormElementKey(const FormElementKey& other)
: m_name(other.name())
, m_type(other.type())
{
ref();
}
FormElementKey& FormElementKey::operator=(const FormElementKey& other)
{
other.ref();
deref();
m_name = other.name();
m_type = other.type();
return *this;
}
void FormElementKey::ref() const
{
if (name())
name()->ref();
if (type())
type()->ref();
}
void FormElementKey::deref() const
{
if (name())
name()->deref();
if (type())
type()->deref();
}
inline bool operator==(const FormElementKey& a, const FormElementKey& b)
{
return a.name() == b.name() && a.type() == b.type();
}
struct FormElementKeyHash {
static unsigned hash(const FormElementKey&);
static bool equal(const FormElementKey& a, const FormElementKey& b) { return a == b; }
static const bool safeToCompareToEmptyOrDeleted = true;
};
unsigned FormElementKeyHash::hash(const FormElementKey& key)
{
return StringHasher::hashMemory<sizeof(FormElementKey)>(&key);
}
struct FormElementKeyHashTraits : WTF::GenericHashTraits<FormElementKey> {
static void constructDeletedValue(FormElementKey& slot) { new (NotNull, &slot) FormElementKey(WTF::HashTableDeletedValue); }
static bool isDeletedValue(const FormElementKey& value) { return value.isHashTableDeletedValue(); }
};
// ----------------------------------------------------------------------------
class SavedFormState {
WTF_MAKE_NONCOPYABLE(SavedFormState);
WTF_MAKE_FAST_ALLOCATED;
public:
SavedFormState() = default;
static std::unique_ptr<SavedFormState> deserialize(const Vector<String>&, size_t& index);
void serializeTo(Vector<String>&) const;
bool isEmpty() const { return m_stateForNewFormElements.isEmpty(); }
void appendControlState(const AtomString& name, const AtomString& type, const FormControlState&);
FormControlState takeControlState(const AtomString& name, const AtomString& type);
Vector<String> referencedFilePaths() const;
private:
HashMap<FormElementKey, Deque<FormControlState>, FormElementKeyHash, FormElementKeyHashTraits> m_stateForNewFormElements;
size_t m_controlStateCount { 0 };
};
static bool isNotFormControlTypeCharacter(UChar ch)
{
return !(ch == '-' || isASCIILower(ch));
}
std::unique_ptr<SavedFormState> SavedFormState::deserialize(const Vector<String>& stateVector, size_t& index)
{
if (index >= stateVector.size())
return nullptr;
// FIXME: We need String::toSizeT().
size_t itemCount = stateVector[index++].toUInt();
if (!itemCount)
return nullptr;
auto savedFormState = makeUnique<SavedFormState>();
while (itemCount--) {
if (index + 1 >= stateVector.size())
return nullptr;
String name = stateVector[index++];
String type = stateVector[index++];
auto state = deserializeFormControlState(stateVector, index);
if (type.isEmpty() || type.find(isNotFormControlTypeCharacter) != notFound || !state)
return nullptr;
savedFormState->appendControlState(name, type, state.value());
}
return savedFormState;
}
void SavedFormState::serializeTo(Vector<String>& stateVector) const
{
stateVector.append(String::number(m_controlStateCount));
for (auto& element : m_stateForNewFormElements) {
const FormElementKey& key = element.key;
for (auto& controlState : element.value) {
stateVector.append(key.name());
stateVector.append(key.type());
serializeFormControlStateTo(controlState, stateVector);
}
}
}
void SavedFormState::appendControlState(const AtomString& name, const AtomString& type, const FormControlState& state)
{
m_stateForNewFormElements.add(FormElementKey { name.impl(), type.impl() }, Deque<FormControlState> { }).iterator->value.append(state);
++m_controlStateCount;
}
FormControlState SavedFormState::takeControlState(const AtomString& name, const AtomString& type)
{
auto iterator = m_stateForNewFormElements.find(FormElementKey { name.impl(), type.impl() });
if (iterator == m_stateForNewFormElements.end())
return { };
auto state = iterator->value.takeFirst();
--m_controlStateCount;
if (iterator->value.isEmpty())
m_stateForNewFormElements.remove(iterator);
return state;
}
Vector<String> SavedFormState::referencedFilePaths() const
{
Vector<String> toReturn;
for (auto& element : m_stateForNewFormElements) {
if (!equal(element.key.type(), "file", 4))
continue;
for (auto& state : element.value) {
for (auto& file : HTMLInputElement::filesFromFileInputFormControlState(state))
toReturn.append(file.path);
}
}
return toReturn;
}
// ----------------------------------------------------------------------------
class FormKeyGenerator {
WTF_MAKE_NONCOPYABLE(FormKeyGenerator);
WTF_MAKE_FAST_ALLOCATED;
public:
FormKeyGenerator() = default;
AtomString formKey(const HTMLFormControlElementWithState&);
void willDeleteForm(HTMLFormElement*);
private:
typedef HashMap<HTMLFormElement*, AtomString> FormToKeyMap;
typedef HashMap<String, unsigned> FormSignatureToNextIndexMap;
FormToKeyMap m_formToKeyMap;
FormSignatureToNextIndexMap m_formSignatureToNextIndexMap;
};
static inline void recordFormStructure(const HTMLFormElement& form, StringBuilder& builder)
{
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
// 2 is enough to distinguish forms in webkit.org/b/91209#c0
const size_t namedControlsToBeRecorded = 2;
auto& controls = form.unsafeAssociatedElements();
builder.appendLiteral(" [");
for (size_t i = 0, namedControls = 0; i < controls.size() && namedControls < namedControlsToBeRecorded; ++i) {
if (!controls[i]->isFormControlElementWithState())
continue;
RefPtr<HTMLFormControlElementWithState> control = static_cast<HTMLFormControlElementWithState*>(controls[i]);
if (!ownerFormForState(*control))
continue;
AtomString name = control->name();
if (name.isEmpty())
continue;
namedControls++;
builder.append(name);
builder.append(' ');
}
builder.append(']');
}
static inline String formSignature(const HTMLFormElement& form)
{
URL actionURL = form.getURLAttribute(actionAttr);
// Remove the query part because it might contain volatile parameters such
// as a session key.
actionURL.setQuery(String());
StringBuilder builder;
if (!actionURL.isEmpty())
builder.append(actionURL.string());
recordFormStructure(form, builder);
return builder.toString();
}
AtomString FormKeyGenerator::formKey(const HTMLFormControlElementWithState& control)
{
auto form = makeRefPtr(ownerFormForState(control));
if (!form) {
static NeverDestroyed<AtomString> formKeyForNoOwner("No owner", AtomString::ConstructFromLiteral);
return formKeyForNoOwner;
}
return m_formToKeyMap.ensure(form.get(), [this, &form] {
auto signature = formSignature(*form);
auto nextIndex = m_formSignatureToNextIndexMap.add(signature, 0).iterator->value++;
// FIXME: Would be nice to have makeAtomString to use to optimize the case where the string already exists.
return makeString(signature, " #", nextIndex);
}).iterator->value;
}
void FormKeyGenerator::willDeleteForm(HTMLFormElement* form)
{
ASSERT(form);
m_formToKeyMap.remove(form);
}
// ----------------------------------------------------------------------------
FormController::FormController() = default;
FormController::~FormController() = default;
unsigned FormController::formElementsCharacterCount() const
{
unsigned count = 0;
for (auto& element : m_formElementsWithState) {
if (element->isTextField())
count += element->saveFormControlState()[0].length();
}
return count;
}
static String formStateSignature()
{
// In the legacy version of serialized state, the first item was a name
// attribute value of a form control. The following string literal should
// contain some characters which are rarely used for name attribute values.
static NeverDestroyed<String> signature(MAKE_STATIC_STRING_IMPL("\n\r?% WebKit serialized form state version 8 \n\r=&"));
return signature;
}
std::unique_ptr<FormController::SavedFormStateMap> FormController::createSavedFormStateMap(const FormElementListHashSet& controlList)
{
FormKeyGenerator keyGenerator;
auto stateMap = makeUnique<SavedFormStateMap>();
for (auto& control : controlList) {
if (!control->shouldSaveAndRestoreFormControlState())
continue;
auto& formState = stateMap->add(keyGenerator.formKey(*control).impl(), nullptr).iterator->value;
if (!formState)
formState = makeUnique<SavedFormState>();
formState->appendControlState(control->name(), control->type(), control->saveFormControlState());
}
return stateMap;
}
Vector<String> FormController::formElementsState() const
{
std::unique_ptr<SavedFormStateMap> stateMap = createSavedFormStateMap(m_formElementsWithState);
Vector<String> stateVector;
stateVector.reserveInitialCapacity(m_formElementsWithState.size() * 4);
stateVector.append(formStateSignature());
for (auto& state : *stateMap) {
stateVector.append(state.key.get());
state.value->serializeTo(stateVector);
}
bool hasOnlySignature = stateVector.size() == 1;
if (hasOnlySignature)
stateVector.clear();
return stateVector;
}
void FormController::setStateForNewFormElements(const Vector<String>& stateVector)
{
formStatesFromStateVector(stateVector, m_savedFormStateMap);
}
FormControlState FormController::takeStateForFormElement(const HTMLFormControlElementWithState& control)
{
if (m_savedFormStateMap.isEmpty())
return FormControlState();
if (!m_formKeyGenerator)
m_formKeyGenerator = makeUnique<FormKeyGenerator>();
SavedFormStateMap::iterator it = m_savedFormStateMap.find(m_formKeyGenerator->formKey(control).impl());
if (it == m_savedFormStateMap.end())
return FormControlState();
FormControlState state = it->value->takeControlState(control.name(), control.type());
if (it->value->isEmpty())
m_savedFormStateMap.remove(it);
return state;
}
void FormController::formStatesFromStateVector(const Vector<String>& stateVector, SavedFormStateMap& map)
{
map.clear();
size_t i = 0;
if (stateVector.size() < 1 || stateVector[i++] != formStateSignature())
return;
while (i + 1 < stateVector.size()) {
AtomString formKey = stateVector[i++];
auto state = SavedFormState::deserialize(stateVector, i);
if (!state) {
i = 0;
break;
}
map.add(formKey.impl(), WTFMove(state));
}
if (i != stateVector.size())
map.clear();
}
void FormController::willDeleteForm(HTMLFormElement& form)
{
if (m_formKeyGenerator)
m_formKeyGenerator->willDeleteForm(&form);
}
void FormController::restoreControlStateFor(HTMLFormControlElementWithState& control)
{
// We don't save state of a control with shouldSaveAndRestoreFormControlState()
// == false. But we need to skip restoring process too because a control in
// another form might have the same pair of name and type and saved its state.
if (!control.shouldSaveAndRestoreFormControlState())
return;
if (ownerFormForState(control))
return;
auto state = takeStateForFormElement(control);
if (!state.isEmpty())
control.restoreFormControlState(state);
}
void FormController::restoreControlStateIn(HTMLFormElement& form)
{
for (auto& element : form.copyAssociatedElementsVector()) {
if (!is<HTMLFormControlElementWithState>(element.get()))
continue;
auto& control = downcast<HTMLFormControlElementWithState>(element.get());
if (!control.shouldSaveAndRestoreFormControlState())
continue;
if (ownerFormForState(control) != &form)
continue;
auto state = takeStateForFormElement(control);
if (!state.isEmpty())
control.restoreFormControlState(state);
}
}
bool FormController::hasFormStateToRestore() const
{
return !m_savedFormStateMap.isEmpty();
}
Vector<String> FormController::referencedFilePaths(const Vector<String>& stateVector)
{
Vector<String> paths;
SavedFormStateMap map;
formStatesFromStateVector(stateVector, map);
for (auto& state : map.values())
paths.appendVector(state->referencedFilePaths());
return paths;
}
void FormController::registerFormElementWithState(HTMLFormControlElementWithState& control)
{
ASSERT(!m_formElementsWithState.contains(&control));
m_formElementsWithState.add(&control);
}
void FormController::unregisterFormElementWithState(HTMLFormControlElementWithState& control)
{
ASSERT(m_formElementsWithState.contains(&control));
m_formElementsWithState.remove(&control);
}
} // namespace WebCore