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/QPointer> 00032 #include <QtWebKit/QWebPage> 00033 #include <QtWebKit/QWebFrame> 00034 #include <QtWebKit/QWebElement> 00035 #include <QtWebKit/QWebElementCollection> 00036 #include <qwindowdefs.h> 00037 00038 #define QL1S(x) QLatin1String(x) 00039 #define QL1C(x) QLatin1Char(x) 00040 00041 // Form parsing JS code adapted from Arora. 00042 // See https://github.com/Arora/arora/blob/master/src/data/parseForms.js 00043 #define FORM_PARSING_JS "(function (){ \ 00044 var forms = new Array; \ 00045 for (var i = 0; i < document.forms.length; ++i) { \ 00046 var form = document.forms[i]; \ 00047 if (form.method.toLowerCase() != 'post') \ 00048 continue; \ 00049 var formObject = new Object; \ 00050 formObject.name = form.name; \ 00051 formObject.index = i; \ 00052 var elements = new Array; \ 00053 for (var j = 0; j < form.elements.length; ++j) { \ 00054 var e = form.elements[j]; \ 00055 var element = new Object; \ 00056 element.name = e.name; \ 00057 element.value = e.value; \ 00058 element.type = e.type; \ 00059 element.readonly = e.hasAttribute('readonly'); \ 00060 element.disabled = e.hasAttribute('disabled'); \ 00061 if (element.autocomplete != null) \ 00062 element.autocomplete = element.autocomplete.value; \ 00063 elements.push(element); \ 00064 } \ 00065 formObject.elements = elements; \ 00066 forms.push(formObject); \ 00067 } \ 00068 return forms; \ 00069 }())" 00070 00071 00076 static QString walletKey(KWebWallet::WebForm form) 00077 { 00078 QString key = form.url.toString(QUrl::RemoveQuery|QUrl::RemoveFragment); 00079 key += QL1C('#'); 00080 key += form.name; 00081 return key; 00082 } 00083 00084 static void collectAllChildFrames(QWebFrame* frame, QList<QWebFrame*>& list) 00085 { 00086 list << frame->childFrames(); 00087 QListIterator<QWebFrame*> it(frame->childFrames()); 00088 while (it.hasNext()) { 00089 collectAllChildFrames(it.next(), list); 00090 } 00091 } 00092 00093 class KWebWallet::KWebWalletPrivate 00094 { 00095 public: 00096 struct FormsData 00097 { 00098 QPointer<QWebFrame> frame; 00099 KWebWallet::WebFormList forms; 00100 }; 00101 00102 KWebWalletPrivate(KWebWallet* parent); 00103 KWebWallet::WebFormList parseFormData(QWebFrame* frame, bool fillform = true, bool ignorepasswd = false); 00104 void fillDataFromCache(KWebWallet::WebFormList &formList); 00105 void saveDataToCache(const QString &key); 00106 void removeDataFromCache(const WebFormList &formList); 00107 00108 // Private slots... 00109 void _k_openWalletDone(bool); 00110 void _k_walletClosed(); 00111 00112 WId wid; 00113 KWebWallet *q; 00114 QPointer<KWallet::Wallet> wallet; 00115 KWebWallet::WebFormList pendingRemoveRequests; 00116 QHash<KUrl, FormsData> pendingFillRequests; 00117 QHash<QString, KWebWallet::WebFormList> pendingSaveRequests; 00118 QSet<KUrl> confirmSaveRequestOverwrites; 00119 }; 00120 00121 KWebWallet::KWebWalletPrivate::KWebWalletPrivate(KWebWallet *parent) 00122 :wid (0), q(parent) 00123 { 00124 } 00125 00126 KWebWallet::WebFormList KWebWallet::KWebWalletPrivate::parseFormData(QWebFrame *frame, bool fillform, bool ignorepasswd) 00127 { 00128 Q_ASSERT(frame); 00129 00130 KWebWallet::WebFormList list; 00131 00132 // Execute the javscript to obtain the necessary fields... 00133 QVariantList results = frame->evaluateJavaScript(QL1S(FORM_PARSING_JS)).toList(); 00134 Q_FOREACH (const QVariant &formVariant, results) { 00135 QVariantMap map = formVariant.toMap(); 00136 KWebWallet::WebForm form; 00137 form.url = frame->url(); 00138 form.name = map[QL1S("name")].toString(); 00139 form.index = map[QL1S("index")].toString(); 00140 bool formHasPasswords = false; 00141 const QVariantList elements = map[QL1S("elements")].toList(); 00142 QList<KWebWallet::WebForm::WebField> inputFields; 00143 Q_FOREACH (const QVariant &element, elements) { 00144 QVariantMap elementMap = element.toMap(); 00145 const QString name = elementMap[QL1S("name")].toString(); 00146 const QString value = (ignorepasswd ? QString() : elementMap[QL1S("value")].toString()); 00147 const QString type = elementMap[QL1S("type")].toString(); 00148 const bool isPasswdInput = (type.compare(QL1S("password"), Qt::CaseInsensitive) == 0); 00149 const bool isTextInput = (type.compare(QL1S("text"), Qt::CaseInsensitive) == 0); 00150 const bool autoCompleteOff = (elementMap[QL1S("autocomplete")].toString().compare(QL1S("off"), Qt::CaseInsensitive) == 0); 00151 if (name.isEmpty()) 00152 continue; 00153 if (!isPasswdInput && !isTextInput) 00154 continue; 00155 if (autoCompleteOff) 00156 continue; 00157 if (elementMap[QL1S("disabled")].toBool()) 00158 continue; 00159 if (fillform && elementMap[QL1S("readonly")].toBool()) 00160 continue; 00161 if (isPasswdInput && !fillform && value.isEmpty()) 00162 continue; 00163 if (isPasswdInput) 00164 formHasPasswords = true; 00165 inputFields.append(qMakePair(name, value)); 00166 } 00167 00168 // Only add the input fields on form save requests... 00169 if (formHasPasswords && !fillform) 00170 form.fields = inputFields; 00171 00172 // Add the form to the list if we are saving it or it has cached data. 00173 if ((fillform && q->hasCachedFormData(form)) || (!fillform && !form.fields.isEmpty())) 00174 list << form; 00175 } 00176 return list; 00177 } 00178 00179 void KWebWallet::KWebWalletPrivate::fillDataFromCache(KWebWallet::WebFormList &formList) 00180 { 00181 if (!wallet) { 00182 kWarning(800) << "Unable to retreive form data from wallet"; 00183 return; 00184 } 00185 00186 QMap<QString, QString> cachedValues; 00187 QMutableListIterator <WebForm> formIt (formList); 00188 00189 while (formIt.hasNext()) { 00190 KWebWallet::WebForm &form = formIt.next(); 00191 const QString key (walletKey(form)); 00192 if (wallet->readMap(key, cachedValues) != 0) { 00193 kWarning(800) << "Unable to read form data for key:" << key; 00194 continue; 00195 } 00196 00197 QMapIterator<QString, QString> valuesIt (cachedValues); 00198 while (valuesIt.hasNext()) { 00199 valuesIt.next(); 00200 //kDebug(800) << "wallet key:" << key << valuesIt.key() << valuesIt.value(); 00201 form.fields << qMakePair(valuesIt.key(), valuesIt.value()); 00202 } 00203 } 00204 } 00205 00206 void KWebWallet::KWebWalletPrivate::saveDataToCache(const QString &key) 00207 { 00208 bool success = false; 00209 const QUrl url = pendingSaveRequests.value(key).first().url; 00210 00211 if (wallet) { 00212 int count = 0; 00213 const KWebWallet::WebFormList list = pendingSaveRequests.value(key); 00214 QListIterator<KWebWallet::WebForm> formIt (list); 00215 00216 while (formIt.hasNext()) { 00217 QMap<QString, QString> values, storedValues; 00218 const KWebWallet::WebForm form = formIt.next(); 00219 const QString accessKey = walletKey(form); 00220 if (confirmSaveRequestOverwrites.contains(url)) { 00221 confirmSaveRequestOverwrites.remove(url); 00222 const int status = wallet->readMap(accessKey, storedValues); 00223 if (status == 0 && storedValues.count()) { 00224 QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields); 00225 while (fieldIt.hasNext()) { 00226 const KWebWallet::WebForm::WebField field = fieldIt.next(); 00227 if (storedValues.contains(field.first) && 00228 storedValues.value(field.first) != field.second) { 00229 emit q->saveFormDataRequested(key, url); 00230 return; 00231 } 00232 } 00233 // If we got here it means the new credential is exactly 00234 // the same as the one already cached ; so skip the 00235 // re-saving part... 00236 success = true; 00237 continue; 00238 } 00239 } 00240 QListIterator<KWebWallet::WebForm::WebField> fieldIt (form.fields); 00241 while (fieldIt.hasNext()) { 00242 const KWebWallet::WebForm::WebField field = fieldIt.next(); 00243 values.insert(field.first, field.second); 00244 } 00245 00246 if (wallet->writeMap(accessKey, values) == 0) 00247 count++; 00248 else 00249 kWarning(800) << "Unable to write form data to wallet"; 00250 } 00251 00252 if (list.isEmpty() || count > 0) 00253 success = true; 00254 00255 pendingSaveRequests.remove(key); 00256 } else { 00257 kWarning(800) << "NULL KWallet instance!"; 00258 } 00259 00260 emit q->saveFormDataCompleted(url, success); 00261 } 00262 00263 void KWebWallet::KWebWalletPrivate::removeDataFromCache(const WebFormList &formList) 00264 { 00265 if (!wallet) { 00266 kWarning(800) << "NULL KWallet instance!"; 00267 return; 00268 } 00269 00270 QListIterator<WebForm> formIt (formList); 00271 while (formIt.hasNext()) 00272 wallet->removeEntry(walletKey(formIt.next())); 00273 } 00274 00275 void KWebWallet::KWebWalletPrivate::_k_openWalletDone(bool ok) 00276 { 00277 Q_ASSERT (wallet); 00278 00279 if (ok && 00280 (wallet->hasFolder(KWallet::Wallet::FormDataFolder()) || 00281 wallet->createFolder(KWallet::Wallet::FormDataFolder())) && 00282 wallet->setFolder(KWallet::Wallet::FormDataFolder())) { 00283 00284 // Do pending fill requests... 00285 if (!pendingFillRequests.isEmpty()) { 00286 KUrl::List urlList; 00287 QMutableHashIterator<KUrl, FormsData> requestIt (pendingFillRequests); 00288 while (requestIt.hasNext()) { 00289 requestIt.next(); 00290 KWebWallet::WebFormList list = requestIt.value().forms; 00291 fillDataFromCache(list); 00292 q->fillWebForm(requestIt.key(), list); 00293 } 00294 00295 pendingFillRequests.clear(); 00296 } 00297 00298 // Do pending save requests... 00299 if (!pendingSaveRequests.isEmpty()) { 00300 QListIterator<QString> keysIt (pendingSaveRequests.keys()); 00301 while (keysIt.hasNext()) 00302 saveDataToCache(keysIt.next()); 00303 } 00304 00305 // Do pending remove requests... 00306 if (!pendingRemoveRequests.isEmpty()) { 00307 removeDataFromCache(pendingRemoveRequests); 00308 pendingRemoveRequests.clear(); 00309 } 00310 } else { 00311 // Delete the wallet if opening the wallet failed or we were unable 00312 // to change to the folder we wanted to change to. 00313 delete wallet; 00314 } 00315 } 00316 00317 void KWebWallet::KWebWalletPrivate::_k_walletClosed() 00318 { 00319 if (wallet) 00320 wallet->deleteLater(); 00321 00322 emit q->walletClosed(); 00323 } 00324 00325 KWebWallet::KWebWallet(QObject *parent, WId wid) 00326 :QObject(parent), d(new KWebWalletPrivate(this)) 00327 { 00328 if (!wid) { 00329 // If wid is 0, make the best effort the discern it from our parent. 00330 QWebPage *page = qobject_cast<QWebPage*>(parent); 00331 if (page) { 00332 QWidget *widget = page->view(); 00333 if (widget && widget->window()) 00334 wid = widget->window()->winId(); 00335 } 00336 } 00337 00338 d->wid = wid; 00339 } 00340 00341 KWebWallet::~KWebWallet() 00342 { 00343 delete d->wallet; 00344 delete d; 00345 } 00346 00347 KWebWallet::WebFormList KWebWallet::formsWithCachedData(QWebFrame* frame, bool recursive) const 00348 { 00349 WebFormList list; 00350 00351 if (frame) { 00352 list << d->parseFormData(frame); 00353 00354 if (recursive) { 00355 QList<QWebFrame*> childFrameList; 00356 collectAllChildFrames(frame, childFrameList); 00357 QListIterator <QWebFrame *> framesIt (childFrameList); 00358 while (framesIt.hasNext()) { 00359 list << d->parseFormData(framesIt.next()); 00360 } 00361 } 00362 } 00363 00364 return list; 00365 } 00366 00367 void KWebWallet::fillFormData(QWebFrame *frame, bool recursive) 00368 { 00369 if (!frame) 00370 return; 00371 00372 KUrl::List urlList; 00373 WebFormList formsList = d->parseFormData(frame); 00374 if (!formsList.isEmpty()) { 00375 const QUrl url (frame->url()); 00376 if (d->pendingFillRequests.contains(url)) { 00377 kWarning(800) << "Duplicate request rejected!"; 00378 } else { 00379 KWebWalletPrivate::FormsData data; 00380 data.frame = frame; 00381 data.forms << formsList; 00382 d->pendingFillRequests.insert(url, data); 00383 urlList << url; 00384 } 00385 } 00386 00387 if (recursive) { 00388 QList<QWebFrame*> childFrameList; 00389 collectAllChildFrames(frame, childFrameList); 00390 QListIterator<QWebFrame*> frameIt (childFrameList); 00391 while (frameIt.hasNext()) { 00392 QWebFrame *childFrame = frameIt.next(); 00393 formsList = d->parseFormData(childFrame); 00394 if (formsList.isEmpty()) 00395 continue; 00396 const QUrl url (childFrame->url()); 00397 if (d->pendingFillRequests.contains(url)) { 00398 kWarning(800) << "Duplicate request rejected!!!"; 00399 } else { 00400 KWebWalletPrivate::FormsData data; 00401 data.frame = childFrame; 00402 data.forms << formsList; 00403 d->pendingFillRequests.insert(url, data); 00404 urlList << url; 00405 } 00406 } 00407 } 00408 00409 if (!urlList.isEmpty()) 00410 fillFormDataFromCache(urlList); 00411 } 00412 00413 void KWebWallet::saveFormData(QWebFrame *frame, bool recursive, bool ignorePasswordFields) 00414 { 00415 if (!frame) 00416 return; 00417 00418 WebFormList list = d->parseFormData(frame, false, ignorePasswordFields); 00419 if (recursive) { 00420 QList<QWebFrame*> childFrameList; 00421 collectAllChildFrames(frame, childFrameList); 00422 QListIterator<QWebFrame*> frameIt (childFrameList); 00423 while (frameIt.hasNext()) 00424 list << d->parseFormData(frameIt.next(), false, ignorePasswordFields); 00425 } 00426 00427 if (list.isEmpty()) 00428 return; 00429 00430 const QString key = QString::number(qHash(frame->url().toString() + frame->frameName()), 16); 00431 const bool isAlreadyPending = d->pendingSaveRequests.contains(key); 00432 d->pendingSaveRequests.insert(key, list); 00433 00434 if (isAlreadyPending) 00435 return; 00436 00437 for (int i = 0 ; i < list.count(); ++i) { 00438 if (hasCachedFormData(list.at(i))) 00439 list.takeAt(i); 00440 } 00441 00442 if (list.isEmpty()) { 00443 d->confirmSaveRequestOverwrites.insert(frame->url()); 00444 saveFormDataToCache(key); 00445 return; 00446 } 00447 00448 emit saveFormDataRequested(key, frame->url()); 00449 } 00450 00451 void KWebWallet::removeFormData(QWebFrame *frame, bool recursive) 00452 { 00453 if (frame) 00454 removeFormDataFromCache(formsWithCachedData(frame, recursive)); 00455 } 00456 00457 void KWebWallet::removeFormData(const WebFormList &forms) 00458 { 00459 d->pendingRemoveRequests << forms; 00460 removeFormDataFromCache(forms); 00461 } 00462 00463 void KWebWallet::acceptSaveFormDataRequest(const QString &key) 00464 { 00465 saveFormDataToCache(key); 00466 } 00467 00468 void KWebWallet::rejectSaveFormDataRequest(const QString & key) 00469 { 00470 d->pendingSaveRequests.remove(key); 00471 } 00472 00473 void KWebWallet::fillWebForm(const KUrl &url, const KWebWallet::WebFormList &forms) 00474 { 00475 QWebFrame *frame = d->pendingFillRequests.value(url).frame; 00476 if (!frame) 00477 return; 00478 00479 QString script; 00480 bool wasFilled = false; 00481 00482 Q_FOREACH (const KWebWallet::WebForm& form, forms) { 00483 Q_FOREACH(const KWebWallet::WebForm::WebField& field, form.fields) 00484 script += QString::fromLatin1("document.forms[\"%1\"].elements[\"%2\"].value=\"%3\";\n") 00485 .arg(form.name.isEmpty() ? form.index : form.name) 00486 .arg(field.first).arg(field.second); 00487 } 00488 00489 if (!script.isEmpty()) { 00490 wasFilled = true; 00491 frame->evaluateJavaScript(script); 00492 } 00493 00494 emit fillFormRequestCompleted(wasFilled); 00495 } 00496 00497 KWebWallet::WebFormList KWebWallet::formsToFill(const KUrl &url) const 00498 { 00499 return d->pendingFillRequests.value(url).forms; 00500 } 00501 00502 KWebWallet::WebFormList KWebWallet::formsToSave(const QString &key) const 00503 { 00504 return d->pendingSaveRequests.value(key); 00505 } 00506 00507 bool KWebWallet::hasCachedFormData(const WebForm &form) const 00508 { 00509 return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), 00510 KWallet::Wallet::FormDataFolder(), 00511 walletKey(form)); 00512 } 00513 00514 void KWebWallet::fillFormDataFromCache(const KUrl::List &urlList) 00515 { 00516 if (!d->wallet) { 00517 d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 00518 d->wid, KWallet::Wallet::Asynchronous); 00519 connect(d->wallet, SIGNAL(walletOpened(bool)), 00520 this, SLOT(_k_openWalletDone(bool))); 00521 connect(d->wallet, SIGNAL(walletClosed()), 00522 this, SLOT(_k_walletClosed())); 00523 return; 00524 } 00525 00526 QListIterator<KUrl> urlIt (urlList); 00527 while (urlIt.hasNext()) { 00528 const KUrl url = urlIt.next(); 00529 WebFormList list = formsToFill(url); 00530 d->fillDataFromCache(list); 00531 fillWebForm(url, list); 00532 } 00533 00534 d->pendingFillRequests.clear(); 00535 } 00536 00537 void KWebWallet::saveFormDataToCache(const QString &key) 00538 { 00539 if (!d->wallet) { 00540 d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 00541 d->wid, KWallet::Wallet::Asynchronous); 00542 connect(d->wallet, SIGNAL(walletOpened(bool)), 00543 this, SLOT(_k_openWalletDone(bool))); 00544 connect(d->wallet, SIGNAL(walletClosed()), 00545 this, SLOT(_k_walletClosed())); 00546 return; 00547 } 00548 00549 d->saveDataToCache(key); 00550 } 00551 00552 void KWebWallet::removeFormDataFromCache(const WebFormList &forms) 00553 { 00554 if (!d->wallet) { 00555 d->wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 00556 d->wid, KWallet::Wallet::Asynchronous); 00557 connect(d->wallet, SIGNAL(walletOpened(bool)), 00558 this, SLOT(_k_openWalletDone(bool))); 00559 connect(d->wallet, SIGNAL(walletClosed()), 00560 this, SLOT(_k_walletClosed())); 00561 return; 00562 } 00563 00564 d->removeDataFromCache(forms); 00565 d->pendingRemoveRequests.clear(); 00566 } 00567 00568 #include "kwebwallet.moc"
KDE 4.6 API Reference