/* ============================================================ * * This file is a part of the rekonq project * * Copyright (C) 2012 by Siteshwar Vashisht * Copyright (C) 2011 by Andrea Diamantini * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ============================================================ */ // Self Includes #include "operasynchandler.h" #include "operasynchandler.moc" // Auto Includes #include "rekonq.h" // Local Includes #include "bookmarkmanager.h" // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include OperaSyncHandler::OperaSyncHandler(QObject *parent) : SyncHandler(parent) , _mode(RECEIVE_CHANGES) , _doLogin(false) , _requestCount(0) , _isSyncing(false) { kDebug() << "Creating Opera Bookmarks handler..."; _webPage.settings()->setAttribute(QWebSettings::AutoLoadImages, false); _webPage.settings()->setAttribute(QWebSettings::PrivateBrowsingEnabled, true); connect(&_webPage, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); _qoauth.setConsumerKey("zCuj9aUcehaHsfKtcHcg2YYLX42CkxDX"); _qoauth.setConsumerSecret("xApuyHdDd9DSbTXLDRXuZzwKI2lOYSsl"); } void OperaSyncHandler::initialLoadAndCheck() { if (!ReKonfig::syncEnabled()) { _firstTimeSynced = false; return; } // Bookmarks if (ReKonfig::syncBookmarks()) { _mode = RECEIVE_CHANGES; startLogin(); } if (ReKonfig::syncHistory()) { emit syncStatus(Rekonq::History, false, i18n("Not supported!")); } if (ReKonfig::syncHistory()) { emit syncStatus(Rekonq::Passwords, false, i18n("Not supported!")); } } bool OperaSyncHandler::syncRelativeEnabled(bool check) { if (!ReKonfig::syncEnabled()) return false; if (!_firstTimeSynced) return false; return check; } // --------------------------------------------------------------------------------------- void OperaSyncHandler::syncHistory() { kDebug() << "Syncing history not supported!"; emit syncStatus(Rekonq::History, false, i18n("Syncing history not supported!")); emit syncHistoryFinished(false); } void OperaSyncHandler::syncPasswords() { kDebug() << "Syncing passwords not supported!"; emit syncStatus(Rekonq::Passwords, false, i18n("Syncing passwords not supported!")); emit syncPasswordsFinished(false); } void OperaSyncHandler::syncBookmarks() { if (_isSyncing) { kDebug() << "Sync already in progress!"; return; } _mode = SEND_CHANGES; startLogin(); } void OperaSyncHandler::startLogin() { if (ReKonfig::syncUser().isEmpty() || ReKonfig::syncPass().isEmpty()) { kDebug() << "No username or password!"; emit syncStatus(Rekonq::Bookmarks, false, i18n("No username or password!")); emit syncBookmarksFinished(false); return; } _isSyncing = true; _doLogin = true; _qoauth.setRequestTimeout(20000); _qoauth.ignoreSslErrors(); if (_authToken.isEmpty() || _authTokenSecret.isEmpty()) { // If we have not yet logged into server, we need to fetch OAuth access token and secret from server. kDebug() << "Loading login page..."; QOAuth::ParamMap requestMap; requestMap.insert("oauth_callback", "oob"); QOAuth::ParamMap requestParam = _qoauth.requestToken("https://auth.opera.com/service/oauth/request_token", QOAuth::POST, QOAuth::HMAC_SHA1, requestMap); if (_qoauth.error() != QOAuth::NoError) { kDebug() << "Error occured while fetching request tokens. Error code is : " << _qoauth.error(); emit syncStatus(Rekonq::Bookmarks, false, i18n("OAuth : Error fetching request token.")); _isSyncing = false; return; } _requestToken = requestParam.value("oauth_token"); _requestTokenSecret = requestParam.value("oauth_token_secret"); kDebug() << "OAuth Request Token : " << _requestToken; kDebug() << "OAuth Request Token Secret : " << _requestTokenSecret; kDebug() << QUrl("https://auth.opera.com/service/oauth/authorize?oauth_token=" + QString(_requestToken) + "&oauth_callback=oob"); QString u = QL1S("https://auth.opera.com/service/oauth/authorize?oauth_token=") + QString(_requestToken) + QL1S("&oauth_callback=oob"); _webPage.mainFrame()->load(QUrl(u)); } else { // We already have OAuth access token and secret, let's fetch bookmarks from server directly. getBookmarks(); } } // Loading a webpage finished, what action to take is decided based on url we have loaded. void OperaSyncHandler::loadFinished(bool ok) { kDebug() << "Load Finished" << ok; if (!ok) { kDebug() << "Error loading: " << _webPage.mainFrame()->url(); emit syncStatus(Rekonq::Bookmarks, false, i18n("Error loading: " + _webPage.mainFrame()->url().toEncoded())); _isSyncing = false; return; } QString path = _webPage.mainFrame()->url().path(); if (path == "/service/oauth/authorize") { QWebFrame *mainFrame = _webPage.mainFrame(); QString html = mainFrame->toHtml(); if (_doLogin == true && html.contains("login-form")) { kDebug() << "Login page"; QWebElement form = mainFrame->findFirstElement("#login-form"); if (form.isNull()) { kDebug() << "form is null"; } QWebElement username = form.findFirst("#username"); QWebElement password = form.findFirst("#password"); QWebElement button = form.findFirst("input[name=\"grant\"]"); username.setAttribute("value", ReKonfig::syncUser()); password.setAttribute("value", ReKonfig::syncPass()); button.evaluateJavaScript("this.click();"); emit syncStatus(Rekonq::Bookmarks, true, i18n("Signing in...")); _doLogin = false; } else if (html.contains("verifier")) { QOAuth::ParamMap authParams; QWebElement authkey = mainFrame->findFirstElement("#verifier"); QByteArray verifier = authkey.toPlainText().toUtf8(); kDebug() << "OAuth verifier code is : " << verifier; authParams.insert("oauth_verifier", verifier); emit syncStatus(Rekonq::Bookmarks, true, i18n("OAuth : Sending verification code.")); QOAuth::ParamMap resultParam = _qoauth.accessToken("https://auth.opera.com/service/oauth/access_token", QOAuth::POST, _requestToken, _requestTokenSecret, QOAuth::HMAC_SHA1, authParams); if (_qoauth.error() != QOAuth::NoError) { kDebug() << "Error occurred while fetching access tokens. Error code is : " << _qoauth.error(); emit syncStatus(Rekonq::Bookmarks, false, i18n("OAuth : Error fetching access token.")); _isSyncing = false; return; } _authToken = resultParam.value("oauth_token"); _authTokenSecret = resultParam.value("oauth_token_secret"); kDebug() << "OAuth Token : " << _authToken; kDebug() << "OAuth Token Secret : " << _authTokenSecret; _requestToken.clear(); _requestTokenSecret.clear(); getBookmarks(); } else if (_doLogin == false) { //Login failed emit syncStatus(Rekonq::Bookmarks, false, i18n("Login failed!")); kDebug() << "Login failed!"; _isSyncing = false; } else { kDebug() << "Unknown page : " << _webPage.mainFrame()->url(); _isSyncing = false; } } } //Fetch all the bookmarks from server void OperaSyncHandler::getBookmarks() { emit syncStatus(Rekonq::Bookmarks, true, i18n("Fetching bookmarks from server...")); QOAuth::ParamMap requestMap; requestMap.insert("api_output", "xml"); kDebug() << "Auth Token : " << _authToken; kDebug() << "Auth Token Secret : " << _authTokenSecret; QByteArray fetchBookmarksUrl = "https://link.api.opera.com/rest/bookmark/descendants/"; QByteArray urlParams = _qoauth.createParametersString(fetchBookmarksUrl, QOAuth::GET, _authToken, _authTokenSecret, QOAuth::HMAC_SHA1, requestMap, QOAuth::ParseForInlineQuery); QNetworkRequest request; fetchBookmarksUrl.append(urlParams); //kDebug() << urlstr; KIO::TransferJob *job = KIO::get(KUrl(fetchBookmarksUrl), KIO::Reload, KIO::HideProgressInfo); connect(job, SIGNAL(result(KJob*)), this, SLOT(fetchBookmarksResultSlot(KJob*))); connect(job, SIGNAL(data(KIO::Job*, QByteArray)), this, SLOT(fetchBookmarksDataSlot(KIO::Job*, QByteArray))); } void OperaSyncHandler::fetchBookmarksDataSlot(KIO::Job* job, QByteArray data) { Q_UNUSED(job); _xmlData += data; } //We have received all the bookmarks which exist on server, now we need to compare them with local bookmarks. void OperaSyncHandler::fetchBookmarksResultSlot(KJob* job) { if (job->error() != 0) { //Error could be because our OAuth token expired, let's reset it. _authToken = ""; _authTokenSecret = ""; _isSyncing = false; //Reset _xmlData for next request _xmlData = ""; kDebug() << "Some error!" << job->error(); return; } // kDebug() << _xmlData; QDomDocument doc("bookmarks"); doc.setContent(_xmlData); QDomNodeList responseList = doc.elementsByTagName("response"); KBookmarkGroup root = BookmarkManager::self()->rootGroup(); if (_mode == RECEIVE_CHANGES) { handleResponse(responseList, root); emit syncStatus(Rekonq::Bookmarks, true, i18n("Done!")); // _isSyncing = false; _mode = SEND_CHANGES; } //After receiving changes from server, send changes to server. handleResponse(responseList, root); QDomElement item = responseList.at(0).toElement(); KBookmark current = root.first(); while (!current.isNull()) { if (current.isGroup()) { QString groupName = current.fullText(); QDomElement child = findOperaFolder(item, groupName); if (child.isNull()) { //Add Opera group here kDebug() << "Add group " << groupName; KJob *job = addBookmarkFolderOnServer(current.fullText()); _jobToGroupMap.insert(job, current.toGroup()); } else { QDomElement grandChild = getChildElement(child, "children"); QString id = getChildString(child, "id"); kDebug() << grandChild.tagName() << id; if (grandChild.isNull()) { kDebug() << "Grand child is null"; handleLocalGroup(current.toGroup(), grandChild, id); } else { handleLocalGroup(current.toGroup(), grandChild, id); } } } else { KUrl url = current.url(); QDomElement child = findOperaBookmark(item, url); if (child.isNull()) { //Add bookmark on server kDebug() << "Add bookmark :" << url; addBookmarkOnServer(current.fullText(), current.url().url()); } /* else { kDebug() << "Bookmark exists :" << url; } */ } current = root.next(current); } decreaseRequestCount(); _xmlData = ""; } void OperaSyncHandler::createBookmarkDataSlot(KIO::Job* job, QByteArray data) { Q_UNUSED(job); Q_UNUSED(data); // kDebug() << data; // _xmlData += data; } // Check whether bookmark creation was successful on server void OperaSyncHandler::createBookmarkResultSlot(KJob* job) { decreaseRequestCount(); if (job->error() != 0) { kDebug() << "Some error!" << job->error(); return; } } void OperaSyncHandler::createBookmarkFolderDataSlot(KIO::Job* job, QByteArray data) { QByteArray &value = _jobToResponseMap[job]; value.append(data); } // If bookmark folder (it's empty) was creted successfully on server, // we need to add all it's children (which exists in local bookmarks) on server. void OperaSyncHandler::createBookmarkFolderResultSlot(KJob* job) { QByteArray value = _jobToResponseMap[job]; KBookmarkGroup root = _jobToGroupMap[job]; _jobToResponseMap.remove(job); _jobToGroupMap.remove(job); if (job->error() != 0) { kDebug() << "Error occured while creating bookmark folder on server. Error code : " << job->error(); decreaseRequestCount(); return; } QDomDocument doc("new bookmark"); doc.setContent(value); QDomNodeList responseList = doc.elementsByTagName("response"); if (responseList.size() > 0) { QDomElement item = responseList.at(0).firstChildElement(); QString parentId = getIdFromResource(item); handleLocalGroup(root, item, parentId); } decreaseRequestCount(); } // Check whether we deleted a resource on server successfully. void OperaSyncHandler::deleteResourceResultSlot(KJob* job) { decreaseRequestCount(); if (job->error() != 160) { kDebug() << "Error occurred while deleting resource on server. Error code : " << job->error(); return; } } // This function is here just for debugging purpose. We don't need it's output. void OperaSyncHandler::deleteResourceDataSlot(KIO::Job* job, QByteArray data) { Q_UNUSED(job); kDebug() << data; } // Checks whether a bookmark exists locally or not, and either add it locally or delete from server void OperaSyncHandler::handleBookmark(const QDomElement &item, KBookmarkGroup root) { QString url = getUrlFromResourceProperties(item); QString title = getTitleFromResourceProperties(item); QString id = getChildString(item, "id"); KBookmark bookmark = findLocalBookmark(root, KUrl(url)); if (bookmark.isNull()) { if (_mode == RECEIVE_CHANGES) { root.addBookmark(title, KUrl(url)); BookmarkManager::self()->manager()->emitChanged(root); } else { //Delete bookmark from server kDebug() << "Deleting bookmark from server : " << title; deleteResourceOnServer(id); } } } //Traverse through all the children of a bookmark folder void OperaSyncHandler::handleBookmarkFolder(const QDomElement &item, KBookmarkGroup &root) { QDomNode child = item.firstChild(); while (!child.isNull()) { QDomNode resource = child.firstChild(); while (!resource.isNull()) { handleResource(resource, root); resource = resource.nextSibling(); } child = child.nextSibling(); } } //Handle resource tag of xml replied from server void OperaSyncHandler::handleResource(const QDomNode &item, KBookmarkGroup &root) { QDomElement element = item.toElement(); QString itemType = getChildString(item, "item_type"); if (itemType == "bookmark") { handleBookmark(element, root); } else if (itemType == "bookmark_folder") { QString title = getTitleFromResourceProperties(item.toElement()); QString id = getChildString(item.toElement(), "id"); if (title == "Trash") return; KBookmarkGroup childGroup = findLocalGroup(root, title); if (_mode == RECEIVE_CHANGES) { if (childGroup.isNull()) { childGroup = root.createNewFolder(title); BookmarkManager::self()->manager()->emitChanged(root); } handleBookmarkFolder(element, childGroup); } else { if (childGroup.isNull()) { //Delete bookmark folder on server kDebug() << "Deleting bookmark folder from server : " << title; deleteResourceOnServer(id); } else { handleBookmarkFolder(element, childGroup); } } } } //Start traversing the response received from server from tag void OperaSyncHandler::handleResponse(const QDomNodeList &responseList, KBookmarkGroup &root) { if (responseList.size() > 0) { QDomNode item = responseList.at(0).firstChild(); do { handleResource(item, root); item = item.nextSibling(); } while (!item.isNull()); } } //This method checks whether we need to add a bookmark or bookmark folder on server which exists only locally void OperaSyncHandler::handleLocalGroup(const KBookmarkGroup &root, const QDomElement &item, QString parentId) { KBookmark current = root.first(); while (!current.isNull()) { if (current.isGroup()) { QString groupName = current.fullText(); QDomElement child = findOperaFolder(item, groupName); if (child.isNull()) { //Add Opera group here KJob *job = addBookmarkFolderOnServer(current.fullText(), parentId); _jobToGroupMap.insert(job, current.toGroup()); } else { QDomElement grandChild = getChildElement(child, "children"); QString id = getChildString(child, "id"); // FIXME: What about here??? if (grandChild.isNull()) { handleLocalGroup(current.toGroup(), grandChild, id); } else { handleLocalGroup(current.toGroup(), grandChild, id); } } } else { KUrl url = current.url(); QDomElement child = findOperaBookmark(item, url); if (child.isNull()) { //Add bookmark on server addBookmarkOnServer(current.fullText(), current.url().url(), parentId); } /* else { kDebug() << "Bookmark exists :" << url; } */ } current = root.next(current); } } //Add a bookmark on server void OperaSyncHandler::addBookmarkOnServer(QString title, QString url, QString parent) { QOAuth::ParamMap requestMap; requestMap.insert("api_output", "xml"); requestMap.insert("api_method", "create"); requestMap.insert("item_type", "bookmark"); requestMap.insert("title", QUrl::toPercentEncoding(title.toUtf8())); requestMap.insert("uri", QUrl::toPercentEncoding(url.toUtf8())); QByteArray requestUrl = "https://link.api.opera.com/rest/bookmark/"; if (!parent.isNull()) { requestUrl.append(parent.toUtf8()); } QByteArray postData = _qoauth.createParametersString(requestUrl, QOAuth::POST, _authToken, _authTokenSecret, QOAuth::HMAC_SHA1, requestMap, QOAuth::ParseForRequestContent); KIO::TransferJob *job = KIO::http_post(KUrl(requestUrl), postData, KIO::HideProgressInfo); job->addMetaData("Content-Type", "application/x-www-form-urlencoded"); connect(job, SIGNAL(result(KJob*)), this, SLOT(createBookmarkResultSlot(KJob*))); connect(job, SIGNAL(data(KIO::Job*, QByteArray)), this, SLOT(createBookmarkDataSlot(KIO::Job*, QByteArray))); ++_requestCount; } //Add a bookmark folder on server KJob *OperaSyncHandler::addBookmarkFolderOnServer(QString title, QString parent) { QOAuth::ParamMap requestMap; requestMap.insert("api_output", "xml"); requestMap.insert("api_method", "create"); requestMap.insert("item_type", "bookmark_folder"); requestMap.insert("title", QUrl::toPercentEncoding(title.toUtf8())); QByteArray requestUrl = "https://link.api.opera.com/rest/bookmark/"; if (!parent.isNull()) { requestUrl.append(parent.toUtf8()); } QByteArray postData = _qoauth.createParametersString(requestUrl, QOAuth::POST, _authToken, _authTokenSecret, QOAuth::HMAC_SHA1, requestMap, QOAuth::ParseForRequestContent); KIO::TransferJob *job = KIO::http_post(KUrl(requestUrl), postData, KIO::HideProgressInfo); job->addMetaData("Content-Type", "application/x-www-form-urlencoded"); _jobToResponseMap.insert(job, ""); connect(job, SIGNAL(result(KJob*)), this, SLOT(createBookmarkFolderResultSlot(KJob*))); connect(job, SIGNAL(data(KIO::Job*, QByteArray)), this, SLOT(createBookmarkFolderDataSlot(KIO::Job*, QByteArray))); ++_requestCount; return job; } //Resource could be either a bookmark folder or bookmark. void OperaSyncHandler::deleteResourceOnServer(QString id) { QOAuth::ParamMap requestMap; requestMap.insert("api_method", "delete"); QByteArray requestUrl = "https://link.api.opera.com/rest/bookmark/"; if (id.isEmpty()) { kDebug() << "Id is empty!"; return; } requestUrl.append(id.toUtf8()); QByteArray postData = _qoauth.createParametersString(requestUrl, QOAuth::POST, _authToken, _authTokenSecret, QOAuth::HMAC_SHA1, requestMap, QOAuth::ParseForRequestContent); kDebug() << "Deleting Resource : " << id; KIO::TransferJob *job = KIO::http_post(KUrl(requestUrl), postData, KIO::HideProgressInfo); job->addMetaData("Content-Type", "application/x-www-form-urlencoded"); connect(job, SIGNAL(result(KJob*)), this, SLOT(deleteResourceResultSlot(KJob*))); connect(job, SIGNAL(data(KIO::Job*, QByteArray)), this, SLOT(deleteResourceDataSlot(KIO::Job*, QByteArray))); ++_requestCount; } //Get url for a bookmark from xml element of Opera bookmarks QString OperaSyncHandler::getUrlFromResourceProperties(const QDomElement &item) { if (item.tagName() != "resource") return QString(); QDomNodeList propertiesList = item.elementsByTagName("properties"); if (propertiesList.size() > 0) { QDomElement properties = propertiesList.at(0).toElement(); QDomNodeList uriList = properties.elementsByTagName("uri"); if (uriList.size() > 0) return uriList.at(0).toElement().text(); } return QString(); } //Get title for a bookmark or folder from xml element of Opera bookmarks QString OperaSyncHandler::getTitleFromResourceProperties(const QDomElement &item) { if (item.tagName() != "resource") return QString(); QDomNodeList propertiesList = item.elementsByTagName("properties"); if (propertiesList.size() > 0) { QDomElement properties = propertiesList.at(0).toElement(); QDomNodeList titleList = properties.elementsByTagName("title"); if (titleList.size() > 0) return titleList.at(0).toElement().text(); } return QString(); } //Get id for a bookmark or folder from xml element of Opera bookmarks QString OperaSyncHandler::getIdFromResource(const QDomElement &item) { if (item.tagName() != "resource") return QString(); QDomNodeList idList = item.elementsByTagName("id"); if (idList.size() > 0) { return idList.at(0).toElement().text(); } return QString(); } //Get text of a child element of a dom node QString OperaSyncHandler::getChildString(const QDomNode &node, const QString &name) { QDomNodeList nodes = node.childNodes(); for (int j = 0; j < nodes.size(); ++j) { QDomElement element = nodes.at(j).toElement(); if (nodes.at(j).nodeName() == name) { return element.text(); } } return NULL; } //Get value of a child element of a dom node as an element QDomElement OperaSyncHandler::getChildElement(const QDomNode &node, const QString &name) { QDomNodeList nodes = node.childNodes(); for (int j = 0; j < nodes.size(); ++j) { QDomElement element = nodes.at(j).toElement(); if (nodes.at(j).nodeName() == name) { return element; } } return QDomElement(); } //Find a bookmark group in a specifiec bookmark group of client KBookmarkGroup OperaSyncHandler::findLocalGroup(const KBookmarkGroup &root, const QString &name) { KBookmark child = root.first(); while (!child.isNull()) { if (child.isGroup() && name == child.fullText()) { break; } child = root.next(child); } return child.toGroup(); } //Find a bookmark in a specifiec bookmark group of client KBookmark OperaSyncHandler::findLocalBookmark(const KBookmarkGroup &root, const KUrl &url) { KBookmark child = root.first(); while (!child.isNull()) { if (!child.isGroup() && url == child.url()) { break; } child = root.next(child); } return child; } //Find bookmark folder in xml returned by server QDomElement OperaSyncHandler::findOperaFolder(const QDomElement &root, const QString &name) { QDomElement current = root.firstChild().toElement(); while (!current.isNull()) { if ((getChildString(current, "item_type") == "bookmark_folder") && getTitleFromResourceProperties(current) == name) break; current = current.nextSibling().toElement(); } return current; } //Find bookmark in xml returned by server QDomElement OperaSyncHandler::findOperaBookmark(const QDomElement &root, const KUrl &url) { QDomElement current = root.firstChild().toElement(); while (!current.isNull()) { if ((getChildString(current, "item_type") == "bookmark") && KUrl(getUrlFromResourceProperties(current)) == url) break; current = current.nextSibling().toElement(); } return current; } /*While sending changes to server, we need to keep track of requests which have been sent to server to check whether syncing is finished. */ void OperaSyncHandler::decreaseRequestCount() { if (_requestCount > 0) { --_requestCount; } if (_requestCount <= 0) { emit syncStatus(Rekonq::Bookmarks, true, i18n("Done!")); _isSyncing = false; } }