Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2009 Google Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions are |
| 6 | * met: |
| 7 | * |
| 8 | * * Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * * Redistributions in binary form must reproduce the above |
| 11 | * copyright notice, this list of conditions and the following disclaimer |
| 12 | * in the documentation and/or other materials provided with the |
| 13 | * distribution. |
| 14 | * * Neither the name of Google Inc. nor the names of its |
| 15 | * contributors may be used to endorse or promote products derived from |
| 16 | * this software without specific prior written permission. |
| 17 | * |
| 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 | */ |
| 30 | |
| 31 | #include "config.h" |
| 32 | #include "WebSearchableFormData.h" |
| 33 | |
| 34 | #include "HTMLNames.h" |
| 35 | #include "WebFormElement.h" |
| 36 | #include "WebInputElement.h" |
| 37 | #include "core/dom/Document.h" |
Torne (Richard Coles) | bfe3590 | 2013-10-22 16:41:51 +0100 | [diff] [blame] | 38 | #include "core/html/FormDataList.h" |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 39 | #include "core/html/HTMLFormControlElement.h" |
| 40 | #include "core/html/HTMLFormElement.h" |
| 41 | #include "core/html/HTMLInputElement.h" |
| 42 | #include "core/html/HTMLOptionElement.h" |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 43 | #include "core/html/HTMLSelectElement.h" |
Torne (Richard Coles) | bfe3590 | 2013-10-22 16:41:51 +0100 | [diff] [blame] | 44 | #include "platform/network/FormDataBuilder.h" |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 45 | #include "wtf/text/TextEncoding.h" |
| 46 | |
| 47 | using namespace WebCore; |
| 48 | using namespace HTMLNames; |
| 49 | |
| 50 | namespace { |
| 51 | |
| 52 | // Gets the encoding for the form. |
| 53 | void GetFormEncoding(const HTMLFormElement* form, WTF::TextEncoding* encoding) |
| 54 | { |
| 55 | String str(form->getAttribute(HTMLNames::accept_charsetAttr)); |
| 56 | str.replace(',', ' '); |
| 57 | Vector<String> charsets; |
| 58 | str.split(' ', charsets); |
| 59 | for (Vector<String>::const_iterator i(charsets.begin()); i != charsets.end(); ++i) { |
| 60 | *encoding = WTF::TextEncoding(*i); |
| 61 | if (encoding->isValid()) |
| 62 | return; |
| 63 | } |
Torne (Richard Coles) | 8abfc58 | 2013-09-12 12:10:38 +0100 | [diff] [blame] | 64 | if (!form->document().loader()) |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 65 | return; |
Torne (Richard Coles) | 8abfc58 | 2013-09-12 12:10:38 +0100 | [diff] [blame] | 66 | *encoding = WTF::TextEncoding(form->document().encoding()); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 67 | } |
| 68 | |
| 69 | // Returns true if the submit request results in an HTTP URL. |
| 70 | bool IsHTTPFormSubmit(const HTMLFormElement* form) |
| 71 | { |
| 72 | // FIXME: This function is insane. This is an overly complicated way to get this information. |
| 73 | String action(form->action()); |
| 74 | // The isNull() check is trying to avoid completeURL returning KURL() when passed a null string. |
Torne (Richard Coles) | 8abfc58 | 2013-09-12 12:10:38 +0100 | [diff] [blame] | 75 | return form->document().completeURL(action.isNull() ? "" : action).protocolIs("http"); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 76 | } |
| 77 | |
| 78 | // If the form does not have an activated submit button, the first submit |
| 79 | // button is returned. |
| 80 | HTMLFormControlElement* GetButtonToActivate(HTMLFormElement* form) |
| 81 | { |
| 82 | HTMLFormControlElement* firstSubmitButton = 0; |
Torne (Richard Coles) | a854de0 | 2013-12-18 16:25:25 +0000 | [diff] [blame] | 83 | const Vector<FormAssociatedElement*>& element = form->associatedElements(); |
| 84 | for (Vector<FormAssociatedElement*>::const_iterator i(element.begin()); i != element.end(); ++i) { |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 85 | if (!(*i)->isFormControlElement()) |
| 86 | continue; |
| 87 | HTMLFormControlElement* control = toHTMLFormControlElement(*i); |
| 88 | if (control->isActivatedSubmit()) { |
| 89 | // There's a button that is already activated for submit, return 0. |
| 90 | return 0; |
| 91 | } |
| 92 | if (!firstSubmitButton && control->isSuccessfulSubmitButton()) |
| 93 | firstSubmitButton = control; |
| 94 | } |
| 95 | return firstSubmitButton; |
| 96 | } |
| 97 | |
| 98 | // Returns true if the selected state of all the options matches the default |
| 99 | // selected state. |
| 100 | bool IsSelectInDefaultState(HTMLSelectElement* select) |
| 101 | { |
| 102 | const Vector<HTMLElement*>& listItems = select->listItems(); |
| 103 | if (select->multiple() || select->size() > 1) { |
| 104 | for (Vector<HTMLElement*>::const_iterator i(listItems.begin()); i != listItems.end(); ++i) { |
| 105 | if (!(*i)->hasLocalName(HTMLNames::optionTag)) |
| 106 | continue; |
| 107 | HTMLOptionElement* optionElement = toHTMLOptionElement(*i); |
| 108 | if (optionElement->selected() != optionElement->hasAttribute(selectedAttr)) |
| 109 | return false; |
| 110 | } |
| 111 | return true; |
| 112 | } |
| 113 | |
| 114 | // The select is rendered as a combobox (called menulist in WebKit). At |
| 115 | // least one item is selected, determine which one. |
| 116 | HTMLOptionElement* initialSelected = 0; |
| 117 | for (Vector<HTMLElement*>::const_iterator i(listItems.begin()); i != listItems.end(); ++i) { |
| 118 | if (!(*i)->hasLocalName(HTMLNames::optionTag)) |
| 119 | continue; |
| 120 | HTMLOptionElement* optionElement = toHTMLOptionElement(*i); |
| 121 | if (optionElement->hasAttribute(selectedAttr)) { |
| 122 | // The page specified the option to select. |
| 123 | initialSelected = optionElement; |
| 124 | break; |
| 125 | } |
| 126 | if (!initialSelected) |
| 127 | initialSelected = optionElement; |
| 128 | } |
| 129 | return !initialSelected || initialSelected->selected(); |
| 130 | } |
| 131 | |
| 132 | // Returns true if the form element is in its default state, false otherwise. |
| 133 | // The default state is the state of the form element on initial load of the |
| 134 | // page, and varies depending upon the form element. For example, a checkbox is |
| 135 | // in its default state if the checked state matches the state of the checked attribute. |
| 136 | bool IsInDefaultState(HTMLFormControlElement* formElement) |
| 137 | { |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 138 | ASSERT(formElement); |
| 139 | if (isHTMLInputElement(*formElement)) { |
| 140 | const HTMLInputElement& inputElement = toHTMLInputElement(*formElement); |
| 141 | if (inputElement.isCheckbox() || inputElement.isRadioButton()) |
| 142 | return inputElement.checked() == inputElement.hasAttribute(checkedAttr); |
| 143 | } else if (isHTMLSelectElement(*formElement)) { |
Ben Murdoch | 0019e4e | 2013-07-18 11:57:54 +0100 | [diff] [blame] | 144 | return IsSelectInDefaultState(toHTMLSelectElement(formElement)); |
| 145 | } |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 146 | return true; |
| 147 | } |
| 148 | |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 149 | // Look for a suitable search text field in a given HTMLFormElement |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 150 | // Return nothing if one of those items are found: |
| 151 | // - A text area field |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 152 | // - A file upload field |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 153 | // - A Password field |
| 154 | // - More than one text field |
| 155 | HTMLInputElement* findSuitableSearchInputElement(const HTMLFormElement* form) |
| 156 | { |
| 157 | HTMLInputElement* textElement = 0; |
Torne (Richard Coles) | a854de0 | 2013-12-18 16:25:25 +0000 | [diff] [blame] | 158 | const Vector<FormAssociatedElement*>& element = form->associatedElements(); |
| 159 | for (Vector<FormAssociatedElement*>::const_iterator i(element.begin()); i != element.end(); ++i) { |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 160 | if (!(*i)->isFormControlElement()) |
| 161 | continue; |
| 162 | |
| 163 | HTMLFormControlElement* control = toHTMLFormControlElement(*i); |
| 164 | |
| 165 | if (control->isDisabledFormControl() || control->name().isNull()) |
| 166 | continue; |
| 167 | |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 168 | if (!IsInDefaultState(control) || isHTMLTextAreaElement(*control)) |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 169 | return 0; |
| 170 | |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 171 | if (isHTMLInputElement(*control) && control->willValidate()) { |
| 172 | const HTMLInputElement& input = toHTMLInputElement(*control); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 173 | |
| 174 | // Return nothing if a file upload field or a password field are found. |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 175 | if (input.isFileUpload() || input.isPasswordField()) |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 176 | return 0; |
| 177 | |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 178 | if (input.isTextField()) { |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 179 | if (textElement) { |
| 180 | // The auto-complete bar only knows how to fill in one value. |
| 181 | // This form has multiple fields; don't treat it as searchable. |
| 182 | return 0; |
| 183 | } |
| 184 | textElement = toHTMLInputElement(control); |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | return textElement; |
| 189 | } |
| 190 | |
| 191 | // Build a search string based on a given HTMLFormElement and HTMLInputElement |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 192 | // |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 193 | // Search string output example from www.google.com: |
| 194 | // "hl=en&source=hp&biw=1085&bih=854&q={searchTerms}&btnG=Google+Search&aq=f&aqi=&aql=&oq=" |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 195 | // |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 196 | // Return false if the provided HTMLInputElement is not found in the form |
| 197 | bool buildSearchString(const HTMLFormElement* form, Vector<char>* encodedString, WTF::TextEncoding* encoding, const HTMLInputElement* textElement) |
| 198 | { |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 199 | bool isElementFound = false; |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 200 | |
Torne (Richard Coles) | 51b2906 | 2013-11-28 11:56:03 +0000 | [diff] [blame] | 201 | Vector<FormAssociatedElement*> elements = form->associatedElements(); |
| 202 | for (Vector<FormAssociatedElement*>::const_iterator i(elements.begin()); i != elements.end(); ++i) { |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 203 | if (!(*i)->isFormControlElement()) |
| 204 | continue; |
| 205 | |
| 206 | HTMLFormControlElement* control = toHTMLFormControlElement(*i); |
| 207 | |
| 208 | if (control->isDisabledFormControl() || control->name().isNull()) |
| 209 | continue; |
| 210 | |
| 211 | FormDataList dataList(*encoding); |
| 212 | if (!control->appendFormData(dataList, false)) |
| 213 | continue; |
| 214 | |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 215 | const WillBeHeapVector<FormDataList::Item>& items = dataList.items(); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 216 | |
Torne (Richard Coles) | d5428f3 | 2014-03-18 10:21:16 +0000 | [diff] [blame] | 217 | for (WillBeHeapVector<FormDataList::Item>::const_iterator j(items.begin()); j != items.end(); ++j) { |
| 218 | if (!encodedString->isEmpty()) |
| 219 | encodedString->append('&'); |
| 220 | FormDataBuilder::encodeStringAsFormData(*encodedString, j->data()); |
| 221 | encodedString->append('='); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 222 | ++j; |
| 223 | if (control == textElement) { |
| 224 | encodedString->append("{searchTerms}", 13); |
| 225 | isElementFound = true; |
| 226 | } else |
| 227 | FormDataBuilder::encodeStringAsFormData(*encodedString, j->data()); |
| 228 | } |
| 229 | } |
| 230 | return isElementFound; |
| 231 | } |
| 232 | } // namespace |
| 233 | |
Torne (Richard Coles) | 51b2906 | 2013-11-28 11:56:03 +0000 | [diff] [blame] | 234 | namespace blink { |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 235 | |
| 236 | WebSearchableFormData::WebSearchableFormData(const WebFormElement& form, const WebInputElement& selectedInputElement) |
| 237 | { |
| 238 | RefPtr<HTMLFormElement> formElement = form.operator PassRefPtr<HTMLFormElement>(); |
| 239 | HTMLInputElement* inputElement = selectedInputElement.operator PassRefPtr<HTMLInputElement>().get(); |
| 240 | |
| 241 | // Only consider forms that GET data. |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 242 | // Allow HTTPS only when an input element is provided. |
| 243 | if (equalIgnoringCase(formElement->getAttribute(methodAttr), "post") |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 244 | || (!IsHTTPFormSubmit(formElement.get()) && !inputElement)) |
| 245 | return; |
| 246 | |
| 247 | Vector<char> encodedString; |
| 248 | WTF::TextEncoding encoding; |
| 249 | |
| 250 | GetFormEncoding(formElement.get(), &encoding); |
| 251 | if (!encoding.isValid()) { |
| 252 | // Need a valid encoding to encode the form elements. |
| 253 | // If the encoding isn't found webkit ends up replacing the params with |
| 254 | // empty strings. So, we don't try to do anything here. |
| 255 | return; |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 256 | } |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 257 | |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 258 | // Look for a suitable search text field in the form when a |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 259 | // selectedInputElement is not provided. |
| 260 | if (!inputElement) { |
| 261 | inputElement = findSuitableSearchInputElement(formElement.get()); |
| 262 | |
| 263 | // Return if no suitable text element has been found. |
| 264 | if (!inputElement) |
| 265 | return; |
| 266 | } |
| 267 | |
| 268 | HTMLFormControlElement* firstSubmitButton = GetButtonToActivate(formElement.get()); |
| 269 | if (firstSubmitButton) { |
| 270 | // The form does not have an active submit button, make the first button |
| 271 | // active. We need to do this, otherwise the URL will not contain the |
| 272 | // name of the submit button. |
| 273 | firstSubmitButton->setActivatedSubmit(true); |
| 274 | } |
| 275 | |
| 276 | bool isValidSearchString = buildSearchString(formElement.get(), &encodedString, &encoding, inputElement); |
| 277 | |
| 278 | if (firstSubmitButton) |
| 279 | firstSubmitButton->setActivatedSubmit(false); |
| 280 | |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 281 | // Return if the search string is not valid. |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 282 | if (!isValidSearchString) |
| 283 | return; |
| 284 | |
| 285 | String action(formElement->action()); |
Torne (Richard Coles) | 8abfc58 | 2013-09-12 12:10:38 +0100 | [diff] [blame] | 286 | KURL url(formElement->document().completeURL(action.isNull() ? "" : action)); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 287 | RefPtr<FormData> formData = FormData::create(encodedString); |
| 288 | url.setQuery(formData->flattenToString()); |
| 289 | m_url = url; |
Ben Murdoch | 02772c6 | 2013-07-26 10:21:05 +0100 | [diff] [blame] | 290 | m_encoding = String(encoding.name()); |
Ben Murdoch | e69819b | 2013-07-17 14:56:49 +0100 | [diff] [blame] | 291 | } |
| 292 | |
Torne (Richard Coles) | 51b2906 | 2013-11-28 11:56:03 +0000 | [diff] [blame] | 293 | } // namespace blink |