1/*
2 * Copyright (C) 2010 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 "FormSubmission.h"
33
34#include "DOMFormData.h"
35#include "Document.h"
36#include "Event.h"
37#include "FormData.h"
38#include "FormDataBuilder.h"
39#include "FormState.h"
40#include "Frame.h"
41#include "FrameLoadRequest.h"
42#include "FrameLoader.h"
43#include "HTMLFormControlElement.h"
44#include "HTMLFormElement.h"
45#include "HTMLInputElement.h"
46#include "HTMLNames.h"
47#include "HTMLParserIdioms.h"
48#include "TextEncoding.h"
49#include <wtf/CurrentTime.h>
50#include <wtf/RandomNumber.h>
51
52namespace WebCore {
53
54using namespace HTMLNames;
55
56static int64_t generateFormDataIdentifier()
57{
58    // Initialize to the current time to reduce the likelihood of generating
59    // identifiers that overlap with those from past/future browser sessions.
60    static int64_t nextIdentifier = static_cast<int64_t>(currentTime() * 1000000.0);
61    return ++nextIdentifier;
62}
63
64static void appendMailtoPostFormDataToURL(KURL& url, const FormData& data, const String& encodingType)
65{
66    String body = data.flattenToString();
67
68    if (equalIgnoringCase(encodingType, "text/plain")) {
69        // Convention seems to be to decode, and s/&/\r\n/. Also, spaces are encoded as %20.
70        body = decodeURLEscapeSequences(body.replace('&', "\r\n").replace('+', ' ') + "\r\n");
71    }
72
73    Vector<char> bodyData;
74    bodyData.append("body=", 5);
75    FormDataBuilder::encodeStringAsFormData(bodyData, body.utf8());
76    body = String(bodyData.data(), bodyData.size()).replace('+', "%20");
77
78    String query = url.query();
79    if (!query.isEmpty())
80        query.append('&');
81    query.append(body);
82    url.setQuery(query);
83}
84
85void FormSubmission::Attributes::parseAction(const String& action)
86{
87    // FIXME: Can we parse into a KURL?
88    m_action = stripLeadingAndTrailingHTMLSpaces(action);
89}
90
91void FormSubmission::Attributes::parseEncodingType(const String& type)
92{
93    if (type.contains("multipart", false) || type.contains("form-data", false)) {
94        m_encodingType = "multipart/form-data";
95        m_isMultiPartForm = true;
96    } else if (type.contains("text", false) || type.contains("plain", false)) {
97        m_encodingType = "text/plain";
98        m_isMultiPartForm = false;
99    } else {
100        m_encodingType = "application/x-www-form-urlencoded";
101        m_isMultiPartForm = false;
102    }
103}
104
105void FormSubmission::Attributes::parseMethodType(const String& type)
106{
107    if (equalIgnoringCase(type, "post"))
108        m_method = FormSubmission::PostMethod;
109    else if (equalIgnoringCase(type, "get"))
110        m_method = FormSubmission::GetMethod;
111}
112
113void FormSubmission::Attributes::copyFrom(const Attributes& other)
114{
115    m_method = other.m_method;
116    m_isMultiPartForm = other.m_isMultiPartForm;
117
118    m_action = other.m_action;
119    m_target = other.m_target;
120    m_encodingType = other.m_encodingType;
121    m_acceptCharset = other.m_acceptCharset;
122}
123
124inline FormSubmission::FormSubmission(Method method, const KURL& action, const String& target, const String& contentType, PassRefPtr<FormState> state, PassRefPtr<FormData> data, const String& boundary, bool lockHistory, PassRefPtr<Event> event)
125    : m_method(method)
126    , m_action(action)
127    , m_target(target)
128    , m_contentType(contentType)
129    , m_formState(state)
130    , m_formData(data)
131    , m_boundary(boundary)
132    , m_lockHistory(lockHistory)
133    , m_event(event)
134{
135}
136
137PassRefPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const Attributes& attributes, PassRefPtr<Event> event, bool lockHistory, FormSubmissionTrigger trigger)
138{
139    ASSERT(form);
140
141    HTMLFormControlElement* submitButton = 0;
142    if (event && event->target() && event->target()->toNode())
143        submitButton = static_cast<HTMLFormControlElement*>(event->target()->toNode());
144
145    FormSubmission::Attributes copiedAttributes;
146    copiedAttributes.copyFrom(attributes);
147    if (submitButton) {
148        String attributeValue;
149        if (!(attributeValue = submitButton->getAttribute(formactionAttr)).isNull())
150            copiedAttributes.parseAction(attributeValue);
151        if (!(attributeValue = submitButton->getAttribute(formenctypeAttr)).isNull())
152            copiedAttributes.parseEncodingType(attributeValue);
153        if (!(attributeValue = submitButton->getAttribute(formmethodAttr)).isNull())
154            copiedAttributes.parseMethodType(attributeValue);
155        if (!(attributeValue = submitButton->getAttribute(formtargetAttr)).isNull())
156            copiedAttributes.setTarget(attributeValue);
157    }
158
159    Document* document = form->document();
160    KURL actionURL = document->completeURL(copiedAttributes.action().isEmpty() ? document->url().string() : copiedAttributes.action());
161    bool isMailtoForm = actionURL.protocolIs("mailto");
162    bool isMultiPartForm = false;
163    String encodingType = copiedAttributes.encodingType();
164
165    if (copiedAttributes.method() == PostMethod) {
166        isMultiPartForm = copiedAttributes.isMultiPartForm();
167        if (isMultiPartForm && isMailtoForm) {
168            encodingType = "application/x-www-form-urlencoded";
169            isMultiPartForm = false;
170        }
171    }
172
173    TextEncoding dataEncoding = isMailtoForm ? UTF8Encoding() : FormDataBuilder::encodingFromAcceptCharset(copiedAttributes.acceptCharset(), document);
174    RefPtr<DOMFormData> domFormData = DOMFormData::create(dataEncoding.encodingForFormSubmission());
175    Vector<pair<String, String> > formValues;
176
177    for (unsigned i = 0; i < form->associatedElements().size(); ++i) {
178        FormAssociatedElement* control = form->associatedElements()[i];
179        HTMLElement* element = toHTMLElement(control);
180        if (!element->disabled())
181            control->appendFormData(*domFormData, isMultiPartForm);
182        if (element->hasLocalName(inputTag)) {
183            HTMLInputElement* input = static_cast<HTMLInputElement*>(control);
184            if (input->isTextField()) {
185                formValues.append(pair<String, String>(input->name(), input->value()));
186                if (input->isSearchField())
187                    input->addSearchResult();
188            }
189        }
190    }
191
192    RefPtr<FormData> formData;
193    String boundary;
194
195    if (isMultiPartForm) {
196        formData = FormData::createMultiPart(*(static_cast<FormDataList*>(domFormData.get())), domFormData->encoding(), document);
197        boundary = formData->boundary().data();
198    } else {
199        formData = FormData::create(*(static_cast<FormDataList*>(domFormData.get())), domFormData->encoding());
200        if (copiedAttributes.method() == PostMethod && isMailtoForm) {
201            // Convert the form data into a string that we put into the URL.
202            appendMailtoPostFormDataToURL(actionURL, *formData, encodingType);
203            formData = FormData::create();
204        }
205    }
206
207    formData->setIdentifier(generateFormDataIdentifier());
208    String targetOrBaseTarget = copiedAttributes.target().isEmpty() ? document->baseTarget() : copiedAttributes.target();
209    RefPtr<FormState> formState = FormState::create(form, formValues, document->frame(), trigger);
210    return adoptRef(new FormSubmission(copiedAttributes.method(), actionURL, targetOrBaseTarget, encodingType, formState.release(), formData.release(), boundary, lockHistory, event));
211}
212
213KURL FormSubmission::requestURL() const
214{
215    if (m_method == FormSubmission::PostMethod)
216        return m_action;
217
218    KURL requestURL(m_action);
219    requestURL.setQuery(m_formData->flattenToString());
220    return requestURL;
221}
222
223void FormSubmission::populateFrameLoadRequest(FrameLoadRequest& frameRequest)
224{
225    if (!m_target.isEmpty())
226        frameRequest.setFrameName(m_target);
227
228    if (!m_referrer.isEmpty())
229        frameRequest.resourceRequest().setHTTPReferrer(m_referrer);
230
231    if (m_method == FormSubmission::PostMethod) {
232        frameRequest.resourceRequest().setHTTPMethod("POST");
233        frameRequest.resourceRequest().setHTTPBody(m_formData);
234
235        // construct some user headers if necessary
236        if (m_contentType.isNull() || m_contentType == "application/x-www-form-urlencoded")
237            frameRequest.resourceRequest().setHTTPContentType(m_contentType);
238        else // contentType must be "multipart/form-data"
239            frameRequest.resourceRequest().setHTTPContentType(m_contentType + "; boundary=" + m_boundary);
240    }
241
242    frameRequest.resourceRequest().setURL(requestURL());
243    FrameLoader::addHTTPOriginIfNeeded(frameRequest.resourceRequest(), m_origin);
244}
245
246}
247