1
mirror of https://invent.kde.org/network/falkon.git synced 2024-09-22 18:22:10 +02:00
falkonOfficial/src/lib/autofill/pageformcompleter.cpp

260 lines
7.7 KiB
C++
Raw Normal View History

/* ============================================================
* QupZilla - WebKit based browser
* Copyright (C) 2013 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "pageformcompleter.h"
#include <QWebPage>
#include <QWebFrame>
#include <QWebElement>
PageFormCompleter::PageFormCompleter(QWebPage* page)
: m_page(page)
{
}
PageFormData PageFormCompleter::extractFormData(const QByteArray &postData) const
{
QString usernameName;
QString usernameValue;
QString passwordName;
QString passwordValue;
QWebElementCollection allForms;
QWebElement foundForm;
QByteArray data = convertWebKitFormBoundaryIfNecessary(postData);
PageFormData formData = {false, QString(), QString(), data};
/* Find all form elements in page (in all frames) */
QList<QWebFrame*> frames;
frames.append(m_page->mainFrame());
while (!frames.isEmpty()) {
QWebFrame* frame = frames.takeFirst();
allForms.append(frame->findAllElements("form"));
frames += frame->childFrames();
}
/* Find form that contains password value sent in data */
foreach(const QWebElement & formElement, allForms) {
foreach(const QWebElement & inputElement, formElement.findAll("input[type=\"password\"]")) {
passwordName = inputElement.attribute("name");
passwordValue = getValueFromData(data, inputElement);
if (!passwordValue.isEmpty() && dataContains(data, passwordName)) {
foundForm = formElement;
break;
}
}
if (!foundForm.isNull()) {
break;
}
}
if (foundForm.isNull()) {
return formData;
}
/* Try to find username (or email) field in the form. */
bool found = false;
QStringList selectors;
selectors << "input[type=\"text\"][name*=\"user\"]"
<< "input[type=\"text\"][name*=\"name\"]"
<< "input[type=\"text\"]"
<< "input[type=\"email\"]"
<< "input:not([type=\"hidden\"])";
foreach(const QString & selector, selectors) {
foreach(const QWebElement & element, foundForm.findAll(selector)) {
usernameName = element.attribute("name");
usernameValue = getValueFromData(data, element);
if (!usernameName.isEmpty() && !usernameValue.isEmpty()) {
found = true;
break;
}
}
if (found) {
break;
}
}
if (!found) {
return formData;
}
formData.found = true;
formData.username = usernameValue;
formData.password = passwordValue;
return formData;
}
void PageFormCompleter::completePage(const QByteArray &data) const
{
const QueryItems &queryItems = createQueryItems(data);
/* Input types that are being completed */
QStringList inputTypes;
inputTypes << "text" << "password" << "email";
/* Find all input elements in the page */
QWebElementCollection inputs;
QList<QWebFrame*> frames;
frames.append(m_page->mainFrame());
while (!frames.isEmpty()) {
QWebFrame* frame = frames.takeFirst();
inputs.append(frame->findAllElements("input"));
frames += frame->childFrames();
}
for (int i = 0; i < queryItems.count(); i++) {
const QString &key = queryItems.at(i).first;
const QString &value = queryItems.at(i).second;
/* Is it really necessary?
key = QUrl::fromEncoded(key.toUtf8()).toString();
value = QUrl::fromEncoded(value.toUtf8()).toString();
*/
for (int i = 0; i < inputs.count(); i++) {
QWebElement element = inputs.at(i);
const QString &typeAttr = element.attribute("type");
if (!inputTypes.contains(typeAttr) && !typeAttr.isEmpty()) {
continue;
}
if (key == element.attribute("name")) {
element.setAttribute("value", value);
}
}
}
}
bool PageFormCompleter::dataContains(const QByteArray &data, const QString &attributeName) const
{
const QueryItems &queryItems = createQueryItems(data);
for (int i = 0; i < queryItems.count(); i++) {
const QueryItem &item = queryItems.at(i);
if (item.first == attributeName) {
return !item.second.isEmpty();
}
}
return false;
}
QString PageFormCompleter::getValueFromData(const QByteArray &data, QWebElement element) const
{
QString name = element.attribute("name");
if (name.isEmpty()) {
return QString();
}
QString value = element.evaluateJavaScript("this.value").toString();
if (!value.isEmpty()) {
return value;
}
const QueryItems &queryItems = createQueryItems(data);
for (int i = 0; i < queryItems.count(); i++) {
const QueryItem &item = queryItems.at(i);
if (item.first == name) {
value = item.second.toUtf8();
}
}
return value;
}
QByteArray PageFormCompleter::convertWebKitFormBoundaryIfNecessary(const QByteArray &data) const
{
/* Sometimes, data are passed in this format:
*
* ------WebKitFormBoundary0bBp3bFMdGwqanMp
* Content-Disposition: form-data; name="name-of-attribute"
*
* value-of-attribute
* ------WebKitFormBoundary0bBp3bFMdGwqanMp--
*
* So this function converts this format into name=value& format
*/
if (!data.contains(QByteArray("------WebKitFormBoundary"))) {
return data;
}
QByteArray formatedData;
QRegExp rx("name=\"(.*)------WebKitFormBoundary");
rx.setMinimal(true);
int pos = 0;
while ((pos = rx.indexIn(data, pos)) != -1) {
QString string = rx.cap(1);
pos += rx.matchedLength();
int endOfAttributeName = string.indexOf(QLatin1Char('"'));
if (endOfAttributeName == -1) {
continue;
}
QString attrName = string.left(endOfAttributeName);
QString attrValue = string.mid(endOfAttributeName + 1).trimmed().remove(QLatin1Char('\n'));
if (attrName.isEmpty() || attrValue.isEmpty()) {
continue;
}
formatedData.append(attrName + "=" + attrValue + "&");
}
return formatedData;
}
PageFormCompleter::QueryItems PageFormCompleter::createQueryItems(const QByteArray &data) const
{
/* Why not to use encodedQueryItems = QByteArrays ?
* Because we need to filter "+" characters that must be spaces
* (not real "+" characters "%2B")
*
* DO NOT TOUCH! It works now with both Qt 4 & Qt 5 ...
*/
#if QT_VERSION >= 0x050000
QueryItems arguments = QUrlQuery(QUrl::fromEncoded("http://foo.com/?" + data)).queryItems();
#else
QByteArray dataCopy = data;
dataCopy.replace('+', ' ');
QueryItems arguments = QUrl::fromEncoded("http://foo.com/?" + dataCopy).queryItems();
#endif
#if QT_VERSION >= 0x050000
for (int i = 0; i < arguments.count(); i++) {
arguments[i].first.replace(QLatin1Char('+'), QLatin1Char(' '));
arguments[i].second.replace(QLatin1Char('+'), QLatin1Char(' '));
}
#endif
return arguments;
}