1/*
2 * This file is part of the KDE project.
3 *
4 * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22
23#include "kwebwallet.h"
24
25#include <kwallet.h>
26#include <kdebug.h>
27
28#include <QtCore/QSet>
29#include <QtCore/QHash>
30#include <QtCore/QFile>
31#include <QtCore/QWeakPointer>
32#include <QtCore/QScopedPointer>
33#include <QtWebKit/QWebPage>
34#include <QtWebKit/QWebFrame>
35#include <QtWebKit/QWebElement>
36#include <QtWebKit/QWebElementCollection>
37#include <qwindowdefs.h>
38
39#define QL1S(x) QLatin1String(x)
40#define QL1C(x) QLatin1Char(x)
41
42// Javascript used to extract/set data from <form> elements.
43#define FILLABLE_FORM_ELEMENT_EXTRACTOR_JS "(function (){ \
44 var forms; \
45 var formList = document.forms; \
46 if (formList.length > 0) { \
47 forms = new Array; \
48 for (var i = 0; i < formList.length; ++i) { \
49 var inputList = formList[i].elements; \
50 if (inputList.length < 1) { \
51 continue; \
52 } \
53 var formObject = new Object; \
54 formObject.name = formList[i].name; \
55 if (typeof(formObject.name) != 'string') { \
56 formObject.name = String(formList[i].id); \
57 } \
58 formObject.index = i; \
59 formObject.elements = new Array; \
60 for (var j = 0; j < inputList.length; ++j) { \
61 if (inputList[j].type != 'text' && inputList[j].type != 'email' && inputList[j].type != 'password') { \
62 continue; \
63 } \
64 if (inputList[j].disabled || inputList[j].autocomplete == 'off') { \
65 continue; \
66 } \
67 var element = new Object; \
68 element.name = inputList[j].name; \
69 if (typeof(element.name) != 'string' ) { \
70 element.name = String(inputList[j].id); \
71 } \
72 element.value = String(inputList[j].value); \
73 element.type = String(inputList[j].type); \
74 element.readonly = Boolean(inputList[j].readOnly); \
75 formObject.elements.push(element); \
76 } \
77 if (formObject.elements.length > 0) { \
78 forms.push(formObject); \
79 } \
80 } \
81 } \
82 return forms; \
83}())"
84
85
86/**
87 * Creates key used to store and retrieve form data.
88 *
89 */
90static QString walletKey(KWebWallet::WebForm form)
91{
92 QString key = form.url.toString(QUrl::RemoveQuery|QUrl::RemoveFragment);
93 key += QL1C('#');
94 key += form.name;
95 return key;
96}
97
98static void collectAllChildFrames(QWebFrame* frame, QList<QWebFrame*>& list)
99{
100 list << frame->childFrames();
101 QListIterator<QWebFrame*> it(frame->childFrames());
102 while (it.hasNext()) {
103 collectAllChildFrames(it.next(), list);
104 }
105}
106
107static QUrl urlForFrame(QWebFrame* frame)
108{
109 return (frame->url().isEmpty() ? frame->baseUrl().resolved(frame->url()) : frame->url());
110}
111
112/*
113 Returns the top most window associated with widget.
114
115 Unlike QWidget::window(), this function does its best to find and return the
116 main application window associated with a given widget. It will not stop when
117 it encounters a dialog which likely "has (or could have) a window-system frame".
118*/
119static QWidget* topLevelWindow(QObject* obj)
120{
121 QWebPage *page = qobject_cast<QWebPage*>(obj);
122 QWidget* widget = (page ? page->view() : qobject_cast<QWidget*>(page));
123 while (widget && widget->parentWidget()) {
124 widget = widget->parentWidget();
125 }
126 return (widget ? widget->window() : 0);
127}
128
129class KWebWallet::KWebWalletPrivate
130{
131public:
132 struct FormsData
133 {
134 QWeakPointer<QWebFrame> frame;
135 KWebWallet::WebFormList forms;
136 };
137
138 KWebWalletPrivate(KWebWallet* parent);
139 KWebWallet::WebFormList parseFormData(QWebFrame* frame, bool fillform = true, bool ignorepasswd = false);
140 void fillDataFromCache(KWebWallet::WebFormList &formList);
141 void saveDataToCache(const QString &key);
142 void removeDataFromCache(const WebFormList &formList);
143 void openWallet();
144
145 // Private slots...
146 void _k_openWalletDone(bool);
147 void _k_walletClosed();
148
149 WId wid;
150 KWebWallet *q;
151 QScopedPointer<KWallet::Wallet> wallet;
152 KWebWallet::WebFormList pendingRemoveRequests;
153 QHash<KUrl, FormsData> pendingFillRequests;
154 QHash<QString, KWebWallet::WebFormList> pendingSaveRequests;
155 QSet<KUrl> confirmSaveRequestOverwrites;
156};
157
158KWebWallet::KWebWalletPrivate::KWebWalletPrivate(KWebWallet *parent)
159 :wid (0), q(parent)
160{
161}
162
163KWebWallet::WebFormList KWebWallet::KWebWalletPrivate::parseFormData(QWebFrame *frame, bool fillform, bool ignorepasswd)
164{
165 Q_ASSERT(frame);
166
167 KWebWallet::WebFormList list;
168
169 const QVariant result (frame->evaluateJavaScript(QL1S(FILLABLE_FORM_ELEMENT_EXTRACTOR_JS)));
170 const QVariantList results (result.toList());
171 Q_FOREACH (const QVariant &formVariant, results) {
172 QVariantMap map = formVariant.toMap();
173 KWebWallet::WebForm form;
174 form.url = urlForFrame(frame);
175 form.name = map[QL1S("name")].toString();
176 form.index = map[QL1S("index")].toString();
177 bool formHasPasswords = false;
178 const QVariantList elements = map[QL1S("elements")].toList();
179 QList<KWebWallet::WebForm::WebField> inputFields;
180 Q_FOREACH (const QVariant &element, elements) {
181 QVariantMap elementMap (element.toMap());
182 const QString name (elementMap[QL1S("name")].toString());
183 const QString value (ignorepasswd ? QString() : elementMap[QL1S("value")].toString());
184 if (name.isEmpty()) {
185 continue;
186 }
187 if (fillform && elementMap[QL1S("readonly")].toBool()) {
188 continue;
189 }
190 if (elementMap[QL1S("type")].toString().compare(QL1S("password"), Qt::CaseInsensitive) == 0) {
191 if (!fillform && value.isEmpty())
192 continue;
193 formHasPasswords = true;
194 }
195 inputFields.append(qMakePair(name, value));
196 }
197
198 // Only add the input fields on form save requests...
199 if (formHasPasswords || fillform) {
200 form.fields = inputFields;
201 }
202
203 // Add the form to the list if we are saving it or it has cached data.
204 if ((fillform && q->hasCachedFormData(form)) || (!fillform && !form.fields.isEmpty()))
205 list << form;
206 }
207
208 return list;
209}
210
211void KWebWallet::KWebWalletPrivate::fillDataFromCache(KWebWallet::WebFormList &formList)
212{
213 if (!wallet) {
214 kWarning(800) << "Unable to retrieve form data from wallet";
215 return;
216 }
217
218 QString lastKey;
219 QMap<QString, QString> cachedValues;
220 QMutableListIterator <WebForm> formIt (formList);
221
222 while (formIt.hasNext()) {
223 KWebWallet::WebForm &form = formIt.next();
224 const QString key (walletKey(form));
225 if (key != lastKey && wallet->readMap(key, cachedValues) != 0) {
226 kWarning(800) << "Unable to read form data for key:" << key;
227 continue;
228 }
229
230 for (int i = 0, count = form.fields.count(); i < count; ++i) {
231 form.fields[i].second = cachedValues.value(form.fields[i].first);
232 }
233 lastKey = key;
234 }
235}
236
237void KWebWallet::KWebWalletPrivate::saveDataToCache(const QString &key)
238{
239 // Make sure the specified keys exists before acting on it. See BR# 270209.
240 if (!pendingSaveRequests.contains(key)) {
241 return;
242 }
243
244 bool success = false;
245 const QUrl url = pendingSaveRequests.value(key).first().url;
246
247 if (wallet) {
248 int count = 0;
249 const KWebWallet::WebFormList list = pendingSaveRequests.value(key);
250 QListIterator<KWebWallet::WebForm> formIt (list);
251
252 while (formIt.hasNext()) {
253 QMap<QString, QString> values, storedValues;
254 const KWebWallet::WebForm form = formIt.next();
255 const QString accessKey = walletKey(form);
256 if (confirmSaveRequestOverwrites.contains(url)) {
257 confirmSaveRequestOverwrites.remove(url);
258 const int status = wallet->readMap(accessKey, storedValues);
259 if (status == 0 && storedValues.count()) {
260 QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields);
261 while (fieldIt.hasNext()) {
262 const KWebWallet::WebForm::WebField field = fieldIt.next();
263 if (storedValues.contains(field.first) &&
264 storedValues.value(field.first) != field.second) {
265 emit q->saveFormDataRequested(key, url);
266 return;
267 }
268 }
269 // If we got here it means the new credential is exactly
270 // the same as the one already cached ; so skip the
271 // re-saving part...
272 success = true;
273 continue;
274 }
275 }
276 QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields);
277 while (fieldIt.hasNext()) {
278 const KWebWallet::WebForm::WebField field = fieldIt.next();
279 values.insert(field.first, field.second);
280 }
281
282 if (wallet->writeMap(accessKey, values) == 0)
283 count++;
284 else
285 kWarning(800) << "Unable to write form data to wallet";
286 }
287
288 if (list.isEmpty() || count > 0)
289 success = true;
290
291 pendingSaveRequests.remove(key);
292 } else {
293 kWarning(800) << "NULL KWallet instance!";
294 }
295
296 emit q->saveFormDataCompleted(url, success);
297}
298
299void KWebWallet::KWebWalletPrivate::openWallet()
300{
301 if (!wallet.isNull()) {
302 return;
303 }
304
305 wallet.reset(KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(),
306 wid, KWallet::Wallet::Asynchronous));
307
308 if (wallet.isNull()) {
309 return;
310 }
311
312 connect(wallet.data(), SIGNAL(walletOpened(bool)), q, SLOT(_k_openWalletDone(bool)));
313 connect(wallet.data(), SIGNAL(walletClosed()), q, SLOT(_k_walletClosed()));
314}
315
316
317void KWebWallet::KWebWalletPrivate::removeDataFromCache(const WebFormList &formList)
318{
319 if (!wallet) {
320 kWarning(800) << "NULL KWallet instance!";
321 return;
322 }
323
324 QListIterator<WebForm> formIt (formList);
325 while (formIt.hasNext())
326 wallet->removeEntry(walletKey(formIt.next()));
327}
328
329void KWebWallet::KWebWalletPrivate::_k_openWalletDone(bool ok)
330{
331 Q_ASSERT (wallet);
332
333 if (ok &&
334 (wallet->hasFolder(KWallet::Wallet::FormDataFolder()) ||
335 wallet->createFolder(KWallet::Wallet::FormDataFolder())) &&
336 wallet->setFolder(KWallet::Wallet::FormDataFolder())) {
337
338 // Do pending fill requests...
339 if (!pendingFillRequests.isEmpty()) {
340 KUrl::List urlList;
341 QMutableHashIterator<KUrl, FormsData> requestIt (pendingFillRequests);
342 while (requestIt.hasNext()) {
343 requestIt.next();
344 KWebWallet::WebFormList list = requestIt.value().forms;
345 fillDataFromCache(list);
346 q->fillWebForm(requestIt.key(), list);
347 }
348
349 pendingFillRequests.clear();
350 }
351
352 // Do pending save requests...
353 if (!pendingSaveRequests.isEmpty()) {
354 QListIterator<QString> keysIt (pendingSaveRequests.keys());
355 while (keysIt.hasNext())
356 saveDataToCache(keysIt.next());
357 }
358
359 // Do pending remove requests...
360 if (!pendingRemoveRequests.isEmpty()) {
361 removeDataFromCache(pendingRemoveRequests);
362 pendingRemoveRequests.clear();
363 }
364 } else {
365 // Delete the wallet if opening the wallet failed or we were unable
366 // to change to the folder we wanted to change to.
367 delete wallet.take();
368 }
369}
370
371void KWebWallet::KWebWalletPrivate::_k_walletClosed()
372{
373 if (wallet)
374 wallet.take()->deleteLater();
375
376 emit q->walletClosed();
377}
378
379KWebWallet::KWebWallet(QObject *parent, WId wid)
380 :QObject(parent), d(new KWebWalletPrivate(this))
381{
382 if (!wid) {
383 // If wid is 0, make a best effort attempt to discern it from our
384 // parent object.
385 QWidget* widget = topLevelWindow(parent);
386 if (widget) {
387 wid = widget->winId();
388 }
389 }
390
391 d->wid = wid;
392}
393
394KWebWallet::~KWebWallet()
395{
396 delete d;
397}
398
399KWebWallet::WebFormList KWebWallet::formsWithCachedData(QWebFrame* frame, bool recursive) const
400{
401 WebFormList list;
402
403 if (frame) {
404 list << d->parseFormData(frame);
405
406 if (recursive) {
407 QList<QWebFrame*> childFrameList;
408 collectAllChildFrames(frame, childFrameList);
409 QListIterator <QWebFrame *> framesIt (childFrameList);
410 while (framesIt.hasNext()) {
411 list << d->parseFormData(framesIt.next());
412 }
413 }
414 }
415
416 return list;
417}
418
419void KWebWallet::fillFormData(QWebFrame *frame, bool recursive)
420{
421 if (!frame)
422 return;
423
424 KUrl::List urlList;
425 WebFormList formsList = d->parseFormData(frame);
426 if (!formsList.isEmpty()) {
427 const QUrl url (urlForFrame(frame));
428 if (d->pendingFillRequests.contains(url)) {
429 kWarning(800) << "Duplicate request rejected!";
430 } else {
431 KWebWalletPrivate::FormsData data;
432 data.frame = QWeakPointer<QWebFrame>(frame);
433 data.forms << formsList;
434 d->pendingFillRequests.insert(url, data);
435 urlList << url;
436 }
437 }
438
439 if (recursive) {
440 QList<QWebFrame*> childFrameList;
441 collectAllChildFrames(frame, childFrameList);
442 QListIterator<QWebFrame*> frameIt (childFrameList);
443 while (frameIt.hasNext()) {
444 QWebFrame *childFrame = frameIt.next();
445 formsList = d->parseFormData(childFrame);
446 if (formsList.isEmpty())
447 continue;
448 const QUrl url (childFrame->url());
449 if (d->pendingFillRequests.contains(url)) {
450 kWarning(800) << "Duplicate request rejected!!!";
451 } else {
452 KWebWalletPrivate::FormsData data;
453 data.frame = QWeakPointer<QWebFrame>(childFrame);
454 data.forms << formsList;
455 d->pendingFillRequests.insert(url, data);
456 urlList << url;
457 }
458 }
459 }
460
461 if (!urlList.isEmpty())
462 fillFormDataFromCache(urlList);
463}
464
465static void createSaveKeyFor(QWebFrame* frame, QString* key)
466{
467 QUrl frameUrl(urlForFrame(frame));
468 frameUrl.setPassword(QString());
469 frameUrl.setPassword(QString());
470
471 QString keyStr = frameUrl.toString();
472 if (!frame->frameName().isEmpty())
473 keyStr += frame->frameName();
474
475 *key = QString::number(qHash(keyStr), 16);
476}
477
478void KWebWallet::saveFormData(QWebFrame *frame, bool recursive, bool ignorePasswordFields)
479{
480 if (!frame)
481 return;
482
483 QString key;
484 createSaveKeyFor(frame, &key);
485 if (d->pendingSaveRequests.contains(key))
486 return;
487
488 WebFormList list = d->parseFormData(frame, false, ignorePasswordFields);
489 if (recursive) {
490 QList<QWebFrame*> childFrameList;
491 collectAllChildFrames(frame, childFrameList);
492 QListIterator<QWebFrame*> frameIt (childFrameList);
493 while (frameIt.hasNext())
494 list << d->parseFormData(frameIt.next(), false, ignorePasswordFields);
495 }
496
497 if (list.isEmpty())
498 return;
499
500 d->pendingSaveRequests.insert(key, list);
501
502 QMutableListIterator<WebForm> it (list);
503 while (it.hasNext()) {
504 const WebForm form (it.next());
505 if (hasCachedFormData(form))
506 it.remove();
507 }
508
509 if (list.isEmpty()) {
510 d->confirmSaveRequestOverwrites.insert(urlForFrame(frame));
511 saveFormDataToCache(key);
512 return;
513 }
514
515 emit saveFormDataRequested(key, urlForFrame(frame));
516}
517
518void KWebWallet::removeFormData(QWebFrame *frame, bool recursive)
519{
520 if (frame)
521 removeFormDataFromCache(formsWithCachedData(frame, recursive));
522}
523
524void KWebWallet::removeFormData(const WebFormList &forms)
525{
526 d->pendingRemoveRequests << forms;
527 removeFormDataFromCache(forms);
528}
529
530void KWebWallet::acceptSaveFormDataRequest(const QString &key)
531{
532 saveFormDataToCache(key);
533}
534
535void KWebWallet::rejectSaveFormDataRequest(const QString & key)
536{
537 d->pendingSaveRequests.remove(key);
538}
539
540void KWebWallet::fillWebForm(const KUrl &url, const KWebWallet::WebFormList &forms)
541{
542 QWeakPointer<QWebFrame> frame = d->pendingFillRequests.value(url).frame;
543 if (!frame)
544 return;
545
546 QString script;
547 bool wasFilled = false;
548
549 Q_FOREACH (const KWebWallet::WebForm& form, forms) {
550 Q_FOREACH(const KWebWallet::WebForm::WebField& field, form.fields) {
551 QString value = field.second;
552 value.replace(QL1C('\\'), QL1S("\\\\"));
553 script += QString::fromLatin1("if (document.forms[\"%1\"].elements[\"%2\"]) document.forms[\"%1\"].elements[\"%2\"].value=\"%3\";\n")
554 .arg((form.name.isEmpty() ? form.index : form.name))
555 .arg(field.first).arg(value);
556 }
557 }
558
559 if (!script.isEmpty()) {
560 wasFilled = true;
561 frame.data()->evaluateJavaScript(script);
562 }
563
564 emit fillFormRequestCompleted(wasFilled);
565}
566
567KWebWallet::WebFormList KWebWallet::formsToFill(const KUrl &url) const
568{
569 return d->pendingFillRequests.value(url).forms;
570}
571
572KWebWallet::WebFormList KWebWallet::formsToSave(const QString &key) const
573{
574 return d->pendingSaveRequests.value(key);
575}
576
577bool KWebWallet::hasCachedFormData(const WebForm &form) const
578{
579 return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
580 KWallet::Wallet::FormDataFolder(),
581 walletKey(form));
582}
583
584void KWebWallet::fillFormDataFromCache(const KUrl::List &urlList)
585{
586 if (d->wallet) {
587 QListIterator<KUrl> urlIt (urlList);
588 while (urlIt.hasNext()) {
589 const KUrl url = urlIt.next();
590 WebFormList list = formsToFill(url);
591 d->fillDataFromCache(list);
592 fillWebForm(url, list);
593 }
594 d->pendingFillRequests.clear();
595 }
596 d->openWallet();
597}
598
599void KWebWallet::saveFormDataToCache(const QString &key)
600{
601 if (d->wallet) {
602 d->saveDataToCache(key);
603 return;
604 }
605 d->openWallet();
606}
607
608void KWebWallet::removeFormDataFromCache(const WebFormList &forms)
609{
610 if (d->wallet) {
611 d->removeDataFromCache(forms);
612 d->pendingRemoveRequests.clear();
613 return;
614 }
615 d->openWallet();
616}
617
618#include "kwebwallet.moc"
619