1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "helpbrowsersupport.h" |
5 | #include "helpenginewrapper.h" |
6 | #include "helpviewer.h" |
7 | #include "tracer.h" |
8 | |
9 | #include <QtHelp/QHelpEngineCore> |
10 | |
11 | #include <QtNetwork/QNetworkAccessManager> |
12 | #include <QtNetwork/QNetworkReply> |
13 | #include <QtNetwork/QNetworkRequest> |
14 | |
15 | #include <QtCore/QTimer> |
16 | #include <QtCore/QCoreApplication> |
17 | #include <QtCore/QUrl> |
18 | #include <QtCore/QDebug> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | // -- messages |
23 | |
24 | static const char g_htmlPage[] = "<html><head><meta http-equiv=\"content-type\" content=\"text/html; " |
25 | "charset=UTF-8\"><title>%1</title><style>body{padding: 3em 0em;background: #eeeeee;}" |
26 | "hr{color: lightgray;width: 100%;}img{float: left;opacity: .8;}#box{background: white;border: 1px solid " |
27 | "lightgray;width: 600px;padding: 60px;margin: auto;}h1{font-size: 130%;font-weight: bold;border-bottom: " |
28 | "1px solid lightgray;margin-left: 48px;}h2{font-size: 100%;font-weight: normal;border-bottom: 1px solid " |
29 | "lightgray;margin-left: 48px;}ul{font-size: 80%;padding-left: 48px;margin: 0;}#reloadButton{padding-left:" |
30 | "48px;}</style></head><body><div id=\"box\"><h1>%2</h1><h2>%3</h2><h2><b>%4</b></h2></div></body></html>" ; |
31 | |
32 | QString HelpBrowserSupport::msgError404() |
33 | { |
34 | return QCoreApplication::translate(context: "HelpViewer" , key: "Error 404..." ); |
35 | } |
36 | |
37 | QString HelpBrowserSupport::msgPageNotFound() |
38 | { |
39 | return QCoreApplication::translate(context: "HelpViewer" , key: "The page could not be found" ); |
40 | } |
41 | |
42 | QString HelpBrowserSupport::msgAllDocumentationSets() |
43 | { |
44 | return QCoreApplication::translate(context: "HelpViewer" , |
45 | key: "Please make sure that you have all " |
46 | "documentation sets installed." ); |
47 | } |
48 | |
49 | QString HelpBrowserSupport::msgLoadError(const QUrl &url) |
50 | { |
51 | return HelpViewer::tr(s: "Error loading: %1" ).arg(a: url.toString()); |
52 | } |
53 | |
54 | QString HelpBrowserSupport::msgHtmlErrorPage(const QUrl &url) |
55 | { |
56 | return QString::fromLatin1(ba: g_htmlPage) |
57 | .arg(args: HelpBrowserSupport::msgError404(), args: HelpBrowserSupport::msgPageNotFound(), |
58 | args: HelpBrowserSupport::msgLoadError(url), args: HelpBrowserSupport::msgAllDocumentationSets()); |
59 | } |
60 | |
61 | // A QNetworkAccessManager implementing unhandled URL schema handling for WebKit-type browsers. |
62 | |
63 | // -- HelpNetworkReply |
64 | |
65 | class HelpNetworkReply : public QNetworkReply |
66 | { |
67 | public: |
68 | HelpNetworkReply(const QNetworkRequest &request, const QByteArray &fileData, |
69 | const QString &mimeType); |
70 | |
71 | void abort() override; |
72 | |
73 | qint64 bytesAvailable() const override |
74 | { return data.size() + QNetworkReply::bytesAvailable(); } |
75 | |
76 | protected: |
77 | qint64 readData(char *data, qint64 maxlen) override; |
78 | |
79 | private: |
80 | QByteArray data; |
81 | const qint64 origLen; |
82 | }; |
83 | |
84 | HelpNetworkReply::HelpNetworkReply(const QNetworkRequest &request, |
85 | const QByteArray &fileData, const QString& mimeType) |
86 | : data(fileData), origLen(fileData.size()) |
87 | { |
88 | TRACE_OBJ |
89 | setRequest(request); |
90 | setUrl(request.url()); |
91 | setOpenMode(QIODevice::ReadOnly); |
92 | |
93 | setHeader(header: QNetworkRequest::ContentTypeHeader, value: mimeType); |
94 | setHeader(header: QNetworkRequest::ContentLengthHeader, value: QByteArray::number(origLen)); |
95 | QTimer::singleShot(interval: 0, receiver: this, slot: &QNetworkReply::metaDataChanged); |
96 | QTimer::singleShot(interval: 0, receiver: this, slot: &QNetworkReply::readyRead); |
97 | QTimer::singleShot(interval: 0, receiver: this, slot: &QNetworkReply::finished); |
98 | } |
99 | |
100 | void HelpNetworkReply::abort() |
101 | { |
102 | TRACE_OBJ |
103 | } |
104 | |
105 | qint64 HelpNetworkReply::readData(char *buffer, qint64 maxlen) |
106 | { |
107 | TRACE_OBJ |
108 | qint64 len = qMin(a: qint64(data.size()), b: maxlen); |
109 | if (len) { |
110 | memcpy(dest: buffer, src: data.constData(), n: len); |
111 | data.remove(index: 0, len); |
112 | } |
113 | if (!data.size()) |
114 | QTimer::singleShot(interval: 0, receiver: this, slot: &QNetworkReply::finished); |
115 | return len; |
116 | } |
117 | |
118 | // -- HelpRedirectNetworkReply |
119 | |
120 | class HelpRedirectNetworkReply : public QNetworkReply |
121 | { |
122 | public: |
123 | HelpRedirectNetworkReply(const QNetworkRequest &request, const QUrl &newUrl) |
124 | { |
125 | setRequest(request); |
126 | setAttribute(code: QNetworkRequest::HttpStatusCodeAttribute, value: 301); |
127 | setAttribute(code: QNetworkRequest::RedirectionTargetAttribute, value: newUrl); |
128 | |
129 | QTimer::singleShot(interval: 0, receiver: this, slot: &QNetworkReply::finished); |
130 | } |
131 | |
132 | protected: |
133 | void abort() override { TRACE_OBJ } |
134 | qint64 readData(char*, qint64) override { TRACE_OBJ return qint64(-1); } |
135 | }; |
136 | |
137 | // -- HelpNetworkAccessManager |
138 | |
139 | class HelpNetworkAccessManager : public QNetworkAccessManager |
140 | { |
141 | public: |
142 | HelpNetworkAccessManager(QObject *parent); |
143 | |
144 | protected: |
145 | QNetworkReply *createRequest(Operation op, |
146 | const QNetworkRequest &request, QIODevice *outgoingData = nullptr) override; |
147 | }; |
148 | |
149 | HelpNetworkAccessManager::HelpNetworkAccessManager(QObject *parent) |
150 | : QNetworkAccessManager(parent) |
151 | { |
152 | TRACE_OBJ |
153 | } |
154 | |
155 | QNetworkReply *HelpNetworkAccessManager::createRequest(Operation, const QNetworkRequest &request, QIODevice*) |
156 | { |
157 | TRACE_OBJ |
158 | |
159 | QByteArray data; |
160 | const QUrl url = request.url(); |
161 | QUrl redirectedUrl; |
162 | switch (HelpBrowserSupport::resolveUrl(url, targetUrl: &redirectedUrl, data: &data)) { |
163 | case HelpBrowserSupport::UrlRedirect: |
164 | return new HelpRedirectNetworkReply(request, redirectedUrl); |
165 | case HelpBrowserSupport::UrlLocalData: { |
166 | const QString mimeType = HelpViewer::mimeFromUrl(url); |
167 | return new HelpNetworkReply(request, data, mimeType); |
168 | } |
169 | case HelpBrowserSupport::UrlResolveError: |
170 | break; |
171 | } |
172 | return new HelpNetworkReply(request, HelpBrowserSupport::msgHtmlErrorPage(url: request.url()).toUtf8(), |
173 | QStringLiteral("text/html" )); |
174 | } |
175 | |
176 | QByteArray HelpBrowserSupport::fileDataForLocalUrl(const QUrl &url) |
177 | { |
178 | return HelpEngineWrapper::instance().fileData(url); |
179 | } |
180 | |
181 | HelpBrowserSupport::ResolveUrlResult HelpBrowserSupport::resolveUrl(const QUrl &url, |
182 | QUrl *targetUrlP, |
183 | QByteArray *dataP) |
184 | { |
185 | const HelpEngineWrapper &engine = HelpEngineWrapper::instance(); |
186 | |
187 | const QUrl targetUrl = engine.findFile(url); |
188 | if (!targetUrl.isValid()) |
189 | return UrlResolveError; |
190 | |
191 | if (targetUrl != url) { |
192 | if (targetUrlP) |
193 | *targetUrlP = targetUrl; |
194 | return UrlRedirect; |
195 | } |
196 | |
197 | if (dataP) |
198 | *dataP = HelpBrowserSupport::fileDataForLocalUrl(url: targetUrl); |
199 | return UrlLocalData; |
200 | } |
201 | |
202 | QNetworkAccessManager *HelpBrowserSupport::createNetworkAccessManager(QObject *parent) |
203 | { |
204 | return new HelpNetworkAccessManager(parent); |
205 | } |
206 | |
207 | QT_END_NAMESPACE |
208 | |