Falkon Develop
Cross-platform Qt-based web browser
opensearchengine.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2009 Jakub Wieczorek <faw217@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301 USA
18 */
19/* ============================================================
20* Falkon - Qt web browser
21* Copyright (C) 2010-2016 David Rosca <nowrep@gmail.com>
22*
23* This program is free software: you can redistribute it and/or modify
24* it under the terms of the GNU General Public License as published by
25* the Free Software Foundation, either version 3 of the License, or
26* (at your option) any later version.
27*
28* This program is distributed in the hope that it will be useful,
29* but WITHOUT ANY WARRANTY; without even the implied warranty of
30* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31* GNU General Public License for more details.
32*
33* You should have received a copy of the GNU General Public License
34* along with this program. If not, see <http://www.gnu.org/licenses/>.
35* ============================================================ */
36
37#include "opensearchengine.h"
39
40#include <qbuffer.h>
41#include <qcoreapplication.h>
42#include <qlocale.h>
43#include <qnetworkrequest.h>
44#include <qnetworkreply.h>
45#include <qregularexpression.h>
46#include <qstringlist.h>
47
48#include <QUrlQuery>
49#include <QJsonDocument>
50
51
103 : QObject(parent)
104 , m_searchMethod(QLatin1String("get"))
105 , m_suggestionsMethod(QLatin1String("get"))
106 , m_networkAccessManager(nullptr)
107 , m_suggestionsReply(nullptr)
108 , m_delegate(nullptr)
109{
110 m_requestMethods.insert(QLatin1String("get"), QNetworkAccessManager::GetOperation);
111 m_requestMethods.insert(QLatin1String("post"), QNetworkAccessManager::PostOperation);
112}
113
118= default;
119
120QString OpenSearchEngine::parseTemplate(const QString &searchTerm, const QString &searchTemplate)
121{
122 QString language = QLocale().name();
123 // Simple conversion to RFC 3066.
124 language.replace(QLatin1Char('_'), QLatin1Char('-'));
125
126 QString result = searchTemplate;
127 result.replace(QLatin1String("{count}"), QLatin1String("20"));
128 result.replace(QLatin1String("{startIndex}"), QLatin1String("0"));
129 result.replace(QLatin1String("{startPage}"), QLatin1String("0"));
130 result.replace(QLatin1String("{language}"), language);
131 result.replace(QLatin1String("{inputEncoding}"), QLatin1String("UTF-8"));
132 result.replace(QLatin1String("{outputEncoding}"), QLatin1String("UTF-8"));
133 result.replace(QRegularExpression(QSL("\\{([^\\}]*:|)source\\??\\}")), QCoreApplication::applicationName());
134 result.replace(QLatin1String("{searchTerms}"), QLatin1String(QUrl::toPercentEncoding(searchTerm)));
135
136 return result;
137}
138
146{
147 return m_name;
148}
149
150void OpenSearchEngine::setName(const QString &name)
151{
152 m_name = name;
153}
154
162{
163 return m_description;
164}
165
166void OpenSearchEngine::setDescription(const QString &description)
167{
168 m_description = description;
169}
170
178{
179 return m_searchUrlTemplate;
180}
181
182void OpenSearchEngine::setSearchUrlTemplate(const QString &searchUrlTemplate)
183{
184 if (!searchUrlTemplate.startsWith(QL1S("http://")) && !searchUrlTemplate.startsWith(QL1S("https://"))) {
185 return;
186 }
187
188 m_searchUrlTemplate = searchUrlTemplate;
189}
190
221QUrl OpenSearchEngine::searchUrl(const QString &searchTerm) const
222{
223 if (m_searchUrlTemplate.isEmpty()) {
224 return {};
225 }
226
227 QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_searchUrlTemplate).toUtf8());
228
229 QUrlQuery query(retVal);
230 if (m_searchMethod != QLatin1String("post")) {
231 Parameters::const_iterator end = m_searchParameters.constEnd();
232 Parameters::const_iterator i = m_searchParameters.constBegin();
233 for (; i != end; ++i) {
234 query.addQueryItem(i->first, parseTemplate(searchTerm, i->second));
235 }
236 retVal.setQuery(query);
237 }
238
239 return retVal;
240}
241
242QByteArray OpenSearchEngine::getPostData(const QString &searchTerm) const
243{
244 if (m_searchMethod != QLatin1String("post")) {
245 return {};
246 }
247
248 QUrl retVal = QUrl(QSL("http://foo.bar"));
249
250 QUrlQuery query(retVal);
251 Parameters::const_iterator end = m_searchParameters.constEnd();
252 Parameters::const_iterator i = m_searchParameters.constBegin();
253 for (; i != end; ++i) {
254 query.addQueryItem(i->first, parseTemplate(searchTerm, i->second));
255 }
256 retVal.setQuery(query);
257
258 QByteArray data = retVal.toEncoded(QUrl::RemoveScheme);
259 return data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray();
260}
261
267{
268 return (!m_suggestionsUrlTemplate.isEmpty() || !m_preparedSuggestionsUrl.isEmpty());
269}
270
278{
279 return m_suggestionsUrlTemplate;
280}
281
282void OpenSearchEngine::setSuggestionsUrlTemplate(const QString &suggestionsUrlTemplate)
283{
284 if (!suggestionsUrlTemplate.startsWith(QL1S("http://")) && !suggestionsUrlTemplate.startsWith(QL1S("https://"))) {
285 return;
286 }
287
288 m_suggestionsUrlTemplate = suggestionsUrlTemplate;
289}
290
301QUrl OpenSearchEngine::suggestionsUrl(const QString &searchTerm) const
302{
303 if (!m_preparedSuggestionsUrl.isEmpty()) {
304 QString s = m_preparedSuggestionsUrl;
305 s.replace(QLatin1String("%s"), searchTerm);
306 return QUrl(s);
307 }
308
309 if (m_suggestionsUrlTemplate.isEmpty()) {
310 return {};
311 }
312
313 QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_suggestionsUrlTemplate).toUtf8());
314
315 QUrlQuery query(retVal);
316 if (m_suggestionsMethod != QLatin1String("post")) {
317 Parameters::const_iterator end = m_suggestionsParameters.constEnd();
318 Parameters::const_iterator i = m_suggestionsParameters.constBegin();
319 for (; i != end; ++i) {
320 query.addQueryItem(i->first, parseTemplate(searchTerm, i->second));
321 }
322 retVal.setQuery(query);
323 }
324
325 return retVal;
326}
327
336{
337 return m_searchParameters;
338}
339
341{
342 m_searchParameters = searchParameters;
343}
344
353{
354 return m_suggestionsParameters;
355}
356
358{
359 m_suggestionsParameters = suggestionsParameters;
360}
361
367{
368 return m_searchMethod;
369}
370
371void OpenSearchEngine::setSearchMethod(const QString &method)
372{
373 QString requestMethod = method.toLower();
374 if (!m_requestMethods.contains(requestMethod)) {
375 return;
376 }
377
378 m_searchMethod = requestMethod;
379}
380
386{
387 return m_suggestionsMethod;
388}
389
390void OpenSearchEngine::setSuggestionsMethod(const QString &method)
391{
392 QString requestMethod = method.toLower();
393 if (!m_requestMethods.contains(requestMethod)) {
394 return;
395 }
396
397 m_suggestionsMethod = requestMethod;
398}
399
413{
414 return m_imageUrl;
415}
416
417void OpenSearchEngine::setImageUrl(const QString &imageUrl)
418{
419 m_imageUrl = imageUrl;
420}
421
423{
424 if (!m_networkAccessManager || m_imageUrl.isEmpty()) {
425 return;
426 }
427
428 QNetworkReply* reply = m_networkAccessManager->get(QNetworkRequest(QUrl::fromEncoded(m_imageUrl.toUtf8())));
429 connect(reply, &QNetworkReply::finished, this, &OpenSearchEngine::imageObtained);
430}
431
432void OpenSearchEngine::imageObtained()
433{
434 auto* reply = qobject_cast<QNetworkReply*>(sender());
435
436 if (!reply) {
437 return;
438 }
439
440 QByteArray response = reply->readAll();
441
442 reply->close();
443 reply->deleteLater();
444
445 if (response.isEmpty()) {
446 return;
447 }
448
449 m_image.loadFromData(response);
450 Q_EMIT imageChanged();
451}
452
463{
464 if (m_image.isNull()) {
465 loadImage();
466 }
467 return m_image;
468}
469
470void OpenSearchEngine::setImage(const QImage &image)
471{
472 if (m_imageUrl.isEmpty()) {
473 QBuffer imageBuffer;
474 imageBuffer.open(QBuffer::ReadWrite);
475 if (image.save(&imageBuffer, "PNG")) {
476 m_imageUrl = QString(QLatin1String("data:image/png;base64,%1"))
477 .arg(QLatin1String(imageBuffer.buffer().toBase64()));
478 }
479 }
480
481 m_image = image;
482 Q_EMIT imageChanged();
483}
484
490{
491 return (!m_name.isEmpty() && !m_searchUrlTemplate.isEmpty());
492}
493
495{
496 return (m_name == other.m_name
497 && m_description == other.m_description
498 && m_imageUrl == other.m_imageUrl
499 && m_searchUrlTemplate == other.m_searchUrlTemplate
500 && m_suggestionsUrlTemplate == other.m_suggestionsUrlTemplate
501 && m_searchParameters == other.m_searchParameters
502 && m_suggestionsParameters == other.m_suggestionsParameters);
503}
504
506{
507 return (m_name < other.m_name);
508}
509
521void OpenSearchEngine::setSuggestionsParameters(const QByteArray &parameters)
522{
523 m_preparedSuggestionsParameters = parameters;
524}
525
526void OpenSearchEngine::setSuggestionsUrl(const QString &string)
527{
528 m_preparedSuggestionsUrl = string;
529}
530
532{
533 return suggestionsUrl(QSL("searchstring")).toString().replace(QLatin1String("searchstring"), QLatin1String("%s"));
534}
535
537{
538 QStringList parameters;
539 Parameters::const_iterator end = m_suggestionsParameters.constEnd();
540 Parameters::const_iterator i = m_suggestionsParameters.constBegin();
541 for (; i != end; ++i) {
542 parameters.append(i->first + QLatin1Char('=') + i->second);
543 }
544
545 QByteArray data = parameters.join(QLatin1String("&")).toUtf8();
546
547 return data;
548}
549
550void OpenSearchEngine::requestSuggestions(const QString &searchTerm)
551{
552 if (searchTerm.isEmpty() || !providesSuggestions()) {
553 return;
554 }
555
556 Q_ASSERT(m_networkAccessManager);
557
558 if (!m_networkAccessManager) {
559 return;
560 }
561
562 if (m_suggestionsReply) {
563 m_suggestionsReply->disconnect(this);
564 m_suggestionsReply->abort();
565 m_suggestionsReply->deleteLater();
566 m_suggestionsReply = nullptr;
567 }
568
569 Q_ASSERT(m_requestMethods.contains(m_suggestionsMethod));
570 if (m_suggestionsMethod == QLatin1String("get")) {
571 m_suggestionsReply = m_networkAccessManager->get(QNetworkRequest(suggestionsUrl(searchTerm)));
572 }
573 else {
574 QStringList parameters;
575 Parameters::const_iterator end = m_suggestionsParameters.constEnd();
576 Parameters::const_iterator i = m_suggestionsParameters.constBegin();
577 for (; i != end; ++i) {
578 parameters.append(i->first + QLatin1Char('=') + i->second);
579 }
580
581 QByteArray data = parameters.join(QLatin1String("&")).toUtf8();
582 m_suggestionsReply = m_networkAccessManager->post(QNetworkRequest(suggestionsUrl(searchTerm)), data);
583 }
584
585 connect(m_suggestionsReply, &QNetworkReply::finished, this, &OpenSearchEngine::suggestionsObtained);
586}
587
598void OpenSearchEngine::requestSearchResults(const QString &searchTerm)
599{
600 if (!m_delegate || searchTerm.isEmpty()) {
601 return;
602 }
603
604 Q_ASSERT(m_requestMethods.contains(m_searchMethod));
605
606 QNetworkRequest request(QUrl(searchUrl(searchTerm)));
607 QByteArray data;
608 QNetworkAccessManager::Operation operation = m_requestMethods.value(m_searchMethod);
609
610 if (operation == QNetworkAccessManager::PostOperation) {
611 QStringList parameters;
612 Parameters::const_iterator end = m_searchParameters.constEnd();
613 Parameters::const_iterator i = m_searchParameters.constBegin();
614 for (; i != end; ++i) {
615 parameters.append(i->first + QLatin1Char('=') + i->second);
616 }
617
618 data = parameters.join(QLatin1String("&")).toUtf8();
619 }
620
621 m_delegate->performSearchRequest(request, operation, data);
622}
623
624void OpenSearchEngine::suggestionsObtained()
625{
626 const QByteArray response = m_suggestionsReply->readAll();
627
628 m_suggestionsReply->close();
629 m_suggestionsReply->deleteLater();
630 m_suggestionsReply = nullptr;
631
632 QJsonParseError err;
633 QJsonDocument json = QJsonDocument::fromJson(response, &err);
634 const QVariant res = json.toVariant();
635
636 if (err.error != QJsonParseError::NoError || res.typeId() != QMetaType::QVariantList)
637 return;
638
639 const QVariantList list = res.toList();
640
641 if (list.size() < 2)
642 return;
643
644 QStringList out;
645
646 const auto items = list.at(1).toList();
647 for (const QVariant &v : items) {
648 out.append(v.toString());
649 }
650
651 Q_EMIT suggestions(out);
652}
653
661QNetworkAccessManager* OpenSearchEngine::networkAccessManager() const
662{
663 return m_networkAccessManager;
664}
665
666void OpenSearchEngine::setNetworkAccessManager(QNetworkAccessManager* networkAccessManager)
667{
668 m_networkAccessManager = networkAccessManager;
669}
670
679{
680 return m_delegate;
681}
682
684{
685 m_delegate = delegate;
686}
687
An abstract class providing custom processing of specific activities.
virtual void performSearchRequest(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &data)=0
A class representing a single search engine described in OpenSearch format.
void requestSearchResults(const QString &searchTerm)
QUrl searchUrl(const QString &searchTerm) const
OpenSearchEngineDelegate * delegate() const
void setSearchParameters(const Parameters &searchParameters)
void setSuggestionsMethod(const QString &method)
void setSearchMethod(const QString &method)
Parameters searchParameters
Parameters suggestionsParameters
QImage image() const
void setImage(const QImage &image)
QNetworkAccessManager * networkAccessManager
QUrl suggestionsUrl(const QString &searchTerm) const
QByteArray getSuggestionsParameters()
void requestSuggestions(const QString &searchTerm)
void setImageUrl(const QString &url)
void setSearchUrlTemplate(const QString &searchUrl)
void setNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
QString suggestionsUrlTemplate
the template of the suggestions URL
void setName(const QString &name)
QByteArray getPostData(const QString &searchTerm) const
bool operator==(const OpenSearchEngine &other) const
QList< Parameter > Parameters
QString description
the description of the engine
void setSuggestionsParameters(const Parameters &suggestionsParameters)
void setDelegate(OpenSearchEngineDelegate *delegate)
void setDescription(const QString &description)
static QString parseTemplate(const QString &searchTerm, const QString &searchTemplate)
void setSuggestionsUrlTemplate(const QString &suggestionsUrl)
void loadImage() const
QString searchUrlTemplate
the template of the search URL
bool operator<(const OpenSearchEngine &other) const
QString name
the name of the engine
void suggestions(const QStringList &suggestions)
void setSuggestionsUrl(const QString &string)
OpenSearchEngine(QObject *parent=nullptr)
i
Definition: i18n.py:23
#define QL1S(x)
Definition: qzcommon.h:44
#define QSL(x)
Definition: qzcommon.h:40