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 | */ |
90 | static 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 | |
98 | static 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 | |
107 | static 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 | */ |
119 | static 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 | |
129 | class KWebWallet::KWebWalletPrivate |
130 | { |
131 | public: |
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 | |
158 | KWebWallet::KWebWalletPrivate::KWebWalletPrivate(KWebWallet *parent) |
159 | :wid (0), q(parent) |
160 | { |
161 | } |
162 | |
163 | KWebWallet::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 | |
211 | void 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 | |
237 | void 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 | |
299 | void 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 | |
317 | void 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 | |
329 | void 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 | |
371 | void KWebWallet::KWebWalletPrivate::_k_walletClosed() |
372 | { |
373 | if (wallet) |
374 | wallet.take()->deleteLater(); |
375 | |
376 | emit q->walletClosed(); |
377 | } |
378 | |
379 | KWebWallet::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 | |
394 | KWebWallet::~KWebWallet() |
395 | { |
396 | delete d; |
397 | } |
398 | |
399 | KWebWallet::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 | |
419 | void 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 | |
465 | static 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 | |
478 | void 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 | |
518 | void KWebWallet::removeFormData(QWebFrame *frame, bool recursive) |
519 | { |
520 | if (frame) |
521 | removeFormDataFromCache(formsWithCachedData(frame, recursive)); |
522 | } |
523 | |
524 | void KWebWallet::removeFormData(const WebFormList &forms) |
525 | { |
526 | d->pendingRemoveRequests << forms; |
527 | removeFormDataFromCache(forms); |
528 | } |
529 | |
530 | void KWebWallet::acceptSaveFormDataRequest(const QString &key) |
531 | { |
532 | saveFormDataToCache(key); |
533 | } |
534 | |
535 | void KWebWallet::rejectSaveFormDataRequest(const QString & key) |
536 | { |
537 | d->pendingSaveRequests.remove(key); |
538 | } |
539 | |
540 | void 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 | |
567 | KWebWallet::WebFormList KWebWallet::formsToFill(const KUrl &url) const |
568 | { |
569 | return d->pendingFillRequests.value(url).forms; |
570 | } |
571 | |
572 | KWebWallet::WebFormList KWebWallet::formsToSave(const QString &key) const |
573 | { |
574 | return d->pendingSaveRequests.value(key); |
575 | } |
576 | |
577 | bool KWebWallet::hasCachedFormData(const WebForm &form) const |
578 | { |
579 | return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), |
580 | KWallet::Wallet::FormDataFolder(), |
581 | walletKey(form)); |
582 | } |
583 | |
584 | void 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 | |
599 | void KWebWallet::saveFormDataToCache(const QString &key) |
600 | { |
601 | if (d->wallet) { |
602 | d->saveDataToCache(key); |
603 | return; |
604 | } |
605 | d->openWallet(); |
606 | } |
607 | |
608 | void 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 | |