PageRenderTime 1397ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/lib/opensearch/searchenginesmanager.cpp

https://github.com/darzjon6951/qupzilla
C++ | 539 lines | 400 code | 113 blank | 26 comment | 49 complexity | b21e128cba9a44950715b4d3c8e922fb MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /* ============================================================
  2. * QupZilla - WebKit based browser
  3. * Copyright (C) 2010-2014 David Rosca <nowrep@gmail.com>
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. * ============================================================ */
  18. #include "searchenginesmanager.h"
  19. #include "searchenginesdialog.h"
  20. #include "editsearchengine.h"
  21. #include "iconprovider.h"
  22. #include "mainapplication.h"
  23. #include "networkmanager.h"
  24. #include "opensearchreader.h"
  25. #include "opensearchengine.h"
  26. #include "settings.h"
  27. #include "qzsettings.h"
  28. #include "webview.h"
  29. #include <QNetworkReply>
  30. #include <QMessageBox>
  31. #include <QWebElement>
  32. #include <QSqlQuery>
  33. #include <QBuffer>
  34. #if QT_VERSION >= 0x050000
  35. #include <QUrlQuery>
  36. #endif
  37. #define ENSURE_LOADED if (!m_settingsLoaded) loadSettings();
  38. static QIcon iconFromBase64(const QByteArray &data)
  39. {
  40. QIcon image;
  41. QByteArray bArray = QByteArray::fromBase64(data);
  42. QBuffer buffer(&bArray);
  43. buffer.open(QIODevice::ReadOnly);
  44. QDataStream in(&buffer);
  45. in >> image;
  46. buffer.close();
  47. if (!image.isNull()) {
  48. return image;
  49. }
  50. return IconProvider::emptyWebIcon();
  51. }
  52. static QByteArray iconToBase64(const QIcon &icon)
  53. {
  54. QByteArray bArray;
  55. QBuffer buffer(&bArray);
  56. buffer.open(QIODevice::WriteOnly);
  57. QDataStream out(&buffer);
  58. out << icon;
  59. buffer.close();
  60. return bArray.toBase64();
  61. }
  62. SearchEnginesManager::SearchEnginesManager(QObject* parent)
  63. : QObject(parent)
  64. , m_settingsLoaded(false)
  65. , m_saveScheduled(false)
  66. {
  67. Settings settings;
  68. settings.beginGroup("SearchEngines");
  69. m_startingEngineName = settings.value("activeEngine", "DuckDuckGo").toString();
  70. m_defaultEngineName = settings.value("DefaultEngine", "DuckDuckGo").toString();
  71. settings.endGroup();
  72. connect(this, SIGNAL(enginesChanged()), this, SLOT(scheduleSave()));
  73. }
  74. void SearchEnginesManager::loadSettings()
  75. {
  76. m_settingsLoaded = true;
  77. QSqlQuery query;
  78. query.exec("SELECT name, icon, url, shortcut, suggestionsUrl, suggestionsParameters, postData FROM search_engines");
  79. while (query.next()) {
  80. Engine en;
  81. en.name = query.value(0).toString();
  82. en.icon = iconFromBase64(query.value(1).toByteArray());
  83. en.url = query.value(2).toString();
  84. en.shortcut = query.value(3).toString();
  85. en.suggestionsUrl = query.value(4).toString();
  86. en.suggestionsParameters = query.value(5).toByteArray();
  87. en.postData = query.value(6).toByteArray();
  88. m_allEngines.append(en);
  89. if (en.name == m_defaultEngineName) {
  90. m_defaultEngine = en;
  91. }
  92. }
  93. if (m_allEngines.isEmpty()) {
  94. restoreDefaults();
  95. }
  96. if (m_defaultEngine.name.isEmpty()) {
  97. m_defaultEngine = m_allEngines[0];
  98. }
  99. }
  100. SearchEngine SearchEnginesManager::engineForShortcut(const QString &shortcut)
  101. {
  102. Engine returnEngine;
  103. if (shortcut.isEmpty()) {
  104. return returnEngine;
  105. }
  106. foreach (const Engine &en, m_allEngines) {
  107. if (en.shortcut == shortcut) {
  108. returnEngine = en;
  109. break;
  110. }
  111. }
  112. return returnEngine;
  113. }
  114. LoadRequest SearchEnginesManager::searchResult(const Engine &engine, const QString &string)
  115. {
  116. ENSURE_LOADED;
  117. // GET search engine
  118. if (engine.postData.isEmpty()) {
  119. QByteArray url = engine.url.toUtf8();
  120. url.replace(QLatin1String("%s"), QUrl::toPercentEncoding(string));
  121. return LoadRequest(QUrl::fromEncoded(url));
  122. }
  123. // POST search engine
  124. QByteArray data = engine.postData;
  125. data.replace("%s", QUrl::toPercentEncoding(string));
  126. QNetworkRequest req(QUrl::fromEncoded(engine.url.toUtf8()));
  127. req.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("application/x-www-form-urlencoded"));
  128. return LoadRequest(req, LoadRequest::PostOperation, data);
  129. }
  130. LoadRequest SearchEnginesManager::searchResult(const QString &string)
  131. {
  132. ENSURE_LOADED;
  133. const Engine en = qzSettings->searchWithDefaultEngine ? m_defaultEngine : m_activeEngine;
  134. return searchResult(en, string);
  135. }
  136. void SearchEnginesManager::restoreDefaults()
  137. {
  138. Engine duck;
  139. duck.name = "DuckDuckGo";
  140. duck.icon = QIcon(":/icons/sites/duck.png");
  141. duck.url = "https://duckduckgo.com/?q=%s&t=qupzilla";
  142. duck.shortcut = "d";
  143. Engine sp;
  144. sp.name = "StartPage";
  145. sp.icon = QIcon(":/icons/sites/startpage.png");
  146. sp.url = "https://startpage.com/do/search";
  147. sp.postData = "query=%s&cat=web&language=english";
  148. sp.shortcut = "sp";
  149. sp.suggestionsUrl = "https://startpage.com/cgi-bin/csuggest?output=json&lang=english&query=%s";
  150. Engine wiki;
  151. wiki.name = "Wikipedia (en)";
  152. wiki.icon = QIcon(":/icons/sites/wikipedia.png");
  153. wiki.url = "http://en.wikipedia.org/wiki/Special:Search?search=%s&fulltext=Search";
  154. wiki.shortcut = "w";
  155. wiki.suggestionsUrl = "http://en.wikipedia.org/w/api.php?action=opensearch&search=%s&namespace=0";
  156. Engine google;
  157. google.name = "Google";
  158. google.icon = QIcon(":icons/sites/google.png");
  159. google.url = "http://www.google.com/search?client=qupzilla&q=%s";
  160. google.shortcut = "g";
  161. google.suggestionsUrl = "http://suggestqueries.google.com/complete/search?output=firefox&q=%s";
  162. addEngine(duck);
  163. addEngine(sp);
  164. addEngine(wiki);
  165. addEngine(google);
  166. m_defaultEngine = duck;
  167. emit enginesChanged();
  168. }
  169. // static
  170. QIcon SearchEnginesManager::iconForSearchEngine(const QUrl &url)
  171. {
  172. QIcon ic = IconProvider::iconForDomain(url);
  173. if (ic.isNull()) {
  174. ic = QIcon(":icons/menu/search-icon.png");
  175. }
  176. return ic;
  177. }
  178. void SearchEnginesManager::engineChangedImage()
  179. {
  180. OpenSearchEngine* engine = qobject_cast<OpenSearchEngine*>(sender());
  181. if (!engine) {
  182. return;
  183. }
  184. foreach (Engine e, m_allEngines) {
  185. if (e.name == engine->name() &&
  186. e.url.contains(engine->searchUrl("%s").toString()) &&
  187. !engine->image().isNull()
  188. ) {
  189. int index = m_allEngines.indexOf(e);
  190. if (index != -1) {
  191. m_allEngines[index].icon = QIcon(QPixmap::fromImage(engine->image()));
  192. emit enginesChanged();
  193. delete engine;
  194. break;
  195. }
  196. }
  197. }
  198. }
  199. void SearchEnginesManager::editEngine(const Engine &before, const Engine &after)
  200. {
  201. removeEngine(before);
  202. addEngine(after);
  203. }
  204. void SearchEnginesManager::addEngine(const Engine &engine)
  205. {
  206. ENSURE_LOADED;
  207. if (m_allEngines.contains(engine)) {
  208. return;
  209. }
  210. m_allEngines.append(engine);
  211. emit enginesChanged();
  212. }
  213. void SearchEnginesManager::addEngineFromForm(const QWebElement &element, WebView* view)
  214. {
  215. QWebElement formElement = element.parent();
  216. while (!formElement.isNull()) {
  217. if (formElement.tagName().toLower() == QLatin1String("form")) {
  218. break;
  219. }
  220. formElement = formElement.parent();
  221. }
  222. if (formElement.isNull()) {
  223. return;
  224. }
  225. const QString method = formElement.hasAttribute("method") ? formElement.attribute("method").toUpper() : "GET";
  226. bool isPost = method == QLatin1String("POST");
  227. QUrl actionUrl = QUrl::fromEncoded(formElement.attribute("action").toUtf8());
  228. if (actionUrl.isRelative()) {
  229. actionUrl = view->url().resolved(actionUrl);
  230. }
  231. QUrl parameterUrl = actionUrl;
  232. if (isPost) {
  233. parameterUrl = QUrl("http://foo.bar");
  234. }
  235. #if QT_VERSION >= 0x050000
  236. QUrlQuery query(parameterUrl);
  237. query.addQueryItem(element.attribute("name"), "%s");
  238. QWebElementCollection allInputs = formElement.findAll("input");
  239. foreach (QWebElement e, allInputs) {
  240. if (element == e || !e.hasAttribute("name")) {
  241. continue;
  242. }
  243. query.addQueryItem(e.attribute("name"), e.evaluateJavaScript("this.value").toString());
  244. }
  245. parameterUrl.setQuery(query);
  246. #else
  247. QList<QPair<QByteArray, QByteArray> > queryItems;
  248. QPair<QByteArray, QByteArray> item;
  249. item.first = element.attribute("name").toUtf8();
  250. item.second = "%s";
  251. queryItems.append(item);
  252. QWebElementCollection allInputs = formElement.findAll("input");
  253. foreach (QWebElement e, allInputs) {
  254. if (element == e || !e.hasAttribute("name")) {
  255. continue;
  256. }
  257. QPair<QByteArray, QByteArray> item;
  258. item.first = QUrl::toPercentEncoding(e.attribute("name").toUtf8());
  259. item.second = QUrl::toPercentEncoding(e.evaluateJavaScript("this.value").toByteArray());
  260. queryItems.append(item);
  261. }
  262. parameterUrl.setEncodedQueryItems(parameterUrl.encodedQueryItems() + queryItems);
  263. #endif
  264. if (!isPost) {
  265. actionUrl = parameterUrl;
  266. }
  267. SearchEngine engine;
  268. engine.name = view->title();
  269. engine.icon = view->icon();
  270. engine.url = actionUrl.toEncoded();
  271. if (isPost) {
  272. QByteArray data = parameterUrl.toEncoded(QUrl::RemoveScheme);
  273. engine.postData = data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray();
  274. }
  275. EditSearchEngine dialog(SearchEnginesDialog::tr("Add Search Engine"), view);
  276. dialog.setName(engine.name);
  277. dialog.setIcon(engine.icon);
  278. dialog.setUrl(engine.url);
  279. dialog.setPostData(engine.postData);
  280. if (dialog.exec() != QDialog::Accepted) {
  281. return;
  282. }
  283. engine.name = dialog.name();
  284. engine.icon = dialog.icon();
  285. engine.url = dialog.url();
  286. engine.shortcut = dialog.shortcut();
  287. engine.postData = dialog.postData().toUtf8();
  288. if (engine.name.isEmpty() || engine.url.isEmpty()) {
  289. return;
  290. }
  291. addEngine(engine);
  292. }
  293. void SearchEnginesManager::addEngine(OpenSearchEngine* engine)
  294. {
  295. ENSURE_LOADED;
  296. Engine en;
  297. en.name = engine->name();
  298. en.url = engine->searchUrl("searchstring").toString().replace(QLatin1String("searchstring"), QLatin1String("%s"));
  299. if (engine->image().isNull()) {
  300. en.icon = iconForSearchEngine(engine->searchUrl(QString()));
  301. }
  302. else {
  303. en.icon = QIcon(QPixmap::fromImage(engine->image()));
  304. }
  305. en.suggestionsUrl = engine->getSuggestionsUrl();
  306. en.suggestionsParameters = engine->getSuggestionsParameters();
  307. en.postData = engine->getPostData("searchstring").replace("searchstring", "%s");
  308. addEngine(en);
  309. connect(engine, SIGNAL(imageChanged()), this, SLOT(engineChangedImage()));
  310. }
  311. void SearchEnginesManager::addEngine(const QUrl &url)
  312. {
  313. ENSURE_LOADED;
  314. if (!url.isValid()) {
  315. return;
  316. }
  317. qApp->setOverrideCursor(Qt::WaitCursor);
  318. QNetworkReply* reply = mApp->networkManager()->get(QNetworkRequest(url));
  319. reply->setParent(this);
  320. connect(reply, SIGNAL(finished()), this, SLOT(replyFinished()));
  321. }
  322. void SearchEnginesManager::replyFinished()
  323. {
  324. qApp->restoreOverrideCursor();
  325. QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  326. if (!reply) {
  327. return;
  328. }
  329. if (reply->error() != QNetworkReply::NoError) {
  330. reply->close();
  331. reply->deleteLater();
  332. return;
  333. }
  334. OpenSearchReader reader;
  335. OpenSearchEngine* engine = reader.read(reply);
  336. engine->setNetworkAccessManager(mApp->networkManager());
  337. reply->close();
  338. reply->deleteLater();
  339. if (checkEngine(engine)) {
  340. addEngine(engine);
  341. QMessageBox::information(0, tr("Search Engine Added"), tr("Search Engine \"%1\" has been successfully added.").arg(engine->name()));
  342. }
  343. }
  344. bool SearchEnginesManager::checkEngine(OpenSearchEngine* engine)
  345. {
  346. if (!engine->isValid()) {
  347. QString errorString = tr("Search Engine is not valid!");
  348. QMessageBox::warning(0, tr("Error"), tr("Error while adding Search Engine <br><b>Error Message: </b> %1").arg(errorString));
  349. return false;
  350. }
  351. return true;
  352. }
  353. void SearchEnginesManager::setActiveEngine(const Engine &engine)
  354. {
  355. ENSURE_LOADED;
  356. if (!m_allEngines.contains(engine)) {
  357. return;
  358. }
  359. m_activeEngine = engine;
  360. emit activeEngineChanged();
  361. }
  362. void SearchEnginesManager::setDefaultEngine(const SearchEnginesManager::Engine &engine)
  363. {
  364. ENSURE_LOADED;
  365. if (!m_allEngines.contains(engine)) {
  366. return;
  367. }
  368. m_defaultEngine = engine;
  369. emit defaultEngineChanged();
  370. }
  371. void SearchEnginesManager::removeEngine(const Engine &engine)
  372. {
  373. ENSURE_LOADED;
  374. int index = m_allEngines.indexOf(engine);
  375. if (index < 0) {
  376. return;
  377. }
  378. QSqlQuery query;
  379. query.prepare("DELETE FROM search_engines WHERE name=? AND url=?");
  380. query.bindValue(0, engine.name);
  381. query.bindValue(1, engine.url);
  382. query.exec();
  383. m_allEngines.remove(index);
  384. emit enginesChanged();
  385. }
  386. void SearchEnginesManager::setAllEngines(const QVector<Engine> &engines)
  387. {
  388. ENSURE_LOADED;
  389. m_allEngines = engines;
  390. emit enginesChanged();
  391. }
  392. QVector<SearchEngine> SearchEnginesManager::allEngines()
  393. {
  394. ENSURE_LOADED;
  395. return m_allEngines;
  396. }
  397. void SearchEnginesManager::saveSettings()
  398. {
  399. Settings settings;
  400. settings.beginGroup("SearchEngines");
  401. settings.setValue("activeEngine", m_activeEngine.name);
  402. settings.setValue("DefaultEngine", m_defaultEngine.name);
  403. settings.endGroup();
  404. if (!m_saveScheduled) {
  405. return;
  406. }
  407. // Well, this is not the best implementation to do as this is taking some time.
  408. // Actually, it is delaying the quit of app for about a 1 sec on my machine with only
  409. // 5 engines. Another problem is that deleting rows without VACUUM isn't actually freeing
  410. // space in database.
  411. //
  412. // But as long as user is not playing with search engines every run it is acceptable.
  413. QSqlQuery query;
  414. query.exec("DELETE FROM search_engines");
  415. foreach (const Engine &en, m_allEngines) {
  416. query.prepare("INSERT INTO search_engines (name, icon, url, shortcut, suggestionsUrl, suggestionsParameters, postData) VALUES (?, ?, ?, ?, ?, ?, ?)");
  417. query.addBindValue(en.name);
  418. query.addBindValue(iconToBase64(en.icon));
  419. query.addBindValue(en.url);
  420. query.addBindValue(en.shortcut);
  421. query.addBindValue(en.suggestionsUrl);
  422. query.addBindValue(en.suggestionsParameters);
  423. query.addBindValue(en.postData);
  424. query.exec();
  425. }
  426. }