KDEWebKit
kwebwallet.cpp
Go to the documentation of this file.
00001 /* 00002 * This file is part of the KDE project. 00003 * 00004 * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 00010 * 00011 * This library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Library General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Library General Public License 00017 * along with this library; see the file COPYING.LIB. If not, write to 00018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 * Boston, MA 02110-1301, USA. 00020 * 00021 */ 00022 00023 #include "kwebwallet.h" 00024 00025 #include <kwallet.h> 00026 #include <kdebug.h> 00027 00028 #include <QtCore/QSet> 00029 #include <QtCore/QHash> 00030 #include <QtCore/QFile> 00031 #include <QtCore/QWeakPointer> 00032 #include <QtCore/QScopedPointer> 00033 #include <QtWebKit/QWebPage> 00034 #include <QtWebKit/QWebFrame> 00035 #include <QtWebKit/QWebElement> 00036 #include <QtWebKit/QWebElementCollection> 00037 #include <qwindowdefs.h> 00038 00039 #define QL1S(x) QLatin1String(x) 00040 #define QL1C(x) QLatin1Char(x) 00041 00042 // The following form parsing JS code was adapted from Arora project. 00043 // See https://github.com/Arora/arora/blob/master/src/data/parseForms.js 00044 #define FORM_PARSING_JS "(function (){ \ 00045 var forms; \ 00046 var doc = (this.contentDocument ? this.contentDocument : document); \ 00047 var numForms = doc.forms.length; \ 00048 if (numForms > 0 ) { \ 00049 forms = new Array; \ 00050 for (var i = 0; i < numForms; ++i) { \ 00051 var form = document.forms[i]; \ 00052 if (form.method.toLowerCase() != 'post') \ 00053 continue; \ 00054 var formObject = new Object; \ 00055 formObject.name = form.name; \ 00056 formObject.index = i; \ 00057 var elements = new Array; \ 00058 var numElements = form.elements.length; \ 00059 for (var j = 0; j < numElements; ++j) { \ 00060 var e = form.elements[j]; \ 00061 var element = new Object; \ 00062 element.name = e.name; \ 00063 element.value = e.value; \ 00064 element.type = e.type; \ 00065 element.readonly = e.hasAttribute('readonly'); \ 00066 element.disabled = e.hasAttribute('disabled'); \ 00067 if (element.autocomplete != null) \ 00068 element.autocomplete = element.autocomplete.value; \ 00069 elements.push(element); \ 00070 } \ 00071 formObject.elements = elements; \ 00072 forms.push(formObject); \ 00073 } \ 00074 } \ 00075 return forms; \ 00076 }())" 00077 00078 00083 static QString walletKey(KWebWallet::WebForm form) 00084 { 00085 QString key = form.url.toString(QUrl::RemoveQuery|QUrl::RemoveFragment); 00086 key += QL1C('#'); 00087 key += form.name; 00088 return key; 00089 } 00090 00091 static void collectAllChildFrames(QWebFrame* frame, QList<QWebFrame*>& list) 00092 { 00093 list << frame->childFrames(); 00094 QListIterator<QWebFrame*> it(frame->childFrames()); 00095 while (it.hasNext()) { 00096 collectAllChildFrames(it.next(), list); 00097 } 00098 } 00099 00100 static QUrl urlForFrame(QWebFrame* frame) 00101 { 00102 return (frame->url().isEmpty() ? frame->baseUrl().resolved(frame->url()) : frame->url()); 00103 } 00104 00105 /* 00106 Returns the top most window associated with widget. 00107 00108 Unlike QWidget::window(), this function does its best to find and return the 00109 main application window associated with a given widget. It will not stop when 00110 it encounters a dialog which likely "has (or could have) a window-system frame". 00111 */ 00112 static QWidget* topLevelWindow(QObject* obj) 00113 { 00114 QWebPage *page = qobject_cast<QWebPage*>(obj); 00115 QWidget* widget = (page ? page->view() : qobject_cast<QWidget*>(page)); 00116 while (widget && widget->parentWidget()) { 00117 widget = widget->parentWidget(); 00118 } 00119 return (widget ? widget->window() : 0); 00120 } 00121 00122 class KWebWallet::KWebWalletPrivate 00123 { 00124 public: 00125 struct FormsData 00126 { 00127 QWeakPointer<QWebFrame> frame; 00128 KWebWallet::WebFormList forms; 00129 }; 00130 00131 KWebWalletPrivate(KWebWallet* parent); 00132 KWebWallet::WebFormList parseFormData(QWebFrame* frame, bool fillform = true, bool ignorepasswd = false); 00133 void fillDataFromCache(KWebWallet::WebFormList &formList); 00134 void saveDataToCache(const QString &key); 00135 void removeDataFromCache(const WebFormList &formList); 00136 void openWallet(); 00137 00138 // Private slots... 00139 void _k_openWalletDone(bool); 00140 void _k_walletClosed(); 00141 00142 WId wid; 00143 KWebWallet *q; 00144 QScopedPointer<KWallet::Wallet> wallet; 00145 KWebWallet::WebFormList pendingRemoveRequests; 00146 QHash<KUrl, FormsData> pendingFillRequests; 00147 QHash<QString, KWebWallet::WebFormList> pendingSaveRequests; 00148 QSet<KUrl> confirmSaveRequestOverwrites; 00149 }; 00150 00151 KWebWallet::KWebWalletPrivate::KWebWalletPrivate(KWebWallet *parent) 00152 :wid (0), q(parent) 00153 { 00154 } 00155 00156 KWebWallet::WebFormList KWebWallet::KWebWalletPrivate::parseFormData(QWebFrame *frame, bool fillform, bool ignorepasswd) 00157 { 00158 Q_ASSERT(frame); 00159 00160 KWebWallet::WebFormList list; 00161 00162 // Execute the javscript to obtain the necessary fields... 00163 QVariantList results = frame->evaluateJavaScript(QL1S(FORM_PARSING_JS)).toList(); 00164 Q_FOREACH (const QVariant &formVariant, results) { 00165 QVariantMap map = formVariant.toMap(); 00166 KWebWallet::WebForm form; 00167 form.url = urlForFrame(frame); 00168 form.name = map[QL1S("name")].toString(); 00169 form.index = map[QL1S("index")].toString(); 00170 bool formHasPasswords = false; 00171 const QVariantList elements = map[QL1S("elements")].toList(); 00172 QList<KWebWallet::WebForm::WebField> inputFields; 00173 Q_FOREACH (const QVariant &element, elements) { 00174 QVariantMap elementMap = element.toMap(); 00175 const QString name = elementMap[QL1S("name")].toString(); 00176 const QString value = (ignorepasswd ? QString() : elementMap[QL1S("value")].toString()); 00177 const QString type = elementMap[QL1S("type")].toString(); 00178 const bool isPasswdInput = (type.compare(QL1S("password"), Qt::CaseInsensitive) == 0); 00179 const bool isTextInput = (type.compare(QL1S("text"), Qt::CaseInsensitive) == 0); 00180 const bool autoCompleteOff = (elementMap[QL1S("autocomplete")].toString().compare(QL1S("off"), Qt::CaseInsensitive) == 0); 00181 if (name.isEmpty()) 00182 continue; 00183 if (!isPasswdInput && !isTextInput) 00184 continue; 00185 if (autoCompleteOff) 00186 continue; 00187 if (elementMap[QL1S("disabled")].toBool()) 00188 continue; 00189 if (fillform && elementMap[QL1S("readonly")].toBool()) 00190 continue; 00191 if (isPasswdInput && !fillform && value.isEmpty()) 00192 continue; 00193 if (isPasswdInput) 00194 formHasPasswords = true; 00195 inputFields.append(qMakePair(name, value)); 00196 } 00197 00198 // Only add the input fields on form save requests... 00199 if (formHasPasswords && !fillform) 00200 form.fields = inputFields; 00201 00202 // Add the form to the list if we are saving it or it has cached data. 00203 if ((fillform && q->hasCachedFormData(form)) || (!fillform && !form.fields.isEmpty())) 00204 list << form; 00205 } 00206 return list; 00207 } 00208 00209 void KWebWallet::KWebWalletPrivate::fillDataFromCache(KWebWallet::WebFormList &formList) 00210 { 00211 if (!wallet) { 00212 kWarning(800) << "Unable to retrieve form data from wallet"; 00213 return; 00214 } 00215 00216 QMap<QString, QString> cachedValues; 00217 QMutableListIterator <WebForm> formIt (formList); 00218 00219 while (formIt.hasNext()) { 00220 KWebWallet::WebForm &form = formIt.next(); 00221 const QString key (walletKey(form)); 00222 if (wallet->readMap(key, cachedValues) != 0) { 00223 kWarning(800) << "Unable to read form data for key:" << key; 00224 continue; 00225 } 00226 00227 QMapIterator<QString, QString> valuesIt (cachedValues); 00228 while (valuesIt.hasNext()) { 00229 valuesIt.next(); 00230 //kDebug(800) << "wallet key:" << key << valuesIt.key() << valuesIt.value(); 00231 form.fields << qMakePair(valuesIt.key(), valuesIt.value()); 00232 } 00233 } 00234 } 00235 00236 void KWebWallet::KWebWalletPrivate::saveDataToCache(const QString &key) 00237 { 00238 // Make sure the specified keys exists before acting on it. See BR# 270209. 00239 if (!pendingSaveRequests.contains(key)) { 00240 return; 00241 } 00242 00243 bool success = false; 00244 const QUrl url = pendingSaveRequests.value(key).first().url; 00245 00246 if (wallet) { 00247 int count = 0; 00248 const KWebWallet::WebFormList list = pendingSaveRequests.value(key); 00249 QListIterator<KWebWallet::WebForm> formIt (list); 00250 00251 while (formIt.hasNext()) { 00252 QMap<QString, QString> values, storedValues; 00253 const KWebWallet::WebForm form = formIt.next(); 00254 const QString accessKey = walletKey(form); 00255 if (confirmSaveRequestOverwrites.contains(url)) { 00256 confirmSaveRequestOverwrites.remove(url); 00257 const int status = wallet->readMap(accessKey, storedValues); 00258 if (status == 0 && storedValues.count()) { 00259 QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields); 00260 while (fieldIt.hasNext()) { 00261 const KWebWallet::WebForm::WebField field = fieldIt.next(); 00262 if (storedValues.contains(field.first) && 00263 storedValues.value(field.first) != field.second) { 00264 emit q->saveFormDataRequested(key, url); 00265 return; 00266 } 00267 } 00268 // If we got here it means the new credential is exactly 00269 // the same as the one already cached ; so skip the 00270 // re-saving part... 00271 success = true; 00272 continue; 00273 } 00274 } 00275 QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields); 00276 while (fieldIt.hasNext()) { 00277 const KWebWallet::WebForm::WebField field = fieldIt.next(); 00278 values.insert(field.first, field.second); 00279 } 00280 00281 if (wallet->writeMap(accessKey, values) == 0) 00282 count++; 00283 else 00284 kWarning(800) << "Unable to write form data to wallet"; 00285 } 00286 00287 if (list.isEmpty() || count > 0) 00288 success = true; 00289 00290 pendingSaveRequests.remove(key); 00291 } else { 00292 kWarning(800) << "NULL KWallet instance!"; 00293 } 00294 00295 emit q->saveFormDataCompleted(url, success); 00296 } 00297 00298 void KWebWallet::KWebWalletPrivate::openWallet() 00299 { 00300 if (!wallet.isNull()) { 00301 return; 00302 } 00303 00304 wallet.reset(KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 00305 wid, KWallet::Wallet::Asynchronous)); 00306 00307 if (wallet.isNull()) { 00308 return; 00309 } 00310 00311 connect(wallet.data(), SIGNAL(walletOpened(bool)), q, SLOT(_k_openWalletDone(bool))); 00312 connect(wallet.data(), SIGNAL(walletClosed()), q, SLOT(_k_walletClosed())); 00313 } 00314 00315 00316 void KWebWallet::KWebWalletPrivate::removeDataFromCache(const WebFormList &formList) 00317 { 00318 if (!wallet) { 00319 kWarning(800) << "NULL KWallet instance!"; 00320 return; 00321 } 00322 00323 QListIterator<WebForm> formIt (formList); 00324 while (formIt.hasNext()) 00325 wallet->removeEntry(walletKey(formIt.next())); 00326 } 00327 00328 void KWebWallet::KWebWalletPrivate::_k_openWalletDone(bool ok) 00329 { 00330 Q_ASSERT (wallet); 00331 00332 if (ok && 00333 (wallet->hasFolder(KWallet::Wallet::FormDataFolder()) || 00334 wallet->createFolder(KWallet::Wallet::FormDataFolder())) && 00335 wallet->setFolder(KWallet::Wallet::FormDataFolder())) { 00336 00337 // Do pending fill requests... 00338 if (!pendingFillRequests.isEmpty()) { 00339 KUrl::List urlList; 00340 QMutableHashIterator<KUrl, FormsData> requestIt (pendingFillRequests); 00341 while (requestIt.hasNext()) { 00342 requestIt.next(); 00343 KWebWallet::WebFormList list = requestIt.value().forms; 00344 fillDataFromCache(list); 00345 q->fillWebForm(requestIt.key(), list); 00346 } 00347 00348 pendingFillRequests.clear(); 00349 } 00350 00351 // Do pending save requests... 00352 if (!pendingSaveRequests.isEmpty()) { 00353 QListIterator<QString> keysIt (pendingSaveRequests.keys()); 00354 while (keysIt.hasNext()) 00355 saveDataToCache(keysIt.next()); 00356 } 00357 00358 // Do pending remove requests... 00359 if (!pendingRemoveRequests.isEmpty()) { 00360 removeDataFromCache(pendingRemoveRequests); 00361 pendingRemoveRequests.clear(); 00362 } 00363 } else { 00364 // Delete the wallet if opening the wallet failed or we were unable 00365 // to change to the folder we wanted to change to. 00366 delete wallet.take(); 00367 } 00368 } 00369 00370 void KWebWallet::KWebWalletPrivate::_k_walletClosed() 00371 { 00372 if (wallet) 00373 wallet.take()->deleteLater(); 00374 00375 emit q->walletClosed(); 00376 } 00377 00378 KWebWallet::KWebWallet(QObject *parent, WId wid) 00379 :QObject(parent), d(new KWebWalletPrivate(this)) 00380 { 00381 if (!wid) { 00382 // If wid is 0, make a best effort attempt to discern it from our 00383 // parent object. 00384 QWidget* widget = topLevelWindow(parent); 00385 if (widget) { 00386 wid = widget->winId(); 00387 } 00388 } 00389 00390 d->wid = wid; 00391 } 00392 00393 KWebWallet::~KWebWallet() 00394 { 00395 delete d; 00396 } 00397 00398 KWebWallet::WebFormList KWebWallet::formsWithCachedData(QWebFrame* frame, bool recursive) const 00399 { 00400 WebFormList list; 00401 00402 if (frame) { 00403 list << d->parseFormData(frame); 00404 00405 if (recursive) { 00406 QList<QWebFrame*> childFrameList; 00407 collectAllChildFrames(frame, childFrameList); 00408 QListIterator <QWebFrame *> framesIt (childFrameList); 00409 while (framesIt.hasNext()) { 00410 list << d->parseFormData(framesIt.next()); 00411 } 00412 } 00413 } 00414 00415 return list; 00416 } 00417 00418 void KWebWallet::fillFormData(QWebFrame *frame, bool recursive) 00419 { 00420 if (!frame) 00421 return; 00422 00423 KUrl::List urlList; 00424 WebFormList formsList = d->parseFormData(frame); 00425 if (!formsList.isEmpty()) { 00426 const QUrl url (urlForFrame(frame)); 00427 if (d->pendingFillRequests.contains(url)) { 00428 kWarning(800) << "Duplicate request rejected!"; 00429 } else { 00430 KWebWalletPrivate::FormsData data; 00431 data.frame = QWeakPointer<QWebFrame>(frame); 00432 data.forms << formsList; 00433 d->pendingFillRequests.insert(url, data); 00434 urlList << url; 00435 } 00436 } 00437 00438 if (recursive) { 00439 QList<QWebFrame*> childFrameList; 00440 collectAllChildFrames(frame, childFrameList); 00441 QListIterator<QWebFrame*> frameIt (childFrameList); 00442 while (frameIt.hasNext()) { 00443 QWebFrame *childFrame = frameIt.next(); 00444 formsList = d->parseFormData(childFrame); 00445 if (formsList.isEmpty()) 00446 continue; 00447 const QUrl url (childFrame->url()); 00448 if (d->pendingFillRequests.contains(url)) { 00449 kWarning(800) << "Duplicate request rejected!!!"; 00450 } else { 00451 KWebWalletPrivate::FormsData data; 00452 data.frame = QWeakPointer<QWebFrame>(childFrame); 00453 data.forms << formsList; 00454 d->pendingFillRequests.insert(url, data); 00455 urlList << url; 00456 } 00457 } 00458 } 00459 00460 if (!urlList.isEmpty()) 00461 fillFormDataFromCache(urlList); 00462 } 00463 00464 void KWebWallet::saveFormData(QWebFrame *frame, bool recursive, bool ignorePasswordFields) 00465 { 00466 if (!frame) 00467 return; 00468 00469 WebFormList list = d->parseFormData(frame, false, ignorePasswordFields); 00470 if (recursive) { 00471 QList<QWebFrame*> childFrameList; 00472 collectAllChildFrames(frame, childFrameList); 00473 QListIterator<QWebFrame*> frameIt (childFrameList); 00474 while (frameIt.hasNext()) 00475 list << d->parseFormData(frameIt.next(), false, ignorePasswordFields); 00476 } 00477 00478 if (list.isEmpty()) 00479 return; 00480 00481 const QString key = QString::number(qHash(urlForFrame(frame).toString() + frame->frameName()), 16); 00482 const bool isAlreadyPending = d->pendingSaveRequests.contains(key); 00483 d->pendingSaveRequests.insert(key, list); 00484 00485 if (isAlreadyPending) 00486 return; 00487 00488 for (int i = 0 ; i < list.count(); ++i) { 00489 if (hasCachedFormData(list.at(i))) 00490 list.takeAt(i); 00491 } 00492 00493 if (list.isEmpty()) { 00494 d->confirmSaveRequestOverwrites.insert(urlForFrame(frame)); 00495 saveFormDataToCache(key); 00496 return; 00497 } 00498 00499 emit saveFormDataRequested(key, urlForFrame(frame)); 00500 } 00501 00502 void KWebWallet::removeFormData(QWebFrame *frame, bool recursive) 00503 { 00504 if (frame) 00505 removeFormDataFromCache(formsWithCachedData(frame, recursive)); 00506 } 00507 00508 void KWebWallet::removeFormData(const WebFormList &forms) 00509 { 00510 d->pendingRemoveRequests << forms; 00511 removeFormDataFromCache(forms); 00512 } 00513 00514 void KWebWallet::acceptSaveFormDataRequest(const QString &key) 00515 { 00516 saveFormDataToCache(key); 00517 } 00518 00519 void KWebWallet::rejectSaveFormDataRequest(const QString & key) 00520 { 00521 d->pendingSaveRequests.remove(key); 00522 } 00523 00524 void KWebWallet::fillWebForm(const KUrl &url, const KWebWallet::WebFormList &forms) 00525 { 00526 QWeakPointer<QWebFrame> frame = d->pendingFillRequests.value(url).frame; 00527 if (!frame) 00528 return; 00529 00530 QString script; 00531 bool wasFilled = false; 00532 00533 Q_FOREACH (const KWebWallet::WebForm& form, forms) { 00534 Q_FOREACH(const KWebWallet::WebForm::WebField& field, form.fields) { 00535 QString value = field.second; 00536 value.replace(QLatin1Char('\\'), QLatin1String("\\\\")); 00537 script += QString::fromLatin1("document.forms[\"%1\"].elements[\"%2\"].value=\"%3\";\n") 00538 .arg((form.name.isEmpty() ? form.index : form.name)) 00539 .arg(field.first).arg(value); 00540 } 00541 } 00542 00543 if (!script.isEmpty()) { 00544 wasFilled = true; 00545 frame.data()->evaluateJavaScript(script); 00546 } 00547 00548 emit fillFormRequestCompleted(wasFilled); 00549 } 00550 00551 KWebWallet::WebFormList KWebWallet::formsToFill(const KUrl &url) const 00552 { 00553 return d->pendingFillRequests.value(url).forms; 00554 } 00555 00556 KWebWallet::WebFormList KWebWallet::formsToSave(const QString &key) const 00557 { 00558 return d->pendingSaveRequests.value(key); 00559 } 00560 00561 bool KWebWallet::hasCachedFormData(const WebForm &form) const 00562 { 00563 return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), 00564 KWallet::Wallet::FormDataFolder(), 00565 walletKey(form)); 00566 } 00567 00568 void KWebWallet::fillFormDataFromCache(const KUrl::List &urlList) 00569 { 00570 if (d->wallet) { 00571 QListIterator<KUrl> urlIt (urlList); 00572 while (urlIt.hasNext()) { 00573 const KUrl url = urlIt.next(); 00574 WebFormList list = formsToFill(url); 00575 d->fillDataFromCache(list); 00576 fillWebForm(url, list); 00577 } 00578 d->pendingFillRequests.clear(); 00579 } 00580 d->openWallet(); 00581 } 00582 00583 void KWebWallet::saveFormDataToCache(const QString &key) 00584 { 00585 if (d->wallet) { 00586 d->saveDataToCache(key); 00587 return; 00588 } 00589 d->openWallet(); 00590 } 00591 00592 void KWebWallet::removeFormDataFromCache(const WebFormList &forms) 00593 { 00594 if (d->wallet) { 00595 d->removeDataFromCache(forms); 00596 d->pendingRemoveRequests.clear(); 00597 return; 00598 } 00599 d->openWallet(); 00600 } 00601 00602 #include "kwebwallet.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 20:58:22 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 20:58:22 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.