/* * 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 "third_party/blink/public/web/web_searchable_form_data.h" #include "third_party/blink/public/web/web_form_element.h" #include "third_party/blink/public/web/web_input_element.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/html/forms/form_data.h" #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" #include "third_party/blink/renderer/core/html/forms/html_form_element.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/html_option_element.h" #include "third_party/blink/renderer/core/html/forms/html_select_element.h" #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/input_type_names.h" #include "third_party/blink/renderer/platform/network/form_data_encoder.h" #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" namespace blink { using namespace html_names; namespace { // Gets the encoding for the form. // TODO(tkent): Use FormDataEncoder::encodingFromAcceptCharset(). void GetFormEncoding(const HTMLFormElement& form, WTF::TextEncoding* encoding) { String str(form.FastGetAttribute(html_names::kAcceptCharsetAttr)); str.Replace(',', ' '); Vector charsets; str.Split(' ', charsets); for (const String& charset : charsets) { *encoding = WTF::TextEncoding(charset); if (encoding->IsValid()) return; } if (form.GetDocument().Loader()) *encoding = WTF::TextEncoding(form.GetDocument().Encoding()); } // If the form does not have an activated submit button, the first submit // button is returned. HTMLFormControlElement* ButtonToActivate(const HTMLFormElement& form) { HTMLFormControlElement* first_submit_button = nullptr; for (auto& element : form.ListedElements()) { 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 (!first_submit_button && control->IsSuccessfulSubmitButton()) first_submit_button = control; } return first_submit_button; } // Returns true if the selected state of all the options matches the default // selected state. bool IsSelectInDefaultState(const HTMLSelectElement& select) { if (select.IsMultiple() || select.size() > 1) { for (auto* const option_element : select.GetOptionList()) { if (option_element->Selected() != option_element->FastHasAttribute(kSelectedAttr)) 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* initial_selected = nullptr; for (auto* const option_element : select.GetOptionList()) { if (option_element->FastHasAttribute(kSelectedAttr)) { // The page specified the option to select. initial_selected = option_element; break; } if (!initial_selected) initial_selected = option_element; } return !initial_selected || initial_selected->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& form_element) { if (auto* input = ToHTMLInputElementOrNull(form_element)) { if (input->type() == input_type_names::kCheckbox || input->type() == input_type_names::kRadio) return input->checked() == input->FastHasAttribute(kCheckedAttr); } else if (auto* select = ToHTMLSelectElementOrNull(form_element)) { return IsSelectInDefaultState(*select); } 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* text_element = nullptr; for (const auto& item : form.ListedElements()) { if (!item->IsFormControlElement()) continue; HTMLFormControlElement& control = ToHTMLFormControlElement(*item); if (control.IsDisabledFormControl() || control.GetName().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() == input_type_names::kFile || input.type() == input_type_names::kPassword) return nullptr; if (input.IsTextField()) { if (text_element) { // 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; } text_element = ToHTMLInputElement(&control); } } } return text_element; } // 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* encoded_string, const WTF::TextEncoding& encoding, const HTMLInputElement* text_element) { bool is_element_found = false; for (const auto& item : form.ListedElements()) { if (!item->IsFormControlElement()) continue; HTMLFormControlElement& control = ToHTMLFormControlElement(*item); if (control.IsDisabledFormControl() || control.GetName().IsNull()) continue; FormData* form_data = FormData::Create(encoding); control.AppendToFormData(*form_data); for (const auto& entry : form_data->Entries()) { if (!encoded_string->IsEmpty()) encoded_string->push_back('&'); FormDataEncoder::EncodeStringAsFormData(*encoded_string, form_data->Encode(entry->name()), FormDataEncoder::kNormalizeCRLF); encoded_string->push_back('='); if (&control == text_element) { encoded_string->Append("{searchTerms}", 13); is_element_found = true; } else { FormDataEncoder::EncodeStringAsFormData( *encoded_string, form_data->Encode(entry->Value()), FormDataEncoder::kNormalizeCRLF); } } } return is_element_found; } } // namespace WebSearchableFormData::WebSearchableFormData( const WebFormElement& form, const WebInputElement& selected_input_element) { HTMLFormElement* form_element = static_cast(form); HTMLInputElement* input_element = static_cast(selected_input_element); // Only consider forms that GET data. if (EqualIgnoringASCIICase(form_element->getAttribute(kMethodAttr), "post")) return; WTF::TextEncoding encoding; GetFormEncoding(*form_element, &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 (!input_element) { input_element = FindSuitableSearchInputElement(*form_element); // Return if no suitable text element has been found. if (!input_element) return; } HTMLFormControlElement* first_submit_button = ButtonToActivate(*form_element); if (first_submit_button) { // 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. first_submit_button->SetActivatedSubmit(true); } Vector encoded_string; bool is_valid_search_string = BuildSearchString( *form_element, &encoded_string, encoding, input_element); if (first_submit_button) first_submit_button->SetActivatedSubmit(false); // Return if the search string is not valid. if (!is_valid_search_string) return; KURL url(form_element->action()); scoped_refptr form_data = EncodedFormData::Create(encoded_string); url.SetQuery(form_data->FlattenToString()); url_ = url; encoding_ = String(encoding.GetName()); } } // namespace blink