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