1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "FormSubmission.h"
34
35#include "ContentSecurityPolicy.h"
36#include "DOMFormData.h"
37#include "Document.h"
38#include "Event.h"
39#include "FormData.h"
40#include "FormDataBuilder.h"
41#include "FormState.h"
42#include "Frame.h"
43#include "FrameLoadRequest.h"
44#include "FrameLoader.h"
45#include "HTMLFormControlElement.h"
46#include "HTMLFormElement.h"
47#include "HTMLInputElement.h"
48#include "HTMLNames.h"
49#include "HTMLParserIdioms.h"
50#include "ScriptDisallowedScope.h"
51#include "TextEncoding.h"
52#include <wtf/WallTime.h>
53
54namespace WebCore {
55
56using namespace HTMLNames;
57
58static int64_t generateFormDataIdentifier()
59{
60 // Initialize to the current time to reduce the likelihood of generating
61 // identifiers that overlap with those from past/future browser sessions.
62 static int64_t nextIdentifier = static_cast<int64_t>(WallTime::now().secondsSinceEpoch().microseconds());
63 return ++nextIdentifier;
64}
65
66static void appendMailtoPostFormDataToURL(URL& url, const FormData& data, const String& encodingType)
67{
68 String body = data.flattenToString();
69
70 if (equalLettersIgnoringASCIICase(encodingType, "text/plain")) {
71 // Convention seems to be to decode, and s/&/\r\n/. Also, spaces are encoded as %20.
72 body = decodeURLEscapeSequences(body.replaceWithLiteral('&', "\r\n").replace('+', ' ') + "\r\n");
73 }
74
75 Vector<char> bodyData;
76 bodyData.append("body=", 5);
77 FormDataBuilder::encodeStringAsFormData(bodyData, body.utf8());
78 body = String(bodyData.data(), bodyData.size()).replaceWithLiteral('+', "%20");
79
80 String query = url.query();
81 if (query.isEmpty())
82 url.setQuery(body);
83 else
84 url.setQuery(query + '&' + body);
85}
86
87void FormSubmission::Attributes::parseAction(const String& action)
88{
89 // FIXME: Can we parse into a URL?
90 m_action = stripLeadingAndTrailingHTMLSpaces(action);
91}
92
93String FormSubmission::Attributes::parseEncodingType(const String& type)
94{
95 if (equalLettersIgnoringASCIICase(type, "multipart/form-data"))
96 return "multipart/form-data"_s;
97 if (equalLettersIgnoringASCIICase(type, "text/plain"))
98 return "text/plain"_s;
99 return "application/x-www-form-urlencoded"_s;
100}
101
102void FormSubmission::Attributes::updateEncodingType(const String& type)
103{
104 m_encodingType = parseEncodingType(type);
105 m_isMultiPartForm = (m_encodingType == "multipart/form-data");
106}
107
108FormSubmission::Method FormSubmission::Attributes::parseMethodType(const String& type)
109{
110 return equalLettersIgnoringASCIICase(type, "post") ? FormSubmission::Method::Post : FormSubmission::Method::Get;
111}
112
113void FormSubmission::Attributes::updateMethodType(const String& type)
114{
115 m_method = parseMethodType(type);
116}
117
118inline FormSubmission::FormSubmission(Method method, const URL& action, const String& target, const String& contentType, Ref<FormState>&& state, Ref<FormData>&& data, const String& boundary, LockHistory lockHistory, Event* event)
119 : m_method(method)
120 , m_action(action)
121 , m_target(target)
122 , m_contentType(contentType)
123 , m_formState(WTFMove(state))
124 , m_formData(WTFMove(data))
125 , m_boundary(boundary)
126 , m_lockHistory(lockHistory)
127 , m_event(event)
128{
129}
130
131static TextEncoding encodingFromAcceptCharset(const String& acceptCharset, Document& document)
132{
133 String normalizedAcceptCharset = acceptCharset;
134 normalizedAcceptCharset.replace(',', ' ');
135
136 for (auto& charset : normalizedAcceptCharset.split(' ')) {
137 TextEncoding encoding(charset);
138 if (encoding.isValid())
139 return encoding;
140 }
141
142 return document.textEncoding();
143}
144
145Ref<FormSubmission> FormSubmission::create(HTMLFormElement& form, const Attributes& attributes, Event* event, LockHistory lockHistory, FormSubmissionTrigger trigger)
146{
147 auto copiedAttributes = attributes;
148
149 if (auto* submitButton = form.findSubmitButton(event)) {
150 AtomicString attributeValue;
151 if (!(attributeValue = submitButton->attributeWithoutSynchronization(formactionAttr)).isNull())
152 copiedAttributes.parseAction(attributeValue);
153 if (!(attributeValue = submitButton->attributeWithoutSynchronization(formenctypeAttr)).isNull())
154 copiedAttributes.updateEncodingType(attributeValue);
155 if (!(attributeValue = submitButton->attributeWithoutSynchronization(formmethodAttr)).isNull())
156 copiedAttributes.updateMethodType(attributeValue);
157 if (!(attributeValue = submitButton->attributeWithoutSynchronization(formtargetAttr)).isNull())
158 copiedAttributes.setTarget(attributeValue);
159 }
160
161 auto& document = form.document();
162 auto actionURL = document.completeURL(copiedAttributes.action().isEmpty() ? document.url().string() : copiedAttributes.action());
163 bool isMailtoForm = actionURL.protocolIs("mailto");
164 bool isMultiPartForm = false;
165 auto encodingType = copiedAttributes.encodingType();
166
167 document.contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(actionURL, ContentSecurityPolicy::InsecureRequestType::FormSubmission);
168
169 if (copiedAttributes.method() == Method::Post) {
170 isMultiPartForm = copiedAttributes.isMultiPartForm();
171 if (isMultiPartForm && isMailtoForm) {
172 encodingType = "application/x-www-form-urlencoded";
173 isMultiPartForm = false;
174 }
175 }
176
177 auto dataEncoding = isMailtoForm ? UTF8Encoding() : encodingFromAcceptCharset(copiedAttributes.acceptCharset(), document);
178 auto domFormData = DOMFormData::create(dataEncoding.encodingForFormSubmissionOrURLParsing());
179 StringPairVector formValues;
180
181 bool containsPasswordData = false;
182 for (auto& control : form.copyAssociatedElementsVector()) {
183 auto& element = control->asHTMLElement();
184 if (!element.isDisabledFormControl())
185 control->appendFormData(domFormData, isMultiPartForm);
186 if (is<HTMLInputElement>(element)) {
187 auto& input = downcast<HTMLInputElement>(element);
188 if (input.isTextField()) {
189 formValues.append({ input.name(), input.value() });
190 input.addSearchResult();
191 }
192 if (input.isPasswordField() && !input.value().isEmpty())
193 containsPasswordData = true;
194 }
195 }
196
197 RefPtr<FormData> formData;
198 String boundary;
199
200 if (isMultiPartForm) {
201 formData = FormData::createMultiPart(domFormData, &document);
202 boundary = formData->boundary().data();
203 } else {
204 formData = FormData::create(domFormData, attributes.method() == Method::Get ? FormData::FormURLEncoded : FormData::parseEncodingType(encodingType));
205 if (copiedAttributes.method() == Method::Post && isMailtoForm) {
206 // Convert the form data into a string that we put into the URL.
207 appendMailtoPostFormDataToURL(actionURL, *formData, encodingType);
208 formData = FormData::create();
209 }
210 }
211
212 formData->setIdentifier(generateFormDataIdentifier());
213 formData->setContainsPasswordData(containsPasswordData);
214
215 auto formState = FormState::create(form, WTFMove(formValues), document, trigger);
216
217 return adoptRef(*new FormSubmission(copiedAttributes.method(), actionURL, form.effectiveTarget(event), encodingType, WTFMove(formState), formData.releaseNonNull(), boundary, lockHistory, event));
218}
219
220URL FormSubmission::requestURL() const
221{
222 if (m_method == Method::Post)
223 return m_action;
224
225 URL requestURL(m_action);
226 requestURL.setQuery(m_formData->flattenToString());
227 return requestURL;
228}
229
230void FormSubmission::populateFrameLoadRequest(FrameLoadRequest& frameRequest)
231{
232 if (!m_target.isEmpty())
233 frameRequest.setFrameName(m_target);
234
235 if (!m_referrer.isEmpty())
236 frameRequest.resourceRequest().setHTTPReferrer(m_referrer);
237
238 if (m_method == Method::Post) {
239 frameRequest.resourceRequest().setHTTPMethod("POST");
240 frameRequest.resourceRequest().setHTTPBody(m_formData.copyRef());
241
242 // construct some user headers if necessary
243 if (m_boundary.isEmpty())
244 frameRequest.resourceRequest().setHTTPContentType(m_contentType);
245 else
246 frameRequest.resourceRequest().setHTTPContentType(m_contentType + "; boundary=" + m_boundary);
247 }
248
249 frameRequest.resourceRequest().setURL(requestURL());
250 FrameLoader::addHTTPOriginIfNeeded(frameRequest.resourceRequest(), m_origin);
251 FrameLoader::addHTTPUpgradeInsecureRequestsIfNeeded(frameRequest.resourceRequest());
252}
253
254}
255