| /* |
| * 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) { |
| auto* formAssociatedElement = controls[i]->asFormAssociatedElement(); |
| if (!formAssociatedElement->isFormControlElementWithState()) |
| continue; |
| RefPtr<HTMLFormControlElementWithState> control = static_cast<HTMLFormControlElementWithState*>(formAssociatedElement); |
| 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 MainThreadNeverDestroyed<const 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 |