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