CredentialTransformData.cpp   [plain text]


/*
 * Copyright (C) 2009 Google Inc. All rights reserved.
 * Copyright (C) 2012 Research In Motion Limited. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

 /*
 * This methods are based on Chromium codes in
 * Source/WebKit/chromium/src/WebPasswordFormUtils.cpp
 */

#include "config.h"
#include "CredentialTransformData.h"

#include "HTMLFormElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "KURL.h"
#include <wtf/Vector.h>
#include <wtf/text/WTFString.h>

namespace WebCore {

namespace {
// Maximum number of password fields we will observe before throwing our
// hands in the air and giving up with a given form.
static const size_t maxPasswords = 3;

// Helper method to clear url of unneeded parts.
KURL stripURL(const KURL& url)
{
    KURL strippedURL = url;
    strippedURL.setUser(String());
    strippedURL.setPass(String());
    strippedURL.setQuery(String());
    strippedURL.setFragmentIdentifier(String());
    return strippedURL;
}

// Helper method to determine which password is the main one, and which is
// an old password (e.g on a "make new password" form), if any.
bool locateSpecificPasswords(Vector<HTMLInputElement*>& passwords,
                             HTMLInputElement** password)
{
    ASSERT(password);

    switch (passwords.size()) {
    case 1:
        // Single password, easy.
        *password = passwords[0];
        break;
    case 2:
        if (passwords[0]->value() == passwords[1]->value())
            // Treat two identical passwords as a single password.
            *password = passwords[0];
        else {
            // Assume first is old password, second is new (no choice but to guess).
            *password = passwords[1];
        }
        break;
    case 3:
        if (passwords[0]->value() == passwords[1]->value()
            && passwords[0]->value() == passwords[2]->value()) {
            // All three passwords the same? Just treat as one and hope.
            *password = passwords[0];
        } else if (passwords[0]->value() == passwords[1]->value()) {
            // Two the same and one different -> old password is duplicated one.
            *password = passwords[2];
        } else if (passwords[1]->value() == passwords[2]->value())
            *password = passwords[1];
        else {
            // Three different passwords, or first and last match with middle
            // different. No idea which is which, so no luck.
            return false;
        }
        break;
    default:
        return false;
    }
    return true;
}

} // namespace

CredentialTransformData::CredentialTransformData(HTMLFormElement* form)
    : m_userNameElement(0)
    , m_passwordElement(0)
    , m_isValid(false)
{
    ASSERT(form);

    // Get the document URL
    KURL fullOrigin(ParsedURLString, form->document()->documentURI());

    // Calculate the canonical action URL
    String action = form->action();
    if (action.isNull())
        action = ""; // missing 'action' attribute implies current URL
    KURL fullAction = form->document()->completeURL(action);
    if (!fullAction.isValid())
        return;

    if (!findPasswordFormFields(form))
        return;

    m_url = stripURL(fullOrigin);
    m_action = stripURL(fullAction);
    m_protectionSpace = ProtectionSpace(m_url.host(), m_url.port(), ProtectionSpaceServerHTTP, "Form", ProtectionSpaceAuthenticationSchemeHTMLForm);
    m_credential = Credential(m_userNameElement->value(), m_passwordElement->value(), CredentialPersistencePermanent);

    m_isValid = true;
}

CredentialTransformData::CredentialTransformData(const KURL& url, const ProtectionSpace& protectionSpace, const Credential& credential)
    : m_url(url)
    , m_protectionSpace(protectionSpace)
    , m_credential(credential)
    , m_userNameElement(0)
    , m_passwordElement(0)
    , m_isValid(true)
{
}

KURL CredentialTransformData::url() const
{
    if (!m_isValid)
        return KURL();

    if (m_protectionSpace.authenticationScheme() == ProtectionSpaceAuthenticationSchemeHTMLForm)
        return m_action;
    return m_url;
}

Credential CredentialTransformData::credential() const
{
    if (m_credential.isEmpty() && m_userNameElement && m_passwordElement)
        return m_credential = Credential(m_userNameElement->value(), m_passwordElement->value(), CredentialPersistencePermanent);
    return m_credential;
}

void CredentialTransformData::setCredential(const Credential& credential)
{
    if (!m_isValid)
        return;

    m_credential = credential;
    m_userNameElement->setValue(credential.user());
    m_passwordElement->setValue(credential.password());
}

bool CredentialTransformData::findPasswordFormFields(HTMLFormElement* form)
{
    ASSERT(form);

    int firstPasswordIndex = 0;
    // First, find the password fields and activated submit button.
    const Vector<FormAssociatedElement*>& formElements = form->associatedElements();
    Vector<HTMLInputElement*> passwords;
    for (size_t i = 0; i < formElements.size(); i++) {
        if (!formElements[i]->isFormControlElement())
            continue;
        HTMLFormControlElement* formElement = static_cast<HTMLFormControlElement*>(formElements[i]);
        if (!formElement->hasLocalName(HTMLNames::inputTag))
            continue;

        HTMLInputElement* inputElement = formElement->toInputElement();
        if (!inputElement->isEnabledFormControl())
            continue;

        if ((passwords.size() < maxPasswords)
            && inputElement->isPasswordField()
            && inputElement->shouldAutocomplete()) {
            if (passwords.isEmpty())
                firstPasswordIndex = i;
            passwords.append(inputElement);
        }
    }

    if (!passwords.isEmpty()) {
        // Then, search backwards for the username field.
        for (int i = firstPasswordIndex - 1; i >= 0; i--) {
            if (!formElements[i]->isFormControlElement())
                continue;
            HTMLFormControlElement* formElement = static_cast<HTMLFormControlElement*>(formElements[i]);
            if (!formElement->hasLocalName(HTMLNames::inputTag))
                continue;

            HTMLInputElement* inputElement = formElement->toInputElement();
            if (!inputElement->isEnabledFormControl())
                continue;

            // Various input types such as text, url, email can be a username field.
            if ((inputElement->isTextField() && !inputElement->isPasswordField())
                && (inputElement->shouldAutocomplete())) {
                m_userNameElement = inputElement;
                break;
            }
        }
    }
    if (!m_userNameElement)
        return false;

    if (!locateSpecificPasswords(passwords, &(m_passwordElement)))
        return false;
    return true;
}

} // namespace WebCore