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 "core/loader/FormSubmission.h" 33 34 #include "core/HTMLNames.h" 35 #include "core/InputTypeNames.h" 36 #include "core/dom/Document.h" 37 #include "core/events/Event.h" 38 #include "core/html/DOMFormData.h" 39 #include "core/html/HTMLFormControlElement.h" 40 #include "core/html/HTMLFormElement.h" 41 #include "core/html/HTMLInputElement.h" 42 #include "core/html/parser/HTMLParserIdioms.h" 43 #include "core/loader/FrameLoadRequest.h" 44 #include "core/loader/FrameLoader.h" 45 #include "platform/heap/Handle.h" 46 #include "platform/network/FormData.h" 47 #include "platform/network/FormDataBuilder.h" 48 #include "wtf/CurrentTime.h" 49 #include "wtf/text/StringBuilder.h" 50 #include "wtf/text/TextEncoding.h" 51 52 namespace blink { 53 54 using namespace HTMLNames; 55 56 static 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 64 static 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.replaceWithLiteral('&', "\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()).replaceWithLiteral('+', "%20"); 77 78 StringBuilder query; 79 query.append(url.query()); 80 if (!query.isEmpty()) 81 query.append('&'); 82 query.append(body); 83 url.setQuery(query.toString()); 84 } 85 86 void FormSubmission::Attributes::parseAction(const String& action) 87 { 88 // m_action cannot be converted to KURL (bug https://crbug.com/388664) 89 m_action = stripLeadingAndTrailingHTMLSpaces(action); 90 } 91 92 AtomicString FormSubmission::Attributes::parseEncodingType(const String& type) 93 { 94 if (equalIgnoringCase(type, "multipart/form-data")) 95 return AtomicString("multipart/form-data", AtomicString::ConstructFromLiteral); 96 if (equalIgnoringCase(type, "text/plain")) 97 return AtomicString("text/plain", AtomicString::ConstructFromLiteral); 98 return AtomicString("application/x-www-form-urlencoded", AtomicString::ConstructFromLiteral); 99 } 100 101 void FormSubmission::Attributes::updateEncodingType(const String& type) 102 { 103 m_encodingType = parseEncodingType(type); 104 m_isMultiPartForm = (m_encodingType == "multipart/form-data"); 105 } 106 107 FormSubmission::Method FormSubmission::Attributes::parseMethodType(const String& type) 108 { 109 if (equalIgnoringCase(type, "post")) 110 return FormSubmission::PostMethod; 111 if (equalIgnoringCase(type, "dialog")) 112 return FormSubmission::DialogMethod; 113 return FormSubmission::GetMethod; 114 } 115 116 void FormSubmission::Attributes::updateMethodType(const String& type) 117 { 118 m_method = parseMethodType(type); 119 } 120 121 String FormSubmission::Attributes::methodString(Method method) 122 { 123 switch (method) { 124 case GetMethod: 125 return "get"; 126 case PostMethod: 127 return "post"; 128 case DialogMethod: 129 return "dialog"; 130 } 131 ASSERT_NOT_REACHED(); 132 return emptyString(); 133 } 134 135 void FormSubmission::Attributes::copyFrom(const Attributes& other) 136 { 137 m_method = other.m_method; 138 m_isMultiPartForm = other.m_isMultiPartForm; 139 140 m_action = other.m_action; 141 m_target = other.m_target; 142 m_encodingType = other.m_encodingType; 143 m_acceptCharset = other.m_acceptCharset; 144 } 145 146 inline FormSubmission::FormSubmission(Method method, const KURL& action, const AtomicString& target, const AtomicString& contentType, PassRefPtrWillBeRawPtr<FormState> state, PassRefPtr<FormData> data, const String& boundary, PassRefPtrWillBeRawPtr<Event> event) 147 : m_method(method) 148 , m_action(action) 149 , m_target(target) 150 , m_contentType(contentType) 151 , m_formState(state) 152 , m_formData(data) 153 , m_boundary(boundary) 154 , m_event(event) 155 { 156 } 157 158 inline FormSubmission::FormSubmission(const String& result) 159 : m_method(DialogMethod) 160 , m_result(result) 161 { 162 } 163 164 PassRefPtrWillBeRawPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const Attributes& attributes, PassRefPtrWillBeRawPtr<Event> event, FormSubmissionTrigger trigger) 165 { 166 ASSERT(form); 167 168 HTMLFormControlElement* submitButton = 0; 169 if (event && event->target()) { 170 for (Node* node = event->target()->toNode(); node; node = node->parentOrShadowHostNode()) { 171 if (node->isElementNode() && toElement(node)->isFormControlElement()) { 172 submitButton = toHTMLFormControlElement(node); 173 break; 174 } 175 } 176 } 177 178 FormSubmission::Attributes copiedAttributes; 179 copiedAttributes.copyFrom(attributes); 180 if (submitButton) { 181 AtomicString attributeValue; 182 if (!(attributeValue = submitButton->fastGetAttribute(formactionAttr)).isNull()) 183 copiedAttributes.parseAction(attributeValue); 184 if (!(attributeValue = submitButton->fastGetAttribute(formenctypeAttr)).isNull()) 185 copiedAttributes.updateEncodingType(attributeValue); 186 if (!(attributeValue = submitButton->fastGetAttribute(formmethodAttr)).isNull()) 187 copiedAttributes.updateMethodType(attributeValue); 188 if (!(attributeValue = submitButton->fastGetAttribute(formtargetAttr)).isNull()) 189 copiedAttributes.setTarget(attributeValue); 190 } 191 192 if (copiedAttributes.method() == DialogMethod) { 193 if (submitButton) 194 return adoptRefWillBeNoop(new FormSubmission(submitButton->resultForDialogSubmit())); 195 return adoptRefWillBeNoop(new FormSubmission("")); 196 } 197 198 Document& document = form->document(); 199 KURL actionURL = document.completeURL(copiedAttributes.action().isEmpty() ? document.url().string() : copiedAttributes.action()); 200 bool isMailtoForm = actionURL.protocolIs("mailto"); 201 bool isMultiPartForm = false; 202 AtomicString encodingType = copiedAttributes.encodingType(); 203 204 if (copiedAttributes.method() == PostMethod) { 205 isMultiPartForm = copiedAttributes.isMultiPartForm(); 206 if (isMultiPartForm && isMailtoForm) { 207 encodingType = AtomicString("application/x-www-form-urlencoded", AtomicString::ConstructFromLiteral); 208 isMultiPartForm = false; 209 } 210 } 211 WTF::TextEncoding dataEncoding = isMailtoForm ? UTF8Encoding() : FormDataBuilder::encodingFromAcceptCharset(copiedAttributes.acceptCharset(), document.inputEncoding(), document.defaultCharset()); 212 RefPtrWillBeRawPtr<DOMFormData> domFormData = DOMFormData::create(dataEncoding.encodingForFormSubmission()); 213 214 bool containsPasswordData = false; 215 for (unsigned i = 0; i < form->associatedElements().size(); ++i) { 216 FormAssociatedElement* control = form->associatedElements()[i]; 217 ASSERT(control); 218 HTMLElement& element = toHTMLElement(*control); 219 if (!element.isDisabledFormControl()) 220 control->appendFormData(*domFormData, isMultiPartForm); 221 if (isHTMLInputElement(element)) { 222 HTMLInputElement& input = toHTMLInputElement(element); 223 if (input.type() == InputTypeNames::password && !input.value().isEmpty()) 224 containsPasswordData = true; 225 } 226 } 227 228 RefPtr<FormData> formData; 229 String boundary; 230 231 if (isMultiPartForm) { 232 formData = domFormData->createMultiPartFormData(); 233 boundary = formData->boundary().data(); 234 } else { 235 formData = domFormData->createFormData(attributes.method() == GetMethod ? FormData::FormURLEncoded : FormData::parseEncodingType(encodingType)); 236 if (copiedAttributes.method() == PostMethod && isMailtoForm) { 237 // Convert the form data into a string that we put into the URL. 238 appendMailtoPostFormDataToURL(actionURL, *formData, encodingType); 239 formData = FormData::create(); 240 } 241 } 242 243 formData->setIdentifier(generateFormDataIdentifier()); 244 formData->setContainsPasswordData(containsPasswordData); 245 AtomicString targetOrBaseTarget = copiedAttributes.target().isEmpty() ? document.baseTarget() : copiedAttributes.target(); 246 return adoptRefWillBeNoop(new FormSubmission(copiedAttributes.method(), actionURL, targetOrBaseTarget, encodingType, FormState::create(*form, trigger), formData.release(), boundary, event)); 247 } 248 249 void FormSubmission::trace(Visitor* visitor) 250 { 251 visitor->trace(m_formState); 252 visitor->trace(m_event); 253 } 254 255 KURL FormSubmission::requestURL() const 256 { 257 if (m_method == FormSubmission::PostMethod) 258 return m_action; 259 260 KURL requestURL(m_action); 261 requestURL.setQuery(m_formData->flattenToString()); 262 return requestURL; 263 } 264 265 void FormSubmission::populateFrameLoadRequest(FrameLoadRequest& frameRequest) 266 { 267 if (!m_target.isEmpty()) 268 frameRequest.setFrameName(m_target); 269 270 if (!m_referrer.referrer.isEmpty()) 271 frameRequest.resourceRequest().setHTTPReferrer(m_referrer); 272 273 if (m_method == FormSubmission::PostMethod) { 274 frameRequest.resourceRequest().setHTTPMethod("POST"); 275 frameRequest.resourceRequest().setHTTPBody(m_formData); 276 277 // construct some user headers if necessary 278 if (m_boundary.isEmpty()) 279 frameRequest.resourceRequest().setHTTPContentType(m_contentType); 280 else 281 frameRequest.resourceRequest().setHTTPContentType(m_contentType + "; boundary=" + m_boundary); 282 } 283 284 frameRequest.resourceRequest().setURL(requestURL()); 285 frameRequest.resourceRequest().addHTTPOriginIfNeeded(AtomicString(m_origin)); 286 } 287 288 } 289