/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. * (C) 2006 Alexey Proskuryakov (ap@nypop.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include "config.h" #include "HTMLFormElement.h" #include "Base64.h" #include "CSSHelper.h" #include "CString.h" #include "Event.h" #include "EventNames.h" #include "FormData.h" #include "FormDataList.h" #include "Frame.h" #include "FrameLoader.h" #include "HTMLDocument.h" #include "HTMLFormCollection.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLNames.h" #include "MIMETypeRegistry.h" #include "RenderTextControl.h" namespace WebCore { using namespace EventNames; using namespace HTMLNames; HTMLFormElement::HTMLFormElement(Document* doc) : HTMLElement(formTag, doc) , m_elementAliases(0) , collectionInfo(0) , m_enctype("application/x-www-form-urlencoded") , m_post(false) , m_multipart(false) , m_autocomplete(true) , m_insubmit(false) , m_doingsubmit(false) , m_inreset(false) , m_malformed(false) , m_preserveAcrossRemove(false) { } HTMLFormElement::~HTMLFormElement() { delete m_elementAliases; delete collectionInfo; for (unsigned i = 0; i < formElements.size(); ++i) formElements[i]->formDestroyed(); for (unsigned i = 0; i < imgElements.size(); ++i) imgElements[i]->m_form = 0; } bool HTMLFormElement::formWouldHaveSecureSubmission(const String &url) { if (url.isNull()) { return false; } return document()->completeURL(url.deprecatedString()).startsWith("https:", false); } void HTMLFormElement::attach() { HTMLElement::attach(); // note we don't deal with calling secureFormRemoved() on detach, because the timing // was such that it cleared our state too early if (formWouldHaveSecureSubmission(m_url)) document()->secureFormAdded(); } void HTMLFormElement::insertedIntoDocument() { if (document()->isHTMLDocument()) { HTMLDocument *doc = static_cast(document()); doc->addNamedItem(oldNameAttr); } HTMLElement::insertedIntoDocument(); } void HTMLFormElement::removedFromDocument() { if (document()->isHTMLDocument()) { HTMLDocument *doc = static_cast(document()); doc->removeNamedItem(oldNameAttr); } HTMLElement::removedFromDocument(); } void HTMLFormElement::handleLocalEvents(Event* event, bool useCapture) { EventTargetNode* targetNode = event->target()->toNode(); if (!useCapture && targetNode && targetNode != this && (event->type() == submitEvent || event->type() == resetEvent)) { event->stopPropagation(); return; } HTMLElement::handleLocalEvents(event, useCapture); } unsigned HTMLFormElement::length() const { int len = 0; for (unsigned i = 0; i < formElements.size(); ++i) if (formElements[i]->isEnumeratable()) ++len; return len; } Node* HTMLFormElement::item(unsigned index) { return elements()->item(index); } void HTMLFormElement::submitClick(Event* event) { bool submitFound = false; for (unsigned i = 0; i < formElements.size(); ++i) { if (formElements[i]->hasLocalName(inputTag)) { HTMLInputElement *element = static_cast(formElements[i]); if (element->isSuccessfulSubmitButton() && element->renderer()) { submitFound = true; element->dispatchSimulatedClick(event); break; } } } if (!submitFound) // submit the form without a submit or image input prepareSubmit(event); } static DeprecatedCString encodeCString(const CString& cstr) { DeprecatedCString e = cstr.deprecatedCString(); // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 // same safe characters as Netscape for compatibility static const char *safe = "-._*"; int elen = e.length(); DeprecatedCString encoded((elen + e.contains('\n')) * 3 + 1); int enclen = 0; for (int pos = 0; pos < elen; pos++) { unsigned char c = e[pos]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || strchr(safe, c)) encoded[enclen++] = c; else if (c == ' ') encoded[enclen++] = '+'; else if (c == '\n' || (c == '\r' && e[pos + 1] != '\n')) { encoded[enclen++] = '%'; encoded[enclen++] = '0'; encoded[enclen++] = 'D'; encoded[enclen++] = '%'; encoded[enclen++] = '0'; encoded[enclen++] = 'A'; } else if (c != '\r') { encoded[enclen++] = '%'; unsigned int h = c / 16; h += (h > 9) ? ('A' - 10) : '0'; encoded[enclen++] = h; unsigned int l = c % 16; l += (l > 9) ? ('A' - 10) : '0'; encoded[enclen++] = l; } } encoded[enclen++] = '\0'; encoded.truncate(enclen); return encoded; } static int randomNumber() { static bool randomSeeded = false; #if PLATFORM(DARWIN) if (!randomSeeded) { srandomdev(); randomSeeded = true; } return random(); #else if (!randomSeeded) { srand(static_cast(time(0))); randomSeeded = true; } return rand(); #endif } PassRefPtr HTMLFormElement::formData(const char* boundary) const { DeprecatedCString enc_string = ""; String str = m_acceptcharset; str.replace(',', ' '); Vector charsets = str.split(' '); TextEncoding encoding; Frame* frame = document()->frame(); Vector::const_iterator end = charsets.end(); for (Vector::const_iterator it = charsets.begin(); it != end; ++it) if ((encoding = TextEncoding(*it)).isValid()) break; if (!encoding.isValid()) { if (frame) encoding = frame->loader()->encoding(); else encoding = Latin1Encoding(); } RefPtr result = new FormData; for (unsigned i = 0; i < formElements.size(); ++i) { HTMLGenericFormElement* current = formElements[i]; FormDataList lst(encoding); if (!current->disabled() && current->appendFormData(lst, m_multipart)) { size_t ln = lst.list().size(); for (size_t j = 0; j < ln; ++j) { const FormDataListItem& item = lst.list()[j]; if (!m_multipart) { // handle ISINDEX / special // but only if its the first entry if (enc_string.isEmpty() && item.m_data == "isindex") { enc_string += encodeCString(lst.list()[j + 1].m_data); ++j; } else { if (!enc_string.isEmpty()) enc_string += '&'; enc_string += encodeCString(item.m_data); enc_string += "="; enc_string += encodeCString(lst.list()[j + 1].m_data); ++j; } } else { DeprecatedCString hstr("--"); hstr += boundary; hstr += "\r\n"; hstr += "Content-Disposition: form-data; name=\""; hstr += item.m_data.data(); hstr += "\""; // if the current type is FILE, then we also need to // include the filename if (current->hasLocalName(inputTag) && static_cast(current)->inputType() == HTMLInputElement::FILE) { String path = static_cast(current)->value(); // FIXME: This won't work if the filename includes a " mark, // or control characters like CR or LF. This also does strange // things if the filename includes characters you can't encode // in the website's character set. hstr += "; filename=\""; int start = path.reverseFind('/') + 1; int length = path.length() - start; hstr += encoding.encode(reinterpret_cast(path.characters() + start), length, true).data(); hstr += "\""; if (!static_cast(current)->value().isEmpty()) { DeprecatedString mimeType = MIMETypeRegistry::getMIMETypeForPath(path).deprecatedString(); if (!mimeType.isEmpty()) { hstr += "\r\nContent-Type: "; hstr += mimeType.ascii(); } } } hstr += "\r\n\r\n"; // append body result->appendData(hstr.data(), hstr.length()); const FormDataListItem& item = lst.list()[j + 1]; if (size_t dataSize = item.m_data.length()) result->appendData(item.m_data.data(), dataSize); else if (!item.m_path.isEmpty()) result->appendFile(item.m_path); result->appendData("\r\n", 2); ++j; } } } } if (m_multipart) { enc_string = "--"; enc_string += boundary; enc_string += "--\r\n"; } result->appendData(enc_string.data(), enc_string.length()); return result; } void HTMLFormElement::parseEnctype(const String& type) { if(type.contains("multipart", false) || type.contains("form-data", false)) { m_enctype = "multipart/form-data"; m_multipart = true; } else if (type.contains("text", false) || type.contains("plain", false)) { m_enctype = "text/plain"; m_multipart = false; } else { m_enctype = "application/x-www-form-urlencoded"; m_multipart = false; } } bool HTMLFormElement::prepareSubmit(Event* event) { Frame* frame = document()->frame(); if (m_insubmit || !frame) return m_insubmit; m_insubmit = true; m_doingsubmit = false; if (dispatchHTMLEvent(submitEvent, true, true) && !m_doingsubmit) m_doingsubmit = true; m_insubmit = false; if (m_doingsubmit) submit(event, true); return m_doingsubmit; } void HTMLFormElement::submit() { submit(0, false); } // Returns a 0-terminated C string in the vector. static void getUniqueBoundaryString(Vector& boundary) { // The RFC 2046 spec says the AlphaNumeric characters plus the following characters // are legal for boundaries: '()+_,-./:=? // However the following characters, though legal, cause some sites to fail: // (),./:= // http://bugs.webkit.org/show_bug.cgi?id=13352 static const char AlphaNumericEncMap[64] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x41 // FIXME gmail does not accept legal characters in the form boundary // As stated above, some legal characters cause, sites to fail. Specifically // the / character which was the last character in the above array. I have // replaced the last character with another character already in the array // (notice the first and last values are both 0x41, A). Instead of picking // another unique legal character for boundary strings that, because it has // never been tested, may or may not break other sites, I simply // replaced / with A. This means A is twice as likely to occur in our boundary // strings than any other character but I think this is fine for the time being. // The FIXME here is about restoring the / character once the aforementioned // radar has been resolved. }; // Start with an informative prefix. const char boundaryPrefix[] = "----WebKitFormBoundary"; boundary.append(boundaryPrefix, strlen(boundaryPrefix)); // Append 16 random 7bit ascii AlphaNumeric characters. Vector randomBytes; for (int i = 0; i < 4; ++i) { int randomness = randomNumber(); randomBytes.append(AlphaNumericEncMap[(randomness >> 24) & 0x3F]); randomBytes.append(AlphaNumericEncMap[(randomness >> 16) & 0x3F]); randomBytes.append(AlphaNumericEncMap[(randomness >> 8) & 0x3F]); randomBytes.append(AlphaNumericEncMap[randomness & 0x3F]); } boundary.append(randomBytes); boundary.append(0); // Add a 0 at the end so we can use this as a C-style string. } void HTMLFormElement::submit(Event* event, bool activateSubmitButton) { FrameView *view = document()->view(); Frame *frame = document()->frame(); if (!view || !frame) return; if (m_insubmit) { m_doingsubmit = true; return; } m_insubmit = true; HTMLGenericFormElement* firstSuccessfulSubmitButton = 0; bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? frame->loader()->clearRecordedFormValues(); for (unsigned i = 0; i < formElements.size(); ++i) { HTMLGenericFormElement* current = formElements[i]; if (current->hasLocalName(inputTag)) { HTMLInputElement* input = static_cast(current); if (input->isTextField()) { frame->loader()->recordFormValue(input->name(), input->value(), this); if (input->isSearchField()) input->addSearchResult(); } } if (needButtonActivation) { if (current->isActivatedSubmit()) needButtonActivation = false; else if (firstSuccessfulSubmitButton == 0 && current->isSuccessfulSubmitButton()) firstSuccessfulSubmitButton = current; } } if (needButtonActivation && firstSuccessfulSubmitButton) firstSuccessfulSubmitButton->setActivatedSubmit(true); if (m_post) { if (!m_multipart) frame->loader()->submitForm("POST", m_url, formData(0), m_target, enctype(), String(), event); else { Vector boundary; getUniqueBoundaryString(boundary); frame->loader()->submitForm("POST", m_url, formData(boundary.data()), m_target, enctype(), boundary.data(), event); } } else { m_multipart = false; frame->loader()->submitForm("GET", m_url, formData(0), m_target, String(), String(), event); } if (needButtonActivation && firstSuccessfulSubmitButton) firstSuccessfulSubmitButton->setActivatedSubmit(false); m_doingsubmit = m_insubmit = false; } void HTMLFormElement::reset() { Frame *frame = document()->frame(); if (m_inreset || !frame) return; m_inreset = true; // ### DOM2 labels this event as not cancelable, however // common browsers( sick! ) allow it be cancelled. if ( !dispatchHTMLEvent(resetEvent,true, true) ) { m_inreset = false; return; } for (unsigned i = 0; i < formElements.size(); ++i) formElements[i]->reset(); m_inreset = false; } void HTMLFormElement::parseMappedAttribute(MappedAttribute *attr) { if (attr->name() == actionAttr) { bool oldURLWasSecure = formWouldHaveSecureSubmission(m_url); m_url = parseURL(attr->value()); bool newURLIsSecure = formWouldHaveSecureSubmission(m_url); if (m_attached && (oldURLWasSecure != newURLIsSecure)) if (newURLIsSecure) document()->secureFormAdded(); else document()->secureFormRemoved(); } else if (attr->name() == targetAttr) m_target = attr->value(); else if (attr->name() == methodAttr) { if (equalIgnoringCase(attr->value(), "post")) m_post = true; else if (equalIgnoringCase(attr->value(), "get")) m_post = false; } else if (attr->name() == enctypeAttr) parseEnctype(attr->value()); else if (attr->name() == accept_charsetAttr) // space separated list of charsets the server // accepts - see rfc2045 m_acceptcharset = attr->value(); else if (attr->name() == acceptAttr) { // ignore this one for the moment... } else if (attr->name() == autocompleteAttr) m_autocomplete = !equalIgnoringCase(attr->value(), "off"); else if (attr->name() == onsubmitAttr) setHTMLEventListener(submitEvent, attr); else if (attr->name() == onresetAttr) setHTMLEventListener(resetEvent, attr); else if (attr->name() == nameAttr) { String newNameAttr = attr->value(); if (inDocument() && document()->isHTMLDocument()) { HTMLDocument *doc = static_cast(document()); doc->removeNamedItem(oldNameAttr); doc->addNamedItem(newNameAttr); } oldNameAttr = newNameAttr; } else HTMLElement::parseMappedAttribute(attr); } template static void removeFromVector(Vector & vec, T* item) { size_t size = vec.size(); for (size_t i = 0; i != size; ++i) if (vec[i] == item) { vec.remove(i); break; } } unsigned HTMLFormElement::formElementIndex(HTMLGenericFormElement *e) { // Check for the special case where this element is the very last thing in // the form's tree of children; we don't want to walk the entire tree in that // common case that occurs during parsing; instead we'll just return a value // that says "add this form element to the end of the array". if (e->traverseNextNode(this)) { unsigned i = 0; for (Node *node = this; node; node = node->traverseNextNode(this)) { if (node == e) return i; if (node->isHTMLElement() && static_cast(node)->isGenericFormElement() && static_cast(node)->form() == this) ++i; } } return formElements.size(); } void HTMLFormElement::registerFormElement(HTMLGenericFormElement* e) { Document* doc = document(); doc->checkedRadioButtons().removeButton(e); m_checkedRadioButtons.addButton(e); formElements.insert(formElementIndex(e), e); doc->incDOMTreeVersion(); } void HTMLFormElement::removeFormElement(HTMLGenericFormElement* e) { m_checkedRadioButtons.removeButton(e); removeFromVector(formElements, e); document()->incDOMTreeVersion(); } bool HTMLFormElement::isURLAttribute(Attribute *attr) const { return attr->name() == actionAttr; } void HTMLFormElement::registerImgElement(HTMLImageElement *e) { imgElements.append(e); } void HTMLFormElement::removeImgElement(HTMLImageElement *e) { removeFromVector(imgElements, e); } PassRefPtr HTMLFormElement::elements() { return new HTMLFormCollection(this); } String HTMLFormElement::name() const { return getAttribute(nameAttr); } void HTMLFormElement::setName(const String &value) { setAttribute(nameAttr, value); } String HTMLFormElement::acceptCharset() const { return getAttribute(accept_charsetAttr); } void HTMLFormElement::setAcceptCharset(const String &value) { setAttribute(accept_charsetAttr, value); } String HTMLFormElement::action() const { return getAttribute(actionAttr); } void HTMLFormElement::setAction(const String &value) { setAttribute(actionAttr, value); } void HTMLFormElement::setEnctype(const String &value) { setAttribute(enctypeAttr, value); } String HTMLFormElement::method() const { return getAttribute(methodAttr); } void HTMLFormElement::setMethod(const String &value) { setAttribute(methodAttr, value); } String HTMLFormElement::target() const { return getAttribute(targetAttr); } void HTMLFormElement::setTarget(const String &value) { setAttribute(targetAttr, value); } PassRefPtr HTMLFormElement::elementForAlias(const AtomicString& alias) { if (alias.isEmpty() || !m_elementAliases) return 0; return m_elementAliases->get(alias.impl()); } void HTMLFormElement::addElementAlias(HTMLGenericFormElement* element, const AtomicString& alias) { if (alias.isEmpty()) return; if (!m_elementAliases) m_elementAliases = new AliasMap; m_elementAliases->set(alias.impl(), element); } void HTMLFormElement::getNamedElements(const AtomicString& name, Vector >& namedItems) { elements()->namedItems(name, namedItems); // see if we have seen something with this name before RefPtr aliasElem; if (aliasElem = elementForAlias(name)) { bool found = false; for (unsigned n = 0; n < namedItems.size(); n++) { if (namedItems[n] == aliasElem.get()) { found = true; break; } } if (!found) // we have seen it before but it is gone now. still, we need to return it. namedItems.append(aliasElem.get()); } // name has been accessed, remember it if (namedItems.size() && aliasElem != namedItems.first()) addElementAlias(static_cast(namedItems.first().get()), name); } void HTMLFormElement::CheckedRadioButtons::addButton(HTMLGenericFormElement* element) { // We only want to add radio buttons. if (!element->isRadioButton()) return; // Without a name, there is no group. if (element->name().isEmpty()) return; HTMLInputElement* inputElement = static_cast(element); // We only track checked buttons. if (!inputElement->checked()) return; if (!m_nameToCheckedRadioButtonMap) m_nameToCheckedRadioButtonMap.set(new NameToInputMap); else { HTMLInputElement* currentCheckedRadio = m_nameToCheckedRadioButtonMap->get(element->name().impl()); if (currentCheckedRadio && currentCheckedRadio != element) currentCheckedRadio->setChecked(false); } m_nameToCheckedRadioButtonMap->set(element->name().impl(), inputElement); } HTMLInputElement* HTMLFormElement::CheckedRadioButtons::checkedButtonForGroup(const AtomicString& name) const { if (!m_nameToCheckedRadioButtonMap) return 0; return m_nameToCheckedRadioButtonMap->get(name.impl()); } void HTMLFormElement::CheckedRadioButtons::removeButton(HTMLGenericFormElement* element) { if (element->name().isEmpty() || !m_nameToCheckedRadioButtonMap) return; NameToInputMap::iterator it = m_nameToCheckedRadioButtonMap->find(element->name().impl()); if (it == m_nameToCheckedRadioButtonMap->end() || it->second != element) return; ASSERT(element->isRadioButton()); ASSERT(element->isChecked()); m_nameToCheckedRadioButtonMap->remove(it); if (m_nameToCheckedRadioButtonMap->isEmpty()) m_nameToCheckedRadioButtonMap.clear(); } } // namespace