files.cpp 27.4 KB
Newer Older
1
/*
2
 * Copyright (C) 2014-2018 CZ.NIC
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QDebug>
#include <QDesktopServices>
26
#include <QFileInfo>
Martin Straka's avatar
Martin Straka committed
27
#include <QQmlEngine>
28

29
#include "ios/src/url_opener.h"
30
#include "src/auxiliaries/attachment_helper.h"
31
#include "src/auxiliaries/email_helper.h"
32
#include "src/common.h"
33
#include "src/crypto/crypto.h"
34
#include "src/dialogues/dialogues.h"
35
#include "src/files.h"
36
#include "src/global.h"
37 38
#include "src/io/filesystem.h"
#include "src/models/accountmodel.h"
39
#include "src/net/xml_layer.h"
40
#include "src/qml_interaction/attachment_data.h"
41
#include "src/qml_interaction/message_envelope.h"
42
#include "src/settings.h"
43
#include "src/sqlite/dbs.h"
44 45
#include "src/sqlite/file_db_container.h"
#include "src/sqlite/message_db_container.h"
46
#include "src/sqlite/zfo_db.h"
47

Martin Straka's avatar
Martin Straka committed
48 49 50
void Files::declareQML(void)
{
	qmlRegisterType<Files>("cz.nic.mobileDatovka.files", 1, 0, "FileIdType");
51
	qmlRegisterType<Files>("cz.nic.mobileDatovka.files", 1, 0, "MsgAttachFlag");
Martin Straka's avatar
Martin Straka committed
52
	qRegisterMetaType<Files::FileIdType>();
53
	qRegisterMetaType<Files::MsgAttachFlag>();
Martin Straka's avatar
Martin Straka committed
54 55
}

56 57
Files::Files(QObject *parent)
    : QObject(parent)
58 59 60
{
}

61
void Files::attachmentSavingNotification(const QString &destPath)
62
{
63 64 65 66
	QFileInfo fi(destPath);

	Dialogues::errorMessage(
	    !destPath.isEmpty() ? Dialogues::INFORMATION : Dialogues::CRITICAL,
67 68 69 70 71
	    tr("Attachment saving"),
	    !destPath.isEmpty() ? tr("Attachments have been saved.") :
	        tr("Attachments have not been saved!"),
	    !destPath.isEmpty() ?
	        tr("Path: '%1'").arg(fi.absolutePath()) : QString());
72 73 74 75 76 77
}

void Files::deleteExpiredFilesFromDbs(int days)
{
	qDebug("%s()", __func__);

78 79
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
80 81
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
82 83 84 85
		Q_ASSERT(0);
		return;
	}

86
	QStringList msgIDList;
87
	QStringList userNameList(GlobInstcs::acntMapPtr->keys());
88
	foreach (const QString &userName, userNameList) {
89 90
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
91
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
92 93 94
		if (fDb == Q_NULLPTR) {
			qCritical() << "ERROR: File database cannot open!"
			    << userName;
95 96 97 98 99 100 101
			return;
		}
		msgIDList = fDb->cleanFilesInDb(days);
		if (msgIDList.isEmpty()) {
			continue;
		}

102 103
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
104
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
105
		if (msgDb == Q_NULLPTR) {
106
			qCritical() << "ERROR: Message database cannot open!";
107 108
			return;
		}
109
		msgDb->beginTransaction();
110 111 112 113
		foreach (const QString &msgId, msgIDList) {
			msgDb->setAttachmentDownloaded(msgId.toLongLong(),
			    false);
		}
114
		msgDb->commitTransaction();
115 116 117
	}
}

118 119 120 121 122 123 124 125 126 127 128
QString Files::getAttachmentFileIcon(const QString &fileName)
{
	return getAttachmentFileIconFromFileExtension(fileName);
}

qint64 Files::getAttachmentSizeInBytes(const QString &filePath)
{
	QFileInfo fileInfo(filePath);
	return fileInfo.size();
}

129
QByteArray Files::getFileRawContentFromDb(const QString &userName, int fileId)
130 131 132
{
	qDebug("%s()", __func__);

133
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
134 135
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
136 137 138 139
		Q_ASSERT(0);
		return QByteArray();
	}

140 141
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
142
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
143

144 145 146 147 148
	if (fDb == Q_NULLPTR) {
		qCritical() << "Cannot open file database!";
		return QByteArray();
	}

149
	return base64ToRaw(fDb->getFileContentFromDb(fileId).content.toUtf8());
150 151
}

152
void Files::openAttachmentFromDb(const QString &userName, int fileId)
153 154 155
{
	qDebug("%s()", __func__);

156
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
157 158
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
159 160 161 162
		Q_ASSERT(0);
		return;
	}

163 164
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
165
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
166

167 168
	if (fDb == Q_NULLPTR) {
		qCritical() << "Cannot open file database!";
169 170 171
		return;
	}

172 173
	FileDb::FileData file = fDb->getFileContentFromDb(fileId);

174
	openAttachment(file.filename, file.content.toUtf8());
175 176
}

177 178
void Files::openAttachment(const QString &fileName,
    const QByteArray &base64Data)
179 180
{
	Q_ASSERT(!fileName.isEmpty());
181
	Q_ASSERT(!base64Data.isEmpty());
182

183 184
	if (fileName.isEmpty() || base64Data.isEmpty()) {
		qCritical() << "File name or its content is empty!";
185 186 187
		return;
	}

188 189 190
	if (isZfoFile(fileName)) {
		/* Don't open zfo files from here. */
		qCritical() << "This should open ZFO files by itself.";
191
		Q_ASSERT(0);
192 193 194
		return;
	}

195
	QString filePath(writeFile(appTmpDirPath(), fileName,
196
	    base64ToRaw(base64Data)));
197 198

	if (!filePath.isEmpty()) {
199
		qInfo() << "Creating temporary file" << filePath;
200 201 202 203 204 205 206 207 208 209 210 211 212 213
		openAttachmentFromPath(filePath);
	} else {
		qCritical() << "Cannot create temporary file for" << fileName;
		Dialogues::errorMessage(Dialogues::CRITICAL,
		    tr("Open attachment error"),
		    tr("Cannot save selected file to disk for opening."),
		    QString());
	}
}

void Files::openAttachmentFromPath(const QString &filePath)
{
	if (filePath.isEmpty()) {
		qCritical() << "File path is empty!";
214
		Q_ASSERT(0);
215 216 217 218 219 220
		return;
	}

	if (isZfoFile(filePath)) {
		/* Don't open zfo files from here. */
		qCritical() << "This should open ZFO files by itself.";
221
		Q_ASSERT(0);
222 223
		return;
	}
224

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
#ifdef Q_OS_IOS

	UrlOpener urlOpener;
	urlOpener.openFile(filePath);

#else

	if (!QDesktopServices::openUrl(QUrl::fromLocalFile(filePath))) {
		Dialogues::errorMessage(Dialogues::CRITICAL,
		    tr("Open attachment error"),
		    tr("There is no application to open this file format."),
		    tr("File: '%1'").arg(filePath));
	}

#endif
240 241
}

242
void Files::sendMsgFilesWithEmail(const QString &userName, qint64 msgId,
243
    MsgAttachFlags attachFlags)
244 245 246
{
	qDebug("%s()", __func__);

247 248
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
249
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
250
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
251
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
252 253 254 255
		Q_ASSERT(0);
		return;
	}

256 257 258 259 260 261 262
	if (userName.isEmpty() || msgId <= 0) {
		return;
	}

	QString body;
	QString subject;

263
	/* Fill email subject and email body */
264 265
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
266
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
267
	if (msgDb == Q_NULLPTR) {
Karel Slaný's avatar
Karel Slaný committed
268 269
		qCritical("Cannot open message database for '%s'.",
		    userName.toUtf8().constData());
270 271 272
		return;
	}
	if (!msgDb->getMessageDataForEmail(msgId, body, subject)) {
Karel Slaný's avatar
Karel Slaný committed
273 274
		qCritical("Missing data of message '%s'.",
		    QString::number(msgId).toUtf8().constData());
275 276 277 278
		return;
	}

	QList<FileDb::FileData> filelist;
279 280

	/* Get attachment files from database if needed */
281
	if (attachFlags & MSG_ATTACHS) {
282 283 284 285
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
Karel Slaný's avatar
Karel Slaný committed
286 287
			qCritical("Cannot open file database for '%s'.",
			    userName.toUtf8().constData());
288 289 290 291 292
			return;
		}

		filelist = fDb->getFilesFromDb(msgId);
		if (filelist.isEmpty()) {
Karel Slaný's avatar
Karel Slaný committed
293 294
			qCritical("Missing attachments for message '%s'.",
			    QString::number(msgId).toUtf8().constData());
295 296
			return;
		}
297 298
	}

299
	/* Get zfo file from database if needed */
300
	if (attachFlags & MSG_ZFO) {
301
		FileDb::FileData msgData;
Karel Slaný's avatar
Karel Slaný committed
302 303
		msgData.content = GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount());
304
		if (msgData.content.isEmpty()) {
Karel Slaný's avatar
Karel Slaný committed
305 306
			qCritical("Missing zfo data for message '%s'.",
			    QString::number(msgId).toUtf8().constData());
307 308 309 310
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("ZFO missing"),
			    tr("ZFO message is not present in local database."),
			    tr("Download complete message again to obtain it."));
311 312 313 314 315 316
			Q_ASSERT(0);
			return;
		}
		msgData.filename = QString("DZ_%1.zfo").arg(msgId);
		filelist.append(msgData);
	}
317

318
	/* Create email content, email attachment path, email eml content */
319 320
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(QString::number(msgId)));
321 322
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
323
	QStringList filePathList;
324

325
	/* Write attachment files to email directory */
326
	foreach (const FileDb::FileData &file, filelist) {
327
		QString fileName = file.filename;
328
		if (fileName.isEmpty()) {
Karel Slaný's avatar
Karel Slaný committed
329
			qCritical("%s", "File name is empty.");
330 331
			return;
		}
332
		fileName = writeFile(targetPath, fileName,
333
		    base64ToRaw(file.content.toUtf8()));
334
		filePathList.append(fileName);
335 336 337 338 339 340
		addAttachmentToEmailMessage(emailMessage, file.filename,
		    file.content.toUtf8(), boundary);
	}

	finishEmailMessage(emailMessage, boundary);

341 342
	/* Send email */
	sendEmail(emailMessage, filePathList, subject, body, msgId);
343
}
344 345 346 347 348

void Files::deleteFileDb(const QString &userName)
{
	qDebug("%s()", __func__);

349 350
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
351 352
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
353 354 355 356
		Q_ASSERT(0);
		return;
	}

357
	int msgResponse = Dialogues::message(Dialogues::QUESTION,
358 359 360
	    tr("Delete files: %1").arg(userName),
	    tr("Do you want to clean up the file database of account '%1'?").arg(userName),
	    tr("Note: All attachment files of messages will be removed from the database."),
361 362
	    Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
363 364 365
		return;
	}

366 367
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
368
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
369
	if (fDb == Q_NULLPTR) {
370
		qCritical() << "ERROR: File database open error!";
371 372
		return;
	}
373
	if (!GlobInstcs::fileDbsPtr->deleteDb(fDb)) {
374
		qCritical() << "ERROR: File database could not delete!";
375 376 377
		return;
	}

378 379
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
380
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
381
	if (msgDb == Q_NULLPTR) {
382
		qCritical() << "ERROR: Message database cannot open!";
383 384 385
		return;
	}
	if (!msgDb->setAttachmentsDownloaded(false)) {
386
		qCritical() << "ERROR: Message data mssing!";
387 388 389
		return;
	}
}
390

Karel Slaný's avatar
Karel Slaný committed
391
void Files::vacuumFileDbs(void)
392 393 394
{
	qDebug("%s()", __func__);

395
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
396 397
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
398 399 400 401
		Q_ASSERT(0);
		return;
	}

Karel Slaný's avatar
Karel Slaný committed
402
	emit statusBarTextChanged(tr("Vacuum databases"), true);
403

404
	QStringList userNameList(GlobInstcs::acntMapPtr->keys());
405
	foreach (const QString &userName, userNameList) {
406 407
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
408
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
409
		if (fDb == Q_NULLPTR) {
410 411
			qCritical() << "ERROR: File database cannot open!"
			    << userName;
412 413
			return;
		}
Karel Slaný's avatar
Karel Slaný committed
414
		fDb->vacuumFileDb();
415 416
	}

Karel Slaný's avatar
Karel Slaný committed
417
	emit statusBarTextChanged(tr("Operation Vacuum has finished"), false);
418
}
419 420 421 422 423

bool Files::deleteAttachmentsFromDb(const QString &userName, qint64 msgId)
{
	qDebug("%s()", __func__);

424 425
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
426 427
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
428 429 430 431
		Q_ASSERT(0);
		return false;
	}

432 433
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
434
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
435
	if (fDb == Q_NULLPTR) {
436
		qCritical() << "ERROR: File database cannot open!" << userName;
437 438 439 440
		return false;
	}

	if (fDb->deleteFilesFromDb(msgId)) {
441 442
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
443
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
444
		if (msgDb == Q_NULLPTR) {
445
			qCritical() << "ERROR: Message database cannot open!";
446 447 448 449 450 451 452
			return false;
		}
		return msgDb->setAttachmentDownloaded(msgId, false);
	}

	return false;
}
453

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
bool Files::fileReadable(const QString &filePath)
{
	if (filePath.isEmpty()) {
		Q_ASSERT(0);
		qCritical() << "Target ZFO path is empty!";
		return false;
	}

	{
		QFileInfo fileInfo(filePath);
		if (!fileInfo.isFile() || !fileInfo.isReadable()) {
			qCritical() << "Cannot open ZFO file from" << filePath;
			return false;
		}
	}

	return true;
}

bool Files::isZfoFile(const QString &fileName)
{
	return QFileInfo(fileName).suffix().toLower() == QStringLiteral("zfo");
}

QByteArray Files::rawFileContent(const QString &filePath)
{
	if (!fileReadable(filePath)) {
		return QByteArray();
	}

	QFile file(filePath);
	if (!file.open(QIODevice::ReadOnly)) {
		Q_ASSERT(0);
		qCritical() << "Cannot open file" << filePath;
		return QByteArray();
	}

	QByteArray rawData(file.readAll());
	file.close();
	return rawData;
}

QByteArray Files::base64ToRaw(const QByteArray &base64Data)
{
	return QByteArray::fromBase64(base64Data);
}

501 502
MsgInfo *Files::zfoData(const QVariant &attachModelVariant,
    const QByteArray &rawZfoData)
503
{
504
	enum MsgInfo::ZfoType type = MsgInfo::TYPE_UNKNOWN;
505
	QString idStr, annot, htmlDescr, emailBody;
506

507
	bool ret = parseXmlData(&type, &idStr, &annot, &htmlDescr,
508
	    FileListModel::fromVariant(attachModelVariant),
509
	    &emailBody, getXmlFromCms(rawZfoData));
510

511
	return ret ?
512
	    new (std::nothrow) MsgInfo(type, idStr, annot, htmlDescr,
513
	        emailBody) :
514
	    new (std::nothrow) MsgInfo();
515 516 517 518 519 520 521
}

bool Files::setAttachmentModel(FileListModel &attachModel,
    const QString &userName, qint64 msgId)
{
	qDebug("%s()", __func__);

522
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
523 524
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
525 526 527 528
		Q_ASSERT(0);
		return false;
	}

529 530
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
531
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547

	if (fDb == Q_NULLPTR) {
		qCritical() << "Cannot open file database!";
		return false;
	}
	attachModel.clearAll();
	fDb->getFileListFromDb(attachModel, msgId);
	return true;
}

void Files::sendAttachmentEmailZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr, QString subject, QString body)
{
	qDebug("%s()", __func__);

	/* Obtain pointer to attachment model. */
548
	const FileListModel *attachModel =
549
	    FileListModel::fromVariant(attachModelVariant);
550 551 552 553 554 555 556 557 558 559 560 561 562 563
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

	/* Obtain message identifier. */
	bool ok = false;
	qint64 msgId = msgIdStr.toLongLong(&ok);
	if (!ok || (msgId < 0)) {
		return;
	}

	QStringList fileList;

564 565
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
566

567 568
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(msgIdStr));
569 570 571 572 573 574 575 576 577 578 579

	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
		/*
		 * On Android the attachment must be saved and the explicitly
		 * add into the email message.
		 */
		QByteArray base64Data(attachModel->data(idx,
		    FileListModel::ROLE_FILE_DATA).toByteArray());
		QString attachName(attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString());
580 581
		QString filePath(writeFile(targetPath, attachName,
		    base64ToRaw(base64Data)));
582 583 584 585 586 587 588 589 590 591
		fileList.append(filePath);
		addAttachmentToEmailMessage(emailMessage, attachName,
		    base64Data, boundary);
	}

	finishEmailMessage(emailMessage, boundary);

	sendEmail(emailMessage, fileList, subject, body, msgId);
}

592 593
void Files::saveMsgFilesToDisk(const QString &userName,
    const QString &msgIdStr, MsgAttachFlags attachFlags)
594 595 596
{
	qDebug("%s()", __func__);

597
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
598
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
599
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
600
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
601 602 603 604
		Q_ASSERT(0);
		return;
	}

605 606 607 608 609 610 611 612 613 614 615 616 617
	/* User name must be supplied. */
	if (userName.isEmpty()) {
		Q_ASSERT(0);
		return;
	}

	/* Obtain message identifier. */
	bool ok = false;
	qint64 msgId = msgIdStr.toLongLong(&ok);
	if (!ok || (msgId < 0)) {
		return;
	}

618
	QList<FileDb::FileData> filelist;
619

620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636
	/* Get attachment files from database if needed */
	if (attachFlags & MSG_ATTACHS) {
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
			qCritical("Cannot open file database for '%s'.",
			    userName.toUtf8().constData());
			return;
		}

		filelist = fDb->getFilesFromDb(msgId);
		if (filelist.isEmpty()) {
			qCritical("Missing attachments for message '%s'.",
			    QString::number(msgId).toUtf8().constData());
			return;
		}
Martin Straka's avatar
Martin Straka committed
637
	}
638

639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
	/* Get zfo file from database if needed */
	if (attachFlags & MSG_ZFO) {
		FileDb::FileData msgData;
		msgData.content = GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount());
		if (msgData.content.isEmpty()) {
			qCritical("Missing zfo data for message '%s'.",
			    QString::number(msgId).toUtf8().constData());
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("ZFO missing"),
			    tr("ZFO message is not present in local database."),
			    tr("Download complete message again to obtain it."));
			Q_ASSERT(0);
			return;
		}
		msgData.filename = QString("DZ_%1.zfo").arg(msgId);
		filelist.append(msgData);
656 657
	}

658
	QString filePath(appMsgAttachDirPath(msgIdStr));
659

660 661
	QString destPath;
	foreach (const FileDb::FileData &file, filelist) {
662
		destPath = writeFile(filePath, file.filename,
663
		    base64ToRaw(file.content.toUtf8()));
664 665 666 667 668 669 670 671 672 673 674 675
	}

	attachmentSavingNotification(destPath);
}

void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr)
{
	qDebug("%s()", __func__);

	/* Obtain pointer to attachment model. */
	const FileListModel *attachModel =
676
	    FileListModel::fromVariant(attachModelVariant);
677 678 679 680 681
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

682
	QString targetPath(appMsgAttachDirPath(msgIdStr));
683 684 685 686

	QString destPath;
	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
687 688
		destPath = writeFile(targetPath, attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString(),
689
		    base64ToRaw(attachModel->data(idx,
690
		    FileListModel::ROLE_FILE_DATA).toByteArray()));
691 692 693 694 695
	}

	attachmentSavingNotification(destPath);
}

696 697 698 699 700 701 702
void Files::deleteTmpFileFromStorage(const QString &filePath)
{
#if defined Q_OS_IOS
	QFile file(filePath);
	file.remove();
#else
	Q_UNUSED(filePath);
703
#endif
704 705
}

706 707 708
void Files::sendEmail(const QString &emailMessage, const QStringList &fileList,
    const QString &subject, const QString &body, qint64 msgId)
{
709 710 711 712
	Q_UNUSED(subject);
	Q_UNUSED(body);
	Q_UNUSED(emailMessage);
	Q_UNUSED(msgId);
713
	Q_UNUSED(fileList);
714 715

#if defined Q_OS_IOS
716 717 718 719

	UrlOpener urlOpener;
	urlOpener.createEmail(body, subject, fileList);

720
#elif defined Q_OS_ANDROID
721 722 723 724

	QDesktopServices::openUrl(QUrl("mailto:?subject=" + subject +
	    "&body=" + body));

725 726
#else

727
	QString tmpEmailFile = writeFile(
728
	    appEmailDirPath(QString::number(msgId)),
729 730 731 732
	    QString::number(msgId) + "_mail.eml", emailMessage.toUtf8());
	QDesktopServices::openUrl(QUrl::fromLocalFile(tmpEmailFile));

#endif
733 734
}

735
QByteArray Files::getXmlFromCms(const QByteArray &rawData)
736 737 738
{
	qDebug("%s()", __func__);

739 740 741
	if (rawData.isEmpty()) {
		Q_ASSERT(0);
		qCritical() << "File content is empty!";
742 743 744
		return QByteArray();
	}

745
	/* Decode CMS and obtain message XML data - uses OpenSSL. */
Martin Straka's avatar
Martin Straka committed
746
	void *xmlContent = Q_NULLPTR;
747
	size_t xmlContentLen = 0;
748
	if (extract_cms_data(rawData.data(), rawData.length(), &xmlContent,
Martin Straka's avatar
Martin Straka committed
749
	        &xmlContentLen) != 0) {
750 751 752
		return QByteArray();
	}
	if (xmlContentLen == 0) {
Martin Straka's avatar
Martin Straka committed
753
		free(xmlContent); xmlContent = Q_NULLPTR;
754 755 756
		return QByteArray();
	}

757
	QByteArray soap((char *)xmlContent, xmlContentLen);
Martin Straka's avatar
Martin Straka committed
758
	free(xmlContent); xmlContent = Q_NULLPTR;
759 760 761 762

	return soap;
}

763
QByteArray Files::decodeZfoFile(const QByteArray &base64ZfoData)
Martin Straka's avatar
Martin Straka committed
764 765 766
{
	qDebug("%s()", __func__);

767 768 769
	if (base64ZfoData.isEmpty()) {
		Q_ASSERT(0);
		qCritical() << "File content is empty.";
Martin Straka's avatar
Martin Straka committed
770 771 772
		return QByteArray();
	}

773
	/* decode signature from base64 and obtain something CMS message */
774
	return getXmlFromCms(base64ToRaw(base64ZfoData));
Martin Straka's avatar
Martin Straka committed
775 776
}

777
bool Files::parseXmlData(enum MsgInfo::ZfoType *type, QString *idStr,
778 779
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray xmlData)
780 781 782
{
	qDebug("%s()", __func__);

783 784
	if (xmlData.isEmpty()) {
		qCritical() << "XML content is empty!";
785
		return false;
786 787
	}

788
	xmlData.prepend("<?xml version='1.0' encoding='utf-8'?>"
789 790 791 792
	      "<SOAP-ENV:Envelope "
	      "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" "
	      "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
	      "<SOAP-ENV:Body>");
793
	xmlData.append("</SOAP-ENV:Body></SOAP-ENV:Envelope>");
794

795
	/* Test if zfo is message, delivery info or unknown format */
796
	if (xmlData.contains(QByteArray("MessageDownloadResponse"))) {
797 798
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_MESSAGE;
799
		}
800 801
		return parseAndShowXmlData(MsgInfo::TYPE_MESSAGE, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
802
	} else if (xmlData.contains(QByteArray("GetDeliveryInfoResponse"))) {
803 804
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_DELIVERY_INFO;
805
		}
806 807
		return parseAndShowXmlData(MsgInfo::TYPE_DELIVERY_INFO, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
808
	} else {
809 810 811 812
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_UNKNOWN;
		}
		qCritical() << "Unknown ZFO format";
813 814
	}

815
	return false;
816 817
}

818
bool Files::parseAndShowXmlData(enum MsgInfo::ZfoType type, QString *idStr,
819 820
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray &xmlData)
821 822 823
{
	qDebug("%s()", __func__);

824
	QXmlStreamReader xml;
825
	MsgEnvelope msg;
826
	QList<AttachmentData> fileList;
827
	QList<Messages::Event> eventList;
828

829 830 831 832 833
	if (type == MsgInfo::TYPE_UNKNOWN) {
		Q_ASSERT(0);
		return false;
	}

834 835
	/* parse message envelope and files */
	xml.addData(xmlData);
836 837 838
	while(!xml.atEnd() && !xml.hasError()) {
		QXmlStreamReader::TokenType token = xml.readNext();
		if (xml.error() != QXmlStreamReader::NoError) {
839
			return false;
840 841 842 843 844 845
		}
		if (token == QXmlStreamReader::StartDocument) {
			continue;
		}
		if (token == QXmlStreamReader::StartElement) {
			if (xml.name() == "dmDm") {
846
				XmlLayer::completeMessageParse(xml, msg, fileList);
847 848 849 850
			}
		}
	}

851
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
852 853 854 855 856 857 858 859 860 861
		/* parse delivery info */
		xml.clear();
		xml.addData(xmlData);
		while(!xml.atEnd() && !xml.hasError()){
			QXmlStreamReader::TokenType token = xml.readNext();
			if (token == QXmlStreamReader::StartDocument) {
				continue;
			}
			if (token == QXmlStreamReader::StartElement) {
				if (xml.name() == "dmEvent") {
862
					eventList.append(XmlLayer::parseEvent(xml));
863 864 865
				}
			}
		}
866 867 868 869
	}

	QString html = divStart;

870 871
	html += "<h3>" + tr("General") + "</h3>";
	html += strongInfoLine(tr("Subject"), msg.dmAnnotation());
872
	QString size = QString::number(msg.dmAttachmentSize());
873
	html += strongInfoLine(tr("Attachment size"),
874
	    (size == "0") ? "&lt;1 kB" : "~" + size + " kB");
875 876 877 878 879 880 881 882 883 884 885 886 887 888
	html += strongInfoLine(tr("Personal delivery"),
	    (msg.dmPersonalDelivery()) ? tr("Yes") : tr("No"));
	html += strongInfoLine(tr("Delivery by fiction"),
	    (msg.dmAllowSubstDelivery()) ? tr("Yes") : tr("No"));

	html += "<h3>" + tr("Sender") + "</h3>";
	html += strongInfoLine(tr("Databox ID"), msg.dbIDSender());
	html += strongInfoLine(tr("Name"), msg.dmSender());
	html += strongInfoLine(tr("Address"),msg.dmSenderAddress());

	html += "<h3>" + tr("Recipient") + "</h3>";
	html += strongInfoLine(tr("Databox ID"), msg.dbIDRecipient());
	html += strongInfoLine(tr("Name"), msg.dmRecipient());
	html += strongInfoLine(tr("Address"),msg.dmRecipientAddress());
889
	if (!msg.dmToHands().isEmpty()) {
890
		html += strongInfoLine(tr("To hands"), msg.dmToHands());
891
	}
892

893
	QString tmpHtml;
894
	if (!msg.dmSenderIdent().isEmpty()) {
895
		tmpHtml += strongInfoLine(tr("Our file mark"),
896
		    msg.dmSenderIdent());
897
	}
898
	if (!msg.dmSenderRefNumber().isEmpty()) {
899
		tmpHtml += strongInfoLine(tr("Our reference number"),
900
		    msg.dmSenderRefNumber());
901
	}
902
	if (!msg.dmRecipientIdent().isEmpty()) {
903
		tmpHtml += strongInfoLine(tr("Your file mark"),
904
		    msg.dmRecipientIdent());
905
	}
906
	if (!msg.dmRecipientRefNumber().isEmpty()) {
907
		tmpHtml += strongInfoLine(tr("Your reference number"),
908
		    msg.dmRecipientRefNumber());
909
	}
910
	if (!msg.dmLegalTitleLaw().isEmpty()) {
911
		tmpHtml += strongInfoLine(tr("Law"), msg.dmLegalTitleLaw());
912
	}
913
	if (!msg.dmLegalTitleYear().isEmpty()) {
914
		tmpHtml += strongInfoLine(tr("Year"), msg.dmLegalTitleYear());
915
	}
916
	if (!msg.dmLegalTitleSect().isEmpty()) {
917
		tmpHtml += strongInfoLine(tr("Section"), msg.dmLegalTitleSect());
918
	}
919
	if (!msg.dmLegalTitlePar().isEmpty()) {
920
		tmpHtml += strongInfoLine(tr("Paragraph"),
921
		    msg.dmLegalTitlePar());
922
	}
923
	if (!msg.dmLegalTitlePoint().isEmpty()) {
924
		tmpHtml += strongInfoLine(tr("Letter"), msg.dmLegalTitlePoint());
925 926
	}
	if (!tmpHtml.isEmpty()) {
927
		html += "<h3>" + tr("Additional info") + "</h3>";
928 929
		html += tmpHtml;
	}
930

931 932
	html += "<h3>" + tr("Message state") + "</h3>";
	html += strongInfoLine(tr("Delivery time"),
933
	    dateTimeStrFromDbFormat(
934
	        dateTimeStrToUTCDbFormat(msg.dmDeliveryTime()),
935
	        DATETIME_QML_FORMAT));
936
	html += strongInfoLine(tr("Accetance time"),
937
	    dateTimeStrFromDbFormat(
938
	        dateTimeStrToUTCDbFormat(msg.dmAcceptanceTime()),
939
	        DATETIME_QML_FORMAT));
940
	html += strongInfoLine(tr("Status"),
941
	    QString::number(msg.dmMessageStatus()));
942

943
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
944
		html += "<h3>" + tr("Events") + "</h3>";
945 946 947 948 949 950 951 952 953
		foreach (const Messages::Event &event, eventList) {
			html += divStart +
			    strongInfoLine(dateTimeStrFromDbFormat(
			        dateTimeStrToUTCDbFormat(event.dmEventTime),
			        DATETIME_QML_FORMAT), event.dmEventDescr)
			    + divEnd;
		}
	}

954 955
	html += divEnd;

956
	// Create body for email
957 958 959 960
	QString body = generateEmailBodyText(msg.dmID(), msg.dmSender(),
	    msg.dmRecipient(), dateTimeStrFromDbFormat(
	    dateTimeStrToUTCDbFormat(msg.dmAcceptanceTime()),
	    DATETIME_QML_FORMAT));
961

962
	if (idStr != Q_NULLPTR) {
963
		*idStr = QString::number(msg.dmID());
964
	}
965
	if (annotation != Q_NULLPTR) {
966
		*annotation = msg.dmAnnotation();
967 968 969 970 971
	}
	if (msgDescrHtml != Q_NULLPTR) {
		*msgDescrHtml = html;
	}
	if (attachModel != Q_NULLPTR) {
972 973
		attachModel->clearAll();
		foreach (const AttachmentData &file, fileList) {
Martin Straka's avatar
Martin Straka committed
974 975 976 977
			attachModel->appendFileEntry(
			    FileListModel::Entry(-1, file.dmFileDescr(),
			    file._dmFileSize(), file._icon(),
			    file.dmEncodedContent(), QString()));
978
		}
979 980 981 982
	}
	if (emailBody != Q_NULLPTR) {
		*emailBody = body;
	}
983

984 985
	return true;
}