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
David Rosca 60b2386a6e Initial port to QtWebEngine
This is first quick port to QtWebEngine, most of advanced features
are not working yet. Please read README.

For now, it will use separate profile directory as well as browser
session, that means you can use both QtWebEngine and QtWebKit versions
at the same time.
2015-01-27 11:01:52 +01:00

290 lines
8.4 KiB
C++

/* ============================================================
* QupZilla - WebKit based browser
* Copyright (C) 2013-2014 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 "qzregexp.h"
#include <QWebEnginePage>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif
PageFormCompleter::PageFormCompleter()
: m_page(0)
, m_frame(0)
{
}
PageFormData PageFormCompleter::extractFormData(QWebEnginePage* page, const QByteArray &postData)
{
m_page = page;
return extractFormData(postData);
}
PageFormData PageFormCompleter::extractFormData(QWebEngineFrame* frame, const QByteArray &postData)
{
m_frame = frame;
return extractFormData(postData);
}
bool PageFormCompleter::completeFormData(QWebEnginePage* page, const QByteArray &data)
{
m_page = page;
return completeFormData(data);
}
bool PageFormCompleter::completeFormData(QWebEngineFrame* frame, const QByteArray &data)
{
m_frame = frame;
return completeFormData(data);
}
PageFormData PageFormCompleter::extractFormData(const QByteArray &postData) const
{
QString usernameValue;
QString passwordValue;
QByteArray data = convertWebKitFormBoundaryIfNecessary(postData);
PageFormData formData = {QString(), QString(), data};
if (data.isEmpty() || !data.contains('=')) {
return formData;
}
const QueryItems queryItems = createQueryItems(data);
if (queryItems.isEmpty()) {
return formData;
}
#if QTWEBENGINE_DISABLED
const QWebElementCollection allForms = getAllElementsFromPage("form");
// Find form that contains password value sent in data
foreach (const QWebElement &formElement, allForms) {
bool found = false;
const QWebElementCollection inputs = formElement.findAll("input[type=\"password\"]");
foreach (QWebElement inputElement, inputs) {
const QString passName = inputElement.attribute("name");
const QString passValue = inputElement.evaluateJavaScript("this.value").toString();
if (queryItemsContains(queryItems, passName, passValue)) {
// Set passwordValue if not empty (to make it possible extract forms without username field)
passwordValue = passValue;
const QueryItem item = findUsername(formElement);
if (queryItemsContains(queryItems, item.first, item.second)) {
usernameValue = item.second;
found = true;
break;
}
}
}
if (found) {
break;
}
}
// It is necessary only to find password, as there may be form without username field
if (passwordValue.isEmpty()) {
return formData;
}
formData.username = usernameValue;
formData.password = passwordValue;
#endif
return formData;
}
// Returns if any data was actually filled in page
bool PageFormCompleter::completeFormData(const QByteArray &data) const
{
bool completed = false;
const QueryItems queryItems = createQueryItems(data);
// Input types that are being completed
QStringList inputTypes;
inputTypes << "text" << "password" << "email";
#if QTWEBENGINE_DISABLED
// Find all input elements in the page
const QWebElementCollection inputs = getAllElementsFromPage("input");
for (int i = 0; i < queryItems.count(); i++) {
const QString key = queryItems.at(i).first;
const QString value = queryItems.at(i).second;
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")) {
completed = true;
element.setAttribute("value", value);
}
}
}
#endif
return completed;
}
bool PageFormCompleter::queryItemsContains(const QueryItems &queryItems, const QString &attributeName,
const QString &attributeValue) const
{
if (attributeName.isEmpty() || attributeValue.isEmpty()) {
return false;
}
for (int i = 0; i < queryItems.count(); i++) {
const QueryItem item = queryItems.at(i);
if (item.first == attributeName) {
return item.second == attributeValue;
}
}
return false;
}
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;
QzRegExp 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::QueryItem PageFormCompleter::findUsername(const QWebElement &form) const
{
#if QTWEBENGINE_DISABLED
// Try to find username (or email) field in the form.
QStringList selectors;
selectors << "input[type=\"text\"][name*=\"user\"]"
<< "input[type=\"text\"][name*=\"name\"]"
<< "input[type=\"text\"]"
<< "input[type=\"email\"]"
<< "input:not([type=\"hidden\"][type=\"password\"])";
foreach (const QString &selector, selectors) {
const QWebElementCollection inputs = form.findAll(selector);
foreach (QWebElement element, inputs) {
const QString name = element.attribute("name");
const QString value = element.evaluateJavaScript("this.value").toString();
if (!name.isEmpty() && !value.isEmpty()) {
QueryItem item;
item.first = name;
item.second = value;
return item;
}
}
}
#endif
return QueryItem();
}
PageFormCompleter::QueryItems PageFormCompleter::createQueryItems(QByteArray data) const
{
// QUrlQuery/QUrl never encodes/decodes + and spaces
data.replace('+', ' ');
#if QT_VERSION >= 0x050000
QUrlQuery query;
query.setQuery(data);
QueryItems arguments = query.queryItems(QUrl::FullyDecoded);
#else
QueryItems arguments = QUrl::fromEncoded("http://foo.com/?" + data).queryItems();
#endif
return arguments;
}
#if QTWEBENGINE_DISABLED
QWebElementCollection PageFormCompleter::getAllElementsFromPage(const QString &selector) const
{
QWebElementCollection list;
if (!m_page && !m_frame)
return list;
if (m_frame)
return m_frame->findAllElements(selector);
if (!m_page->mainFrame())
return list;
QList<QWebFrame*> frames;
frames.append(m_page->mainFrame());
while (!frames.isEmpty()) {
QWebFrame* frame = frames.takeFirst();
if (frame && !frame->documentElement().isNull()) {
list.append(frame->findAllElements(selector));
frames += frame->childFrames();
}
}
return list;
}
#endif