1 /*!
2  * \copyright Copyright (c) 2016-2021 Governikus GmbH & Co. KG, Germany
3  */
4 
5 #include "AppUpdateData.h"
6 
7 #include "AppSettings.h"
8 #include "VersionNumber.h"
9 
10 #include <QDir>
11 #include <QFile>
12 #include <QJsonArray>
13 #include <QJsonParseError>
14 #include <QLoggingCategory>
15 #include <QOperatingSystemVersion>
16 
17 using namespace governikus;
18 
Q_DECLARE_LOGGING_CATEGORY(appupdate)19 Q_DECLARE_LOGGING_CATEGORY(appupdate)
20 
21 AppUpdateData::AppUpdateData(const GlobalStatus& pParsingResult)
22 	: mMinOsVersion()
23 	, mDate()
24 	, mVersion()
25 	, mUrl()
26 	, mSize(-1)
27 	, mChecksumUrl()
28 	, mNotesUrl()
29 	, mNotes()
30 	, mChecksumAlgorithm(QCryptographicHash::Sha256)
31 	, mChecksum()
32 	, mChecksumValid(false)
33 	, mUpdatePackagePath()
34 	, mParsingResult(pParsingResult)
35 {
36 }
37 
38 
AppUpdateData(const QByteArray & pData)39 AppUpdateData::AppUpdateData(const QByteArray& pData)
40 	: AppUpdateData(GlobalStatus::Code::No_Error)
41 {
42 	QJsonParseError jsonError;
43 
44 	const auto& json = QJsonDocument::fromJson(pData, &jsonError);
45 	if (jsonError.error != QJsonParseError::NoError)
46 	{
47 		qCWarning(appupdate) << "Cannot parse json data:" << jsonError.errorString();
48 		mParsingResult = GlobalStatus::Code::Downloader_Data_Corrupted;
49 		return;
50 	}
51 
52 	const auto& obj = json.object();
53 	const QJsonValue& items = obj[QLatin1String("items")];
54 
55 	if (!items.isArray())
56 	{
57 		qCWarning(appupdate) << "Field 'items' cannot be parsed";
58 		mParsingResult = GlobalStatus::Code::Downloader_Data_Corrupted;
59 		return;
60 	}
61 
62 	const auto& itemArray = items.toArray();
63 	const auto& end = itemArray.constEnd();
64 	for (auto iter = itemArray.constBegin(); iter != end; ++iter)
65 	{
66 		if (iter->isObject())
67 		{
68 			auto jsonObject = iter->toObject();
69 			if (checkPlatformObject(jsonObject))
70 			{
71 				mMinOsVersion = QVersionNumber::fromString(jsonObject[QLatin1String("minimum_platform")].toString());
72 				mVersion = jsonObject[QLatin1String("version")].toString();
73 				mUrl = QUrl(jsonObject[QLatin1String("url")].toString());
74 				mNotesUrl = QUrl(jsonObject[QLatin1String("notes")].toString());
75 				mDate = QDateTime::fromString(jsonObject[QLatin1String("date")].toString(), Qt::ISODate);
76 				mChecksumUrl = QUrl(jsonObject[QLatin1String("checksum")].toString());
77 				mSize = qMax(-1, jsonObject[QLatin1String("size")].toInt(-1));
78 				return;
79 			}
80 		}
81 		else
82 		{
83 			qCWarning(appupdate) << "Object of field 'items' cannot be parsed";
84 			mParsingResult = GlobalStatus::Code::Downloader_Data_Corrupted;
85 			return;
86 		}
87 	}
88 
89 	mParsingResult = GlobalStatus::Code::Downloader_Missing_Platform;
90 	qCWarning(appupdate) << "No matching platform found in update json";
91 }
92 
93 
isValid() const94 bool AppUpdateData::isValid() const
95 {
96 	// Valid means = required data!
97 	return !mVersion.isEmpty() &&
98 		   mUrl.isValid() &&
99 		   !mUrl.fileName().isEmpty() &&
100 		   mChecksumUrl.isValid() &&
101 		   !mChecksumUrl.fileName().isEmpty() &&
102 		   mNotesUrl.isValid();
103 }
104 
105 
getParsingResult() const106 const GlobalStatus& AppUpdateData::getParsingResult() const
107 {
108 	return mParsingResult;
109 }
110 
111 
isCompatible() const112 bool AppUpdateData::isCompatible() const
113 {
114 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
115 	const auto osv = QOperatingSystemVersion::current();
116 	const auto currentVersion = QVersionNumber(osv.majorVersion(), osv.minorVersion(), osv.microVersion());
117 	return currentVersion >= mMinOsVersion;
118 
119 #else
120 	return true;
121 
122 #endif
123 }
124 
125 
getDate() const126 const QDateTime& AppUpdateData::getDate() const
127 {
128 	return mDate;
129 }
130 
131 
getVersion() const132 const QString& AppUpdateData::getVersion() const
133 {
134 	return mVersion;
135 }
136 
137 
getUrl() const138 const QUrl& AppUpdateData::getUrl() const
139 {
140 	return mUrl;
141 }
142 
143 
getSize() const144 int AppUpdateData::getSize() const
145 {
146 	return mSize;
147 }
148 
149 
getChecksumUrl() const150 const QUrl& AppUpdateData::getChecksumUrl() const
151 {
152 	return mChecksumUrl;
153 }
154 
155 
getNotesUrl() const156 const QUrl& AppUpdateData::getNotesUrl() const
157 {
158 	return mNotesUrl;
159 }
160 
161 
setNotes(const QString & pNotes)162 void AppUpdateData::setNotes(const QString& pNotes)
163 {
164 	mNotes = pNotes;
165 }
166 
167 
getNotes() const168 const QString& AppUpdateData::getNotes() const
169 {
170 	return mNotes;
171 }
172 
173 
setChecksum(const QByteArray & pChecksum,QCryptographicHash::Algorithm pAlgorithm)174 void AppUpdateData::setChecksum(const QByteArray& pChecksum, QCryptographicHash::Algorithm pAlgorithm)
175 {
176 	if (mChecksum == pChecksum && mChecksumAlgorithm == pAlgorithm)
177 	{
178 		return;
179 	}
180 
181 	mChecksumAlgorithm = pAlgorithm;
182 
183 	if (pChecksum.isEmpty())
184 	{
185 		mChecksum.clear();
186 		mChecksumValid = false;
187 		return;
188 	}
189 
190 	mChecksum = pChecksum.split(' ').first();
191 	verifyChecksum();
192 }
193 
194 
getChecksum() const195 const QByteArray& AppUpdateData::getChecksum() const
196 {
197 	return mChecksum;
198 }
199 
200 
verifyChecksum()201 void AppUpdateData::verifyChecksum()
202 {
203 	if (mUpdatePackagePath.isEmpty()
204 			|| mChecksum.isEmpty()
205 			|| !QFile::exists(mUpdatePackagePath))
206 	{
207 		mChecksumValid = false;
208 		return;
209 	}
210 
211 	qCDebug(appupdate) << "Verify checksum with algorithm:" << mChecksumAlgorithm;
212 
213 	QFile file(mUpdatePackagePath);
214 	file.open(QFile::ReadOnly);
215 	QCryptographicHash hasher(mChecksumAlgorithm);
216 	hasher.addData(&file);
217 	file.close();
218 
219 	mChecksumValid = hasher.result().toHex() == mChecksum;
220 }
221 
222 
isChecksumValid() const223 bool AppUpdateData::isChecksumValid() const
224 {
225 	return mChecksumValid;
226 }
227 
228 
setUpdatePackagePath(const QString & pFile)229 void AppUpdateData::setUpdatePackagePath(const QString& pFile)
230 {
231 	mUpdatePackagePath = pFile;
232 	verifyChecksum();
233 }
234 
235 
getUpdatePackagePath() const236 QString AppUpdateData::getUpdatePackagePath() const
237 {
238 	return QDir::toNativeSeparators(mUpdatePackagePath);
239 }
240 
241 
checkPlatformObject(const QJsonObject & pJson)242 bool AppUpdateData::checkPlatformObject(const QJsonObject& pJson)
243 {
244 	const auto& platform = pJson[QLatin1String("platform")].toString();
245 
246 	if (!isPlatform(platform))
247 	{
248 		qCDebug(appupdate) << "Unused platform:" << platform;
249 		return false;
250 	}
251 
252 	qCDebug(appupdate) << "Found platform:" << platform;
253 	return true;
254 }
255 
256 
isPlatform(const QString & pPlatform)257 bool AppUpdateData::isPlatform(const QString& pPlatform)
258 {
259 #ifdef Q_OS_WIN
260 	if (pPlatform == QLatin1String("win"))
261 	{
262 		return true;
263 	}
264 #endif
265 
266 #ifdef Q_OS_MACOS
267 	if (pPlatform == QLatin1String("mac"))
268 	{
269 		return true;
270 	}
271 #endif
272 
273 #if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
274 	if (pPlatform == QLatin1String("src"))
275 	{
276 		return true;
277 	}
278 #endif
279 
280 	return false;
281 }
282