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