1 /*!
2 * \copyright Copyright (c) 2014-2021 Governikus GmbH & Co. KG, Germany
3 */
4
5 #include "WebserviceActivationHandler.h"
6
7 #include "Env.h"
8 #include "HttpServerStatusParser.h"
9 #include "LanguageLoader.h"
10 #include "Template.h"
11 #include "VersionInfo.h"
12 #include "VersionNumber.h"
13 #include "WebserviceActivationContext.h"
14
15 #include <QCoreApplication>
16 #include <QFile>
17 #include <QLoggingCategory>
18 #include <QUrlQuery>
19
20 using namespace governikus;
21
Q_DECLARE_LOGGING_CATEGORY(activation)22 Q_DECLARE_LOGGING_CATEGORY(activation)
23
24 WebserviceActivationHandler::WebserviceActivationHandler()
25 : ActivationHandler()
26 , mServer()
27 {
28 }
29
30
~WebserviceActivationHandler()31 WebserviceActivationHandler::~WebserviceActivationHandler()
32 {
33 }
34
35
stop()36 void WebserviceActivationHandler::stop()
37 {
38 mServer.reset();
39 }
40
41
start()42 bool WebserviceActivationHandler::start()
43 {
44 mServer = Env::getShared<HttpServer>();
45
46 if (mServer->isListening())
47 {
48 connect(mServer.data(), &HttpServer::fireNewHttpRequest, this, &WebserviceActivationHandler::onNewRequest);
49 return true;
50 }
51
52 const quint16 port = HttpServer::cPort;
53 HttpServerStatusParser parser(port);
54 QString serverAppName = parser.request() ? parser.getVersionInfo().getName() : parser.getServerHeader();
55 if (serverAppName.startsWith(VersionInfo::getInstance().getName()))
56 {
57 qCDebug(activation) << "We are already started... calling ShowUI";
58 HttpServerRequestor requestor;
59 if (requestor.request(HttpServerRequestor::createUrl(QStringLiteral("ShowUI=") + UiModule::CURRENT, port)).isNull())
60 {
61 qCWarning(activation) << "ShowUI request timed out";
62 }
63 }
64 else
65 {
66 qCCritical(activation) << "Cannot start application. Port on localhost is already bound by another program:" << serverAppName;
67
68 //: ERROR ALL_PLATFORMS An unknown programme is using the local port on which the AA2 listens.
69 QString msg = tr("An unknown program uses the required port (%1). Please exit the other program and try again!").arg(port);
70 if (!serverAppName.isEmpty())
71 {
72 //: ERROR ALL_PLATFORMS A known programme is using the local port on which the AA2 listens.
73 msg = tr("The program (%1) uses the required port (%2). Please close %1 and try again!").arg(serverAppName).arg(port);
74 }
75 Q_EMIT fireShowUserInformation(msg);
76 }
77
78 mServer.reset();
79 return false;
80 }
81
82
getQueryParameter(const QUrl & pUrl)83 QMap<QString, QString> WebserviceActivationHandler::getQueryParameter(const QUrl& pUrl)
84 {
85 QMap<QString, QString> map;
86
87 const auto queryItems = QUrlQuery(pUrl).queryItems();
88 for (auto& item : queryItems)
89 {
90 map.insert(item.first.toLower(), item.second);
91 }
92
93 return map;
94 }
95
96
onNewRequest(const QSharedPointer<HttpRequest> & pRequest)97 void WebserviceActivationHandler::onNewRequest(const QSharedPointer<HttpRequest>& pRequest)
98 {
99 static const QString SHOW_UI = QStringLiteral("showui");
100 static const QString STATUS = QStringLiteral("status");
101 static const QString TCTOKEN_URL = QStringLiteral("tctokenurl");
102
103 const auto& url = pRequest->getUrl();
104 if (url.path() == QLatin1String("/eID-Client"))
105 {
106 const auto urlParameter = getQueryParameter(url);
107
108 if (urlParameter.contains(SHOW_UI))
109 {
110 qCDebug(activation) << "Request type: showui";
111 UiModule module = Enum<UiModule>::fromString(urlParameter.value(SHOW_UI).toUpper(), UiModule::DEFAULT);
112 handleShowUiRequest(module, pRequest);
113 return;
114 }
115 else if (urlParameter.contains(STATUS))
116 {
117 qCDebug(activation) << "Request type: status";
118 StatusFormat statusFormat = Enum<StatusFormat>::fromString(urlParameter.value(STATUS).toUpper(), StatusFormat::PLAIN);
119 handleStatusRequest(statusFormat, pRequest);
120 return;
121 }
122 else if (urlParameter.contains(TCTOKEN_URL))
123 {
124 qCDebug(activation) << "Request type: authentication";
125 Q_EMIT fireAuthenticationRequest(QSharedPointer<WebserviceActivationContext>::create(pRequest));
126 return;
127 }
128 }
129 else if (url.path() == QLatin1String("/favicon.ico"))
130 {
131 handleImageRequest(pRequest, QStringLiteral(":/images/desktop/npa.ico")); // it MUST be an ICO!
132 return;
133 }
134 else if (url.path().startsWith(QLatin1String("/images/")) && !url.path().contains(QLatin1String("../")))
135 {
136 handleImageRequest(pRequest, QStringLiteral(":%1").arg(url.path()));
137 return;
138 }
139
140 qCWarning(activation) << "Request type: unknown";
141
142 Template htmlTemplate = Template::fromFile(QStringLiteral(":/html_templates/error.html"));
143 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
144 htmlTemplate.setContextParameter(QStringLiteral("TITLE"), tr("404 Not found"));
145 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
146 htmlTemplate.setContextParameter(QStringLiteral("MESSAGE_HEADER"), tr("Invalid request"));
147 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
148 htmlTemplate.setContextParameter(QStringLiteral("MESSAGE_HEADER_EXPLANATION"), tr("Your browser sent a request that couldn't be interpreted."));
149 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
150 htmlTemplate.setContextParameter(QStringLiteral("ERROR_MESSAGE_LABEL"), tr("Error message"));
151 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
152 htmlTemplate.setContextParameter(QStringLiteral("ERROR_MESSAGE"), tr("Unknown request: %1").arg(url.toString()));
153 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
154 htmlTemplate.setContextParameter(QStringLiteral("REPORT_HEADER"), tr("Would you like to report this error?"));
155 htmlTemplate.setContextParameter(QStringLiteral("REPORT_LINK"), QStringLiteral("https://www.ausweisapp.bund.de/%1/aa2/report").arg(LanguageLoader::getLocalCode()));
156 //: ERROR ALL_PLATFORMS The broweser sent an unknown or faulty request, part of an HTML error page.
157 htmlTemplate.setContextParameter(QStringLiteral("REPORT_BUTTON"), tr("Report now"));
158 QByteArray htmlPage = htmlTemplate.render().toUtf8();
159
160 HttpResponse response;
161 response.setStatus(HTTP_STATUS_NOT_FOUND);
162 response.setBody(htmlPage, QByteArrayLiteral("text/html; charset=utf-8"));
163 pRequest->send(response);
164 }
165
166
handleShowUiRequest(UiModule pUiModule,const QSharedPointer<HttpRequest> & pRequest)167 void WebserviceActivationHandler::handleShowUiRequest(UiModule pUiModule, const QSharedPointer<HttpRequest>& pRequest)
168 {
169 pRequest->send(HTTP_STATUS_OK);
170
171 QString userAgent = QString::fromLatin1(pRequest->getHeader(QByteArrayLiteral("user-agent")));
172 if (userAgent.startsWith(QCoreApplication::applicationName()))
173 {
174 QString version = userAgent.remove(QCoreApplication::applicationName() + QLatin1Char('/')).split(QLatin1Char(' ')).at(0);
175 VersionNumber callerVersion(version);
176
177 if (callerVersion > VersionNumber::getApplicationVersion())
178 {
179 qCWarning(activation) << "Current version is lower than caller version";
180 //: ERROR ALL_PLATFORMS The external request to show the UI requested a newer version than the one currently installed.
181 Q_EMIT fireShowUserInformation(tr("You tried to start a newer version (%1) of currently running AusweisApp2. Please stop the current version (%2) and start again!").arg(version, QCoreApplication::applicationVersion()));
182 return;
183 }
184 else if (callerVersion < VersionNumber::getApplicationVersion())
185 {
186 qCWarning(activation) << "Current version is higher than caller version";
187 //: ERROR ALL_PLATFORMS The external request to show the UI requested an older version than the one currently installed.
188 Q_EMIT fireShowUserInformation(tr("You tried to start an older version (%1) of currently running AusweisApp2. Please open the currently running version (%2)!").arg(version, QCoreApplication::applicationVersion()));
189 return;
190 }
191 }
192
193 Q_EMIT fireShowUiRequest(pUiModule);
194 }
195
196
handleImageRequest(const QSharedPointer<HttpRequest> & pRequest,const QString & pImagePath) const197 void WebserviceActivationHandler::handleImageRequest(const QSharedPointer<HttpRequest>& pRequest, const QString& pImagePath) const
198 {
199 HttpResponse response;
200
201 QFile imageFile(pImagePath);
202 if (imageFile.open(QIODevice::ReadOnly))
203 {
204 response.setStatus(HTTP_STATUS_OK);
205 response.setBody(imageFile.readAll(), guessImageContentType(pImagePath));
206 imageFile.close();
207 }
208 else
209 {
210 qCCritical(activation) << "Unknown image file requested" << pImagePath;
211 response.setStatus(HTTP_STATUS_NOT_FOUND);
212 response.setBody(QByteArrayLiteral("Not found"), QByteArrayLiteral("text/plain; charset=utf-8"));
213 }
214
215 pRequest->send(response);
216 }
217
218
guessImageContentType(const QString & pFileName) const219 QByteArray WebserviceActivationHandler::guessImageContentType(const QString& pFileName) const
220 {
221 if (pFileName.endsWith(QLatin1String(".ico"), Qt::CaseInsensitive))
222 {
223 return QByteArrayLiteral("image/x-icon");
224 }
225 if (pFileName.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) || pFileName.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive))
226 {
227 return QByteArrayLiteral("image/jpeg");
228 }
229 if (pFileName.endsWith(QLatin1String(".png"), Qt::CaseInsensitive))
230 {
231 return QByteArrayLiteral("image/png");
232 }
233 if (pFileName.endsWith(QLatin1String(".svg"), Qt::CaseInsensitive))
234 {
235 return QByteArrayLiteral("image/svg+xml");
236 }
237 qCWarning(activation) << "Unknown content type, returning default for image" << pFileName;
238 return QByteArrayLiteral("image");
239 }
240
241
handleStatusRequest(StatusFormat pStatusFormat,const QSharedPointer<HttpRequest> & pRequest) const242 void WebserviceActivationHandler::handleStatusRequest(StatusFormat pStatusFormat, const QSharedPointer<HttpRequest>& pRequest) const
243 {
244 qCDebug(activation) << "Create response with status format:" << pStatusFormat;
245
246 HttpResponse response(HTTP_STATUS_OK);
247 response.setHeader(QByteArrayLiteral("Access-Control-Allow-Origin"), QByteArrayLiteral("*"));
248 switch (pStatusFormat)
249 {
250 case StatusFormat::PLAIN:
251 response.setBody(VersionInfo::getInstance().toText().toUtf8(), QByteArrayLiteral("text/plain; charset=utf-8"));
252 break;
253
254 case StatusFormat::JSON:
255 response.setBody(VersionInfo::getInstance().toJson(), QByteArrayLiteral("application/json"));
256 break;
257 }
258
259 pRequest->send(response);
260 }
261