| /* |
| * This file is part of the DOM implementation for KDE. |
| * |
| * 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 Apple Computer, Inc. |
| * (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 "HTMLFormElementImpl.h" |
| #include "HTMLFormCollectionImpl.h" |
| #include "html_imageimpl.h" |
| #include "html_documentimpl.h" |
| #include "csshelper.h" // For kthml::parseURL |
| #include "FormDataList.h" |
| |
| #include "rendering/render_form.h" |
| #include "EventNames.h" |
| |
| #include "Frame.h" |
| |
| #include "TextEncoding.h" |
| #include "htmlnames.h" |
| |
| namespace WebCore { |
| |
| using namespace EventNames; |
| using namespace HTMLNames; |
| |
| HTMLFormElementImpl::HTMLFormElementImpl(DocumentImpl *doc) |
| : HTMLElementImpl(formTag, doc) |
| { |
| collectionInfo = 0; |
| m_post = false; |
| m_multipart = false; |
| m_autocomplete = true; |
| m_insubmit = false; |
| m_doingsubmit = false; |
| m_inreset = false; |
| m_enctype = "application/x-www-form-urlencoded"; |
| m_boundary = "----------0xKhTmLbOuNdArY"; |
| m_malformed = false; |
| } |
| |
| HTMLFormElementImpl::~HTMLFormElementImpl() |
| { |
| delete collectionInfo; |
| |
| for (unsigned i = 0; i < formElements.size(); ++i) |
| formElements[i]->m_form = 0; |
| for (unsigned i = 0; i < imgElements.size(); ++i) |
| imgElements[i]->m_form = 0; |
| } |
| |
| bool HTMLFormElementImpl::formWouldHaveSecureSubmission(const DOMString &url) |
| { |
| if (url.isNull()) { |
| return false; |
| } |
| return getDocument()->completeURL(url.qstring()).startsWith("https:", false); |
| } |
| |
| void HTMLFormElementImpl::attach() |
| { |
| HTMLElementImpl::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)) |
| getDocument()->secureFormAdded(); |
| } |
| |
| void HTMLFormElementImpl::insertedIntoDocument() |
| { |
| if (getDocument()->isHTMLDocument()) { |
| HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument()); |
| document->addNamedItem(oldNameAttr); |
| } |
| |
| HTMLElementImpl::insertedIntoDocument(); |
| } |
| |
| void HTMLFormElementImpl::removedFromDocument() |
| { |
| if (getDocument()->isHTMLDocument()) { |
| HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument()); |
| document->removeNamedItem(oldNameAttr); |
| } |
| |
| HTMLElementImpl::removedFromDocument(); |
| } |
| |
| int HTMLFormElementImpl::length() const |
| { |
| int len = 0; |
| for (unsigned i = 0; i < formElements.size(); ++i) |
| if (formElements[i]->isEnumeratable()) |
| ++len; |
| |
| return len; |
| } |
| |
| |
| void HTMLFormElementImpl::submitClick() |
| { |
| bool submitFound = false; |
| for (unsigned i = 0; i < formElements.size(); ++i) { |
| if (formElements[i]->hasLocalName(inputTag)) { |
| HTMLInputElementImpl *element = static_cast<HTMLInputElementImpl *>(formElements[i]); |
| if (element->isSuccessfulSubmitButton() && element->renderer()) { |
| submitFound = true; |
| element->click(false); |
| break; |
| } |
| } |
| } |
| if (!submitFound) // submit the form without a submit or image input |
| prepareSubmit(); |
| } |
| |
| static QCString encodeCString(const QCString& e) |
| { |
| // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 |
| // safe characters like NS handles them for compatibility |
| static const char *safe = "-._*"; |
| int elen = e.length(); |
| QCString 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; |
| } |
| |
| bool HTMLFormElementImpl::formData(FormData &form_data) const |
| { |
| QCString enc_string = ""; // used for non-multipart data |
| |
| QString str = m_acceptcharset.qstring(); |
| str.replace(',', ' '); |
| QStringList charsets = QStringList::split(' ', str); |
| TextEncoding encoding(InvalidEncoding); |
| Frame *frame = getDocument()->frame(); |
| for (QStringList::Iterator it = charsets.begin(); it != charsets.end(); ++it) { |
| if ((encoding = TextEncoding((*it).latin1())).isValid()) |
| break; |
| } |
| |
| if (!encoding.isValid()) { |
| if (frame) |
| encoding = TextEncoding(frame->encoding().latin1()); |
| else |
| encoding = TextEncoding(Latin1Encoding); |
| } |
| |
| for (unsigned i = 0; i < formElements.size(); ++i) { |
| HTMLGenericFormElementImpl* current = formElements[i]; |
| FormDataList lst(encoding); |
| |
| if (!current->disabled() && current->appendFormData(lst, m_multipart)) { |
| for (QValueListConstIterator<FormDataListItem> it = lst.begin(); it != lst.end(); ++it) { |
| if (!m_multipart) { |
| // handle ISINDEX / <input name=isindex> special |
| // but only if its the first entry |
| if ( enc_string.isEmpty() && (*it).m_data == "isindex" ) { |
| ++it; |
| enc_string += encodeCString( (*it).m_data ); |
| } |
| else { |
| if(!enc_string.isEmpty()) |
| enc_string += '&'; |
| |
| enc_string += encodeCString((*it).m_data); |
| enc_string += "="; |
| ++it; |
| enc_string += encodeCString((*it).m_data); |
| } |
| } |
| else |
| { |
| QCString hstr("--"); |
| hstr += m_boundary.qstring().latin1(); |
| hstr += "\r\n"; |
| hstr += "Content-Disposition: form-data; name=\""; |
| hstr += (*it).m_data.data(); |
| hstr += "\""; |
| |
| // if the current type is FILE, then we also need to |
| // include the filename |
| if (current->hasLocalName(inputTag) && |
| static_cast<HTMLInputElementImpl*>(current)->inputType() == HTMLInputElementImpl::FILE) |
| { |
| QString path = static_cast<HTMLInputElementImpl*>(current)->value().qstring(); |
| |
| // 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=\""; |
| hstr += encoding.fromUnicode(path.mid(path.findRev('/') + 1), true); |
| hstr += "\""; |
| |
| if(!static_cast<HTMLInputElementImpl*>(current)->value().isEmpty()) |
| { |
| QString mimeType = frame ? frame->mimeTypeForFileName(path) : QString(); |
| if (!mimeType.isEmpty()) { |
| hstr += "\r\nContent-Type: "; |
| hstr += mimeType.ascii(); |
| } |
| } |
| } |
| |
| hstr += "\r\n\r\n"; |
| ++it; |
| |
| // append body |
| form_data.appendData(hstr.data(), hstr.length()); |
| const FormDataListItem &item = *it; |
| size_t dataSize = item.m_data.size(); |
| if (dataSize != 0) |
| form_data.appendData(item.m_data, dataSize - 1); |
| else if (!item.m_path.isEmpty()) |
| form_data.appendFile(item.m_path); |
| form_data.appendData("\r\n", 2); |
| } |
| } |
| } |
| } |
| |
| |
| if (m_multipart) |
| enc_string = ("--" + m_boundary.qstring() + "--\r\n").ascii(); |
| |
| form_data.appendData(enc_string.data(), enc_string.length()); |
| return true; |
| } |
| |
| void HTMLFormElementImpl::parseEnctype(const DOMString& 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; |
| } |
| } |
| |
| void HTMLFormElementImpl::setBoundary( const DOMString& bound ) |
| { |
| m_boundary = bound; |
| } |
| |
| bool HTMLFormElementImpl::prepareSubmit() |
| { |
| Frame *frame = getDocument()->frame(); |
| if (m_insubmit || !frame) |
| return m_insubmit; |
| |
| m_insubmit = true; |
| m_doingsubmit = false; |
| |
| if ( dispatchHTMLEvent(submitEvent,false,true) && !m_doingsubmit ) |
| m_doingsubmit = true; |
| |
| m_insubmit = false; |
| |
| if ( m_doingsubmit ) |
| submit(true); |
| |
| return m_doingsubmit; |
| } |
| |
| void HTMLFormElementImpl::submit( bool activateSubmitButton ) |
| { |
| FrameView *view = getDocument()->view(); |
| Frame *frame = getDocument()->frame(); |
| if (!view || !frame) { |
| return; |
| } |
| |
| if ( m_insubmit ) { |
| m_doingsubmit = true; |
| return; |
| } |
| |
| m_insubmit = true; |
| |
| HTMLGenericFormElementImpl* firstSuccessfulSubmitButton = 0; |
| bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? |
| |
| frame->clearRecordedFormValues(); |
| for (unsigned i = 0; i < formElements.size(); ++i) { |
| HTMLGenericFormElementImpl* current = formElements[i]; |
| // Our app needs to get form values for password fields for doing password autocomplete, |
| // so we are more lenient in pushing values, and let the app decide what to save when. |
| if (current->hasLocalName(inputTag)) { |
| HTMLInputElementImpl *input = static_cast<HTMLInputElementImpl*>(current); |
| if (input->inputType() == HTMLInputElementImpl::TEXT |
| || input->inputType() == HTMLInputElementImpl::PASSWORD |
| || input->inputType() == HTMLInputElementImpl::SEARCH) |
| { |
| frame->recordFormValue(input->name().qstring(), input->value().qstring(), this); |
| if (input->renderer() && input->inputType() == HTMLInputElementImpl::SEARCH) |
| static_cast<RenderLineEdit*>(input->renderer())->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) |
| m_multipart = false; |
| |
| FormData form_data; |
| if (formData(form_data)) { |
| if(m_post) { |
| frame->submitForm( "post", m_url.qstring(), form_data, |
| m_target.qstring(), |
| enctype().qstring(), |
| boundary().qstring() ); |
| } |
| else { |
| frame->submitForm( "get", m_url.qstring(), form_data, |
| m_target.qstring() ); |
| } |
| } |
| |
| if (needButtonActivation && firstSuccessfulSubmitButton) { |
| firstSuccessfulSubmitButton->setActivatedSubmit(false); |
| } |
| |
| m_doingsubmit = m_insubmit = false; |
| } |
| |
| void HTMLFormElementImpl::reset( ) |
| { |
| Frame *frame = getDocument()->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 HTMLFormElementImpl::parseMappedAttribute(MappedAttributeImpl *attr) |
| { |
| if (attr->name() == actionAttr) |
| { |
| bool oldURLWasSecure = formWouldHaveSecureSubmission(m_url); |
| m_url = khtml::parseURL(attr->value()); |
| bool newURLIsSecure = formWouldHaveSecureSubmission(m_url); |
| |
| if (m_attached && (oldURLWasSecure != newURLIsSecure)) |
| if (newURLIsSecure) |
| getDocument()->secureFormAdded(); |
| else |
| getDocument()->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) { |
| DOMString newNameAttr = attr->value(); |
| if (inDocument() && getDocument()->isHTMLDocument()) { |
| HTMLDocumentImpl *document = static_cast<HTMLDocumentImpl *>(getDocument()); |
| document->removeNamedItem(oldNameAttr); |
| document->addNamedItem(newNameAttr); |
| } |
| oldNameAttr = newNameAttr; |
| } else |
| HTMLElementImpl::parseMappedAttribute(attr); |
| } |
| |
| template<class T, size_t n> static void removeFromVector(Vector<T*, n> & 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 HTMLFormElementImpl::formElementIndex(HTMLGenericFormElementImpl *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 (NodeImpl *node = this; node; node = node->traverseNextNode(this)) { |
| if (node == e) |
| return i; |
| if (node->isHTMLElement() |
| && static_cast<HTMLElementImpl *>(node)->isGenericFormElement() |
| && static_cast<HTMLGenericFormElementImpl *>(node)->form() == this) |
| ++i; |
| } |
| } |
| return formElements.size(); |
| } |
| |
| void HTMLFormElementImpl::registerFormElement(HTMLGenericFormElementImpl* e) |
| { |
| DocumentImpl* doc = getDocument(); |
| if (e->isRadioButton() && !e->name().isEmpty()) { |
| HTMLGenericFormElementImpl* currentCheckedRadio = doc->checkedRadioButtonForGroup(e->name().impl(), 0); |
| if (currentCheckedRadio == e) |
| doc->removeRadioButtonGroup(e->name().impl(), 0); |
| if (e->isChecked()) |
| doc->radioButtonChecked(static_cast<HTMLInputElementImpl*>(e), this); |
| } |
| formElements.insert(formElementIndex(e), e); |
| } |
| |
| void HTMLFormElementImpl::removeFormElement(HTMLGenericFormElementImpl* e) |
| { |
| if (!e->name().isEmpty()) { |
| HTMLGenericFormElementImpl* currentCheckedRadio = getDocument()->checkedRadioButtonForGroup(e->name().impl(), this); |
| if (currentCheckedRadio == e) |
| getDocument()->removeRadioButtonGroup(e->name().impl(), this); |
| } |
| removeFromVector(formElements, e); |
| } |
| |
| bool HTMLFormElementImpl::isURLAttribute(AttributeImpl *attr) const |
| { |
| return attr->name() == actionAttr; |
| } |
| |
| void HTMLFormElementImpl::registerImgElement(HTMLImageElementImpl *e) |
| { |
| imgElements.append(e); |
| } |
| |
| void HTMLFormElementImpl::removeImgElement(HTMLImageElementImpl *e) |
| { |
| removeFromVector(imgElements, e); |
| } |
| |
| RefPtr<HTMLCollectionImpl> HTMLFormElementImpl::elements() |
| { |
| return RefPtr<HTMLCollectionImpl>(new HTMLFormCollectionImpl(this)); |
| } |
| |
| DOMString HTMLFormElementImpl::name() const |
| { |
| return getAttribute(nameAttr); |
| } |
| |
| void HTMLFormElementImpl::setName(const DOMString &value) |
| { |
| setAttribute(nameAttr, value); |
| } |
| |
| DOMString HTMLFormElementImpl::acceptCharset() const |
| { |
| return getAttribute(accept_charsetAttr); |
| } |
| |
| void HTMLFormElementImpl::setAcceptCharset(const DOMString &value) |
| { |
| setAttribute(accept_charsetAttr, value); |
| } |
| |
| DOMString HTMLFormElementImpl::action() const |
| { |
| return getAttribute(actionAttr); |
| } |
| |
| void HTMLFormElementImpl::setAction(const DOMString &value) |
| { |
| setAttribute(actionAttr, value); |
| } |
| |
| void HTMLFormElementImpl::setEnctype(const DOMString &value) |
| { |
| setAttribute(enctypeAttr, value); |
| } |
| |
| DOMString HTMLFormElementImpl::method() const |
| { |
| return getAttribute(methodAttr); |
| } |
| |
| void HTMLFormElementImpl::setMethod(const DOMString &value) |
| { |
| setAttribute(methodAttr, value); |
| } |
| |
| DOMString HTMLFormElementImpl::target() const |
| { |
| return getAttribute(targetAttr); |
| } |
| |
| void HTMLFormElementImpl::setTarget(const DOMString &value) |
| { |
| setAttribute(targetAttr, value); |
| } |
| |
| } // namespace |