Home | History | Annotate | Download | only in web
      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 "public/web/WebSearchableFormData.h"
     33 
     34 #include "core/HTMLNames.h"
     35 #include "core/InputTypeNames.h"
     36 #include "core/dom/Document.h"
     37 #include "core/html/FormDataList.h"
     38 #include "core/html/HTMLFormControlElement.h"
     39 #include "core/html/HTMLFormElement.h"
     40 #include "core/html/HTMLInputElement.h"
     41 #include "core/html/HTMLOptionElement.h"
     42 #include "core/html/HTMLSelectElement.h"
     43 #include "platform/network/FormDataBuilder.h"
     44 #include "public/web/WebFormElement.h"
     45 #include "public/web/WebInputElement.h"
     46 #include "wtf/text/TextEncoding.h"
     47 
     48 namespace blink {
     49 
     50 using namespace HTMLNames;
     51 
     52 namespace {
     53 
     54 // Gets the encoding for the form.
     55 void GetFormEncoding(const HTMLFormElement* form, WTF::TextEncoding* encoding)
     56 {
     57     String str(form->getAttribute(HTMLNames::accept_charsetAttr));
     58     str.replace(',', ' ');
     59     Vector<String> charsets;
     60     str.split(' ', charsets);
     61     for (Vector<String>::const_iterator i(charsets.begin()); i != charsets.end(); ++i) {
     62         *encoding = WTF::TextEncoding(*i);
     63         if (encoding->isValid())
     64             return;
     65     }
     66     if (!form->document().loader())
     67          return;
     68     *encoding = WTF::TextEncoding(form->document().encoding());
     69 }
     70 
     71 // Returns true if the submit request results in an HTTP URL.
     72 bool IsHTTPFormSubmit(const HTMLFormElement* form)
     73 {
     74     // FIXME: This function is insane. This is an overly complicated way to get this information.
     75     String action(form->action());
     76     // The isNull() check is trying to avoid completeURL returning KURL() when passed a null string.
     77     return form->document().completeURL(action.isNull() ? "" : action).protocolIs("http");
     78 }
     79 
     80 // If the form does not have an activated submit button, the first submit
     81 // button is returned.
     82 HTMLFormControlElement* GetButtonToActivate(HTMLFormElement* form)
     83 {
     84     HTMLFormControlElement* firstSubmitButton = 0;
     85     const FormAssociatedElement::List& element = form->associatedElements();
     86     for (FormAssociatedElement::List::const_iterator i(element.begin()); i != element.end(); ++i) {
     87         if (!(*i)->isFormControlElement())
     88             continue;
     89         HTMLFormControlElement* control = toHTMLFormControlElement(*i);
     90         if (control->isActivatedSubmit()) {
     91             // There's a button that is already activated for submit, return 0.
     92             return 0;
     93         }
     94         if (!firstSubmitButton && control->isSuccessfulSubmitButton())
     95             firstSubmitButton = control;
     96     }
     97     return firstSubmitButton;
     98 }
     99 
    100 // Returns true if the selected state of all the options matches the default
    101 // selected state.
    102 bool IsSelectInDefaultState(HTMLSelectElement* select)
    103 {
    104     const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select->listItems();
    105     if (select->multiple() || select->size() > 1) {
    106         for (WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >::const_iterator i(listItems.begin()); i != listItems.end(); ++i) {
    107             if (!isHTMLOptionElement(*i))
    108                 continue;
    109             HTMLOptionElement* optionElement = toHTMLOptionElement(*i);
    110             if (optionElement->selected() != optionElement->hasAttribute(selectedAttr))
    111                 return false;
    112         }
    113         return true;
    114     }
    115 
    116     // The select is rendered as a combobox (called menulist in WebKit). At
    117     // least one item is selected, determine which one.
    118     HTMLOptionElement* initialSelected = 0;
    119     for (WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >::const_iterator i(listItems.begin()); i != listItems.end(); ++i) {
    120         if (!isHTMLOptionElement(*i))
    121             continue;
    122         HTMLOptionElement* optionElement = toHTMLOptionElement(*i);
    123         if (optionElement->hasAttribute(selectedAttr)) {
    124             // The page specified the option to select.
    125             initialSelected = optionElement;
    126             break;
    127         }
    128         if (!initialSelected)
    129             initialSelected = optionElement;
    130     }
    131     return !initialSelected || initialSelected->selected();
    132 }
    133 
    134 // Returns true if the form element is in its default state, false otherwise.
    135 // The default state is the state of the form element on initial load of the
    136 // page, and varies depending upon the form element. For example, a checkbox is
    137 // in its default state if the checked state matches the state of the checked attribute.
    138 bool IsInDefaultState(HTMLFormControlElement* formElement)
    139 {
    140     ASSERT(formElement);
    141     if (isHTMLInputElement(*formElement)) {
    142         const HTMLInputElement& inputElement = toHTMLInputElement(*formElement);
    143         if (inputElement.type() == InputTypeNames::checkbox || inputElement.type() == InputTypeNames::radio)
    144             return inputElement.checked() == inputElement.hasAttribute(checkedAttr);
    145     } else if (isHTMLSelectElement(*formElement)) {
    146         return IsSelectInDefaultState(toHTMLSelectElement(formElement));
    147     }
    148     return true;
    149 }
    150 
    151 // Look for a suitable search text field in a given HTMLFormElement
    152 // Return nothing if one of those items are found:
    153 //  - A text area field
    154 //  - A file upload field
    155 //  - A Password field
    156 //  - More than one text field
    157 HTMLInputElement* findSuitableSearchInputElement(const HTMLFormElement* form)
    158 {
    159     HTMLInputElement* textElement = 0;
    160     const FormAssociatedElement::List& element = form->associatedElements();
    161     for (FormAssociatedElement::List::const_iterator i(element.begin()); i != element.end(); ++i) {
    162         if (!(*i)->isFormControlElement())
    163             continue;
    164 
    165         HTMLFormControlElement* control = toHTMLFormControlElement(*i);
    166 
    167         if (control->isDisabledFormControl() || control->name().isNull())
    168             continue;
    169 
    170         if (!IsInDefaultState(control) || isHTMLTextAreaElement(*control))
    171             return 0;
    172 
    173         if (isHTMLInputElement(*control) && control->willValidate()) {
    174             const HTMLInputElement& input = toHTMLInputElement(*control);
    175 
    176             // Return nothing if a file upload field or a password field are found.
    177             if (input.type() == InputTypeNames::file || input.type() == InputTypeNames::password)
    178                 return 0;
    179 
    180             if (input.isTextField()) {
    181                 if (textElement) {
    182                     // The auto-complete bar only knows how to fill in one value.
    183                     // This form has multiple fields; don't treat it as searchable.
    184                     return 0;
    185                 }
    186                 textElement = toHTMLInputElement(control);
    187             }
    188         }
    189     }
    190     return textElement;
    191 }
    192 
    193 // Build a search string based on a given HTMLFormElement and HTMLInputElement
    194 //
    195 // Search string output example from www.google.com:
    196 // "hl=en&source=hp&biw=1085&bih=854&q={searchTerms}&btnG=Google+Search&aq=f&aqi=&aql=&oq="
    197 //
    198 // Return false if the provided HTMLInputElement is not found in the form
    199 bool buildSearchString(const HTMLFormElement* form, Vector<char>* encodedString, WTF::TextEncoding* encoding, const HTMLInputElement* textElement)
    200 {
    201     bool isElementFound = false;
    202 
    203     const FormAssociatedElement::List& elements = form->associatedElements();
    204     for (FormAssociatedElement::List::const_iterator i(elements.begin()); i != elements.end(); ++i) {
    205         if (!(*i)->isFormControlElement())
    206             continue;
    207 
    208         HTMLFormControlElement* control = toHTMLFormControlElement(*i);
    209 
    210         if (control->isDisabledFormControl() || control->name().isNull())
    211             continue;
    212 
    213         RefPtrWillBeRawPtr<FormDataList> dataList = FormDataList::create(*encoding);
    214         if (!control->appendFormData(*dataList, false))
    215             continue;
    216 
    217         const WillBeHeapVector<FormDataList::Item>& items = dataList->items();
    218 
    219         for (WillBeHeapVector<FormDataList::Item>::const_iterator j(items.begin()); j != items.end(); ++j) {
    220             if (!encodedString->isEmpty())
    221                 encodedString->append('&');
    222             FormDataBuilder::encodeStringAsFormData(*encodedString, j->data());
    223             encodedString->append('=');
    224             ++j;
    225             if (control == textElement) {
    226                 encodedString->append("{searchTerms}", 13);
    227                 isElementFound = true;
    228             } else
    229                 FormDataBuilder::encodeStringAsFormData(*encodedString, j->data());
    230         }
    231     }
    232     return isElementFound;
    233 }
    234 } // namespace
    235 
    236 WebSearchableFormData::WebSearchableFormData(const WebFormElement& form, const WebInputElement& selectedInputElement)
    237 {
    238     RefPtrWillBeRawPtr<HTMLFormElement> formElement = static_cast<PassRefPtrWillBeRawPtr<HTMLFormElement> >(form);
    239     HTMLInputElement* inputElement = static_cast<PassRefPtrWillBeRawPtr<HTMLInputElement> >(selectedInputElement).get();
    240 
    241     // Only consider forms that GET data.
    242     // Allow HTTPS only when an input element is provided.
    243     if (equalIgnoringCase(formElement->getAttribute(methodAttr), "post")
    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;
    256     }
    257 
    258     // Look for a suitable search text field in the form when a
    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 
    281     // Return if the search string is not valid.
    282     if (!isValidSearchString)
    283         return;
    284 
    285     String action(formElement->action());
    286     KURL url(formElement->document().completeURL(action.isNull() ? "" : action));
    287     RefPtr<FormData> formData = FormData::create(encodedString);
    288     url.setQuery(formData->flattenToString());
    289     m_url = url;
    290     m_encoding = String(encoding.name());
    291 }
    292 
    293 } // namespace blink
    294