/* * Copyright (C) 2009 Google 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER OR 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 "public/web/WebSearchableFormData.h" #include "core/HTMLNames.h" #include "core/InputTypeNames.h" #include "core/dom/Document.h" #include "core/html/FormData.h" #include "core/html/HTMLFormControlElement.h" #include "core/html/HTMLFormElement.h" #include "core/html/HTMLInputElement.h" #include "core/html/HTMLOptionElement.h" #include "core/html/HTMLSelectElement.h" #include "platform/network/FormDataEncoder.h" #include "public/web/WebFormElement.h" #include "public/web/WebInputElement.h" #include "wtf/text/TextEncoding.h" namespace blink { using namespace HTMLNames; namespace { // Gets the encoding for the form. // TODO(tkent): Use FormDataEncoder::encodingFromAcceptCharset(). void getFormEncoding(const HTMLFormElement& form, WTF::TextEncoding* encoding) { String str(form.fastGetAttribute(HTMLNames::accept_charsetAttr)); str.replace(',', ' '); Vector charsets; str.split(' ', charsets); for (const String& charset : charsets) { *encoding = WTF::TextEncoding(charset); if (encoding->isValid()) return; } if (form.document().loader()) *encoding = WTF::TextEncoding(form.document().encoding()); } // Returns true if the submit request results in an HTTP URL. bool isHTTPFormSubmit(const HTMLFormElement& form) { // FIXME: This function is insane. This is an overly complicated way to get // this information. String action(form.action()); // The isNull() check is trying to avoid completeURL returning KURL() when // passed a null string. return form.document().completeURL(action.isNull() ? "" : action).protocolIs("http"); } // If the form does not have an activated submit button, the first submit // button is returned. HTMLFormControlElement* buttonToActivate(const HTMLFormElement& form) { HTMLFormControlElement* firstSubmitButton = nullptr; for (auto& element : form.associatedElements()) { if (!element->isFormControlElement()) continue; HTMLFormControlElement* control = toHTMLFormControlElement(element); if (control->isActivatedSubmit()) { // There's a button that is already activated for submit, return // nullptr. return nullptr; } if (!firstSubmitButton && control->isSuccessfulSubmitButton()) firstSubmitButton = control; } return firstSubmitButton; } // Returns true if the selected state of all the options matches the default // selected state. bool isSelectInDefaultState(const HTMLSelectElement& select) { const WillBeHeapVector>& listItems = select.listItems(); if (select.multiple() || select.size() > 1) { for (const auto& item : listItems) { if (!isHTMLOptionElement(*item)) continue; HTMLOptionElement* optionElement = toHTMLOptionElement(item); if (optionElement->selected() != optionElement->fastHasAttribute(selectedAttr)) return false; } return true; } // The select is rendered as a combobox (called menulist in WebKit). At // least one item is selected, determine which one. HTMLOptionElement* initialSelected = nullptr; for (const auto& item : listItems) { if (!isHTMLOptionElement(*item)) continue; HTMLOptionElement* optionElement = toHTMLOptionElement(item); if (optionElement->fastHasAttribute(selectedAttr)) { // The page specified the option to select. initialSelected = optionElement; break; } if (!initialSelected) initialSelected = optionElement; } return !initialSelected || initialSelected->selected(); } // Returns true if the form element is in its default state, false otherwise. // The default state is the state of the form element on initial load of the // page, and varies depending upon the form element. For example, a checkbox is // in its default state if the checked state matches the state of the checked // attribute. bool isInDefaultState(const HTMLFormControlElement& formElement) { if (isHTMLInputElement(formElement)) { const HTMLInputElement& inputElement = toHTMLInputElement(formElement); if (inputElement.type() == InputTypeNames::checkbox || inputElement.type() == InputTypeNames::radio) return inputElement.checked() == inputElement.fastHasAttribute(checkedAttr); } else if (isHTMLSelectElement(formElement)) { return isSelectInDefaultState(toHTMLSelectElement(formElement)); } return true; } // Look for a suitable search text field in a given HTMLFormElement // Return nothing if one of those items are found: // - A text area field // - A file upload field // - A Password field // - More than one text field HTMLInputElement* findSuitableSearchInputElement(const HTMLFormElement& form) { HTMLInputElement* textElement = nullptr; for (const auto& item : form.associatedElements()) { if (!item->isFormControlElement()) continue; HTMLFormControlElement& control = toHTMLFormControlElement(*item); if (control.isDisabledFormControl() || control.name().isNull()) continue; if (!isInDefaultState(control) || isHTMLTextAreaElement(control)) return nullptr; if (isHTMLInputElement(control) && control.willValidate()) { const HTMLInputElement& input = toHTMLInputElement(control); // Return nothing if a file upload field or a password field are // found. if (input.type() == InputTypeNames::file || input.type() == InputTypeNames::password) return nullptr; if (input.isTextField()) { if (textElement) { // The auto-complete bar only knows how to fill in one // value. This form has multiple fields; don't treat it as // searchable. return nullptr; } textElement = toHTMLInputElement(&control); } } } return textElement; } // Build a search string based on a given HTMLFormElement and HTMLInputElement // // Search string output example from www.google.com: // "hl=en&source=hp&biw=1085&bih=854&q={searchTerms}&btnG=Google+Search&aq=f&aqi=&aql=&oq=" // // Return false if the provided HTMLInputElement is not found in the form bool buildSearchString(const HTMLFormElement& form, Vector* encodedString, const WTF::TextEncoding& encoding, const HTMLInputElement* textElement) { bool isElementFound = false; for (const auto& item : form.associatedElements()) { if (!item->isFormControlElement()) continue; HTMLFormControlElement& control = toHTMLFormControlElement(*item); if (control.isDisabledFormControl() || control.name().isNull()) continue; FormData* formData = FormData::create(encoding); control.appendToFormData(*formData); for (const auto& entry : formData->entries()) { if (!encodedString->isEmpty()) encodedString->append('&'); FormDataEncoder::encodeStringAsFormData(*encodedString, entry->name()); encodedString->append('='); if (&control == textElement) { encodedString->append("{searchTerms}", 13); isElementFound = true; } else { FormDataEncoder::encodeStringAsFormData(*encodedString, entry->value()); } } } return isElementFound; } } // namespace WebSearchableFormData::WebSearchableFormData(const WebFormElement& form, const WebInputElement& selectedInputElement) { RefPtrWillBeRawPtr formElement = static_cast>(form); HTMLInputElement* inputElement = static_cast>(selectedInputElement).get(); // Only consider forms that GET data. // Allow HTTPS only when an input element is provided. if (equalIgnoringCase(formElement->getAttribute(methodAttr), "post") || (!isHTTPFormSubmit(*formElement) && !inputElement)) return; WTF::TextEncoding encoding; getFormEncoding(*formElement, &encoding); if (!encoding.isValid()) { // Need a valid encoding to encode the form elements. // If the encoding isn't found webkit ends up replacing the params with // empty strings. So, we don't try to do anything here. return; } // Look for a suitable search text field in the form when a // selectedInputElement is not provided. if (!inputElement) { inputElement = findSuitableSearchInputElement(*formElement); // Return if no suitable text element has been found. if (!inputElement) return; } HTMLFormControlElement* firstSubmitButton = buttonToActivate(*formElement); if (firstSubmitButton) { // The form does not have an active submit button, make the first button // active. We need to do this, otherwise the URL will not contain the // name of the submit button. firstSubmitButton->setActivatedSubmit(true); } Vector encodedString; bool isValidSearchString = buildSearchString(*formElement, &encodedString, encoding, inputElement); if (firstSubmitButton) firstSubmitButton->setActivatedSubmit(false); // Return if the search string is not valid. if (!isValidSearchString) return; String action(formElement->action()); KURL url(formElement->document().completeURL(action.isNull() ? "" : action)); RefPtr formData = EncodedFormData::create(encodedString); url.setQuery(formData->flattenToString()); m_url = url; m_encoding = String(encoding.name()); } } // namespace blink