1 //**********************************************************************************
2 //EncryptPad Copyright 2016 Evgeny Pokhilko
3 //<http://www.evpo.net/encryptpad>
4 //
5 //This file is part of EncryptPad
6 //
7 //EncryptPad is free software: you can redistribute it and/or modify
8 //it under the terms of the GNU General Public License as published by
9 //the Free Software Foundation, either version 2 of the License, or
10 //(at your option) any later version.
11 //
12 //EncryptPad is distributed in the hope that it will be useful,
13 //but WITHOUT ANY WARRANTY; without even the implied warranty of
14 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 //GNU General Public License for more details.
16 //
17 //You should have received a copy of the GNU General Public License
18 //along with EncryptPad. If not, see <http://www.gnu.org/licenses/>.
19 //**********************************************************************************
20 #include <QtGui>
21 #include <QtWidgets>
22
23 #include <iostream>
24 #include <string>
25 #include <algorithm>
26 #include <ctime>
27 #include "get_passphrase_dialog.h"
28 #include "confirm_passphrase_dialog.h"
29 #include "mainwindow.h"
30 #include "key_generation.h"
31 #include "file_encryption.h"
32 #include "encryptmsg/algo_spec.h"
33 #include "file_properties_dialog.h"
34 #include "os_api.h"
35 #include "file_name_helper.h"
36 #include "new_key_dialog.h"
37 #include "preferences_dialog.h"
38 #include "find_and_replace.h"
39 #include "set_encryption_key.h"
40 #include "get_passphrase_or_key_dialog.h"
41 #include "file_encryption_dialog.h"
42 #include "passphrase_generation_dialog.h"
43 #include "version.h"
44 #include "common_definitions.h"
45 #include "encryptmsg/openpgp_conversions.h"
46 #include "repository.h"
47 #include "encryptmsg_version.h"
48 #include "fake_vim_edit.h"
49 #include "plog/Log.h"
50
51 typedef unsigned char byte;
52
53 namespace
54 {
55 const char *kConfigFileName = "encryptpad.ini";
56
SetDefaultMetadataValues(EncryptPad::PacketMetadata & metadata,const EncryptPad::PacketMetadata & defaultMetadata)57 void SetDefaultMetadataValues(EncryptPad::PacketMetadata &metadata, const EncryptPad::PacketMetadata &defaultMetadata)
58 {
59 metadata = defaultMetadata;
60 metadata.file_name = "_CONSOLE";
61 }
62
63 // TakeBakFile
64 // returns true if ok and false if we need to cancel the whole operation
TakeBakFile(const QString & fileName)65 bool TakeBakFile(const QString &fileName)
66 {
67 QFileInfo fileInfo(fileName);
68 QFile file(fileName);
69
70 if(!file.exists())
71 return true;
72
73 QString bakFileName = fileInfo.dir().path() + "/" + fileInfo.completeBaseName() + QString(".bak");
74
75 if(bakFileName.toUpper() == fileName.toUpper())
76 {
77 // we are editing the bak file
78 return true;
79 }
80
81 if(QFile::exists(bakFileName))
82 {
83 if(!QFile::remove(bakFileName))
84 return false;
85 }
86
87 if(!file.rename(bakFileName))
88 return false;
89
90 return true;
91 }
92 }
93
94 const int MainWindow::maxZoomIn = 75;
95 const int MainWindow::minZoomOut = 3;
96
MainWindow()97 MainWindow::MainWindow():
98 encryptionKeyFile(tr("")),
99 persistEncryptionKeyPath(false),
100 windowsEol(false),
101 takeBakFile(false),
102 enc(),
103 metadata(),
104 encryptionModified(false),
105 isBusy(false),
106 currentZoom(0),
107 load_state_machine_(enc),
108 plain_text_switch_(this), plain_text_functor_(plain_text_switch_), recent_files_service_(this),
109 loadAdapter(this),
110 loadHandler(this, loadAdapter, metadata),
111 saveSuccess(false),
112 fakeVimWrapper()
113 {
114 setWindowIcon(QIcon(":/images/application_icon.png"));
115 textEdit = createEditorWidget(this);
116
117 setCentralWidget(textEdit);
118 QPalette palette = textEdit->palette();
119 QColor color = palette.color(QPalette::Active, QPalette::Highlight);
120 palette.setColor(QPalette::Inactive, QPalette::Highlight, color);
121 color = palette.color(QPalette::Active, QPalette::HighlightedText);
122 palette.setColor(QPalette::Inactive, QPalette::HighlightedText, color);
123 textEdit->setPalette(palette);
124
125 createActions();
126 createMenus();
127 createToolBars();
128 createStatusBar();
129
130 readSettings();
131
132 if(preferences.enableFakeVim)
133 {
134 fakeVimWrapper = CreateFakeVimWrapper(textEdit, this);
135 const auto *proxy = fakeVimWrapper->proxy.get();
136
137 connect(proxy, SIGNAL(requestRead(const QString&)), this, SLOT(open(QString)));
138
139 connect(proxy, SIGNAL(requestSave()), this, SLOT(save()));
140 connect(proxy, SIGNAL(requestSave(const QString&)), this, SLOT(saveAs(const QString&)));
141
142 connect(proxy, SIGNAL(requestSaveAndQuit()), this, SLOT(close()));
143 connect(proxy, SIGNAL(requestSaveAndQuit(const QString&)), this, SLOT(saveAsAndClose(const QString&)));
144
145 connect(proxy, SIGNAL(requestQuit()), this, SLOT(close()));
146 file_request_service_.SetDontUseNativeDialog(true);
147 }
148
149 SetDefaultMetadataValues(metadata, preferences.defaultFileProperties);
150
151 connect(textEdit->document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
152 connect(textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
153 connect(textEdit, SIGNAL(urlDropped(QUrl)), this, SLOT(onUrlDrop(QUrl)));
154 connect(textEdit, SIGNAL(leaveControl()), this, SLOT(onTextEditLeave()));
155 connect(&load_state_machine_, SIGNAL(AsyncOperationCompleted()), this, SLOT(AsyncOperationCompleted()));
156 connect(&load_state_machine_, SIGNAL(UpdateStatus(const QString&)), this, SLOT(UpdateStatus(const QString &)));
157 connect(&plain_text_switch_, SIGNAL(UpdateSwitch(bool)), this, SLOT(UpdateEncryptedPlainSwitch(bool)));
158 connect(&recent_files_service_, SIGNAL(FileSelected(const QString &)), this, SLOT(open(QString)));
159
160 plain_text_functor_.EncryptedPlainSwitchChange(!enc.GetIsPlainText());
161 enc.SetEncryptedPlainSwitchFunctor(&plain_text_functor_);
162
163 setCurrentFile("");
164
165 update();
166
167 setUnifiedTitleAndToolBarOnMac(true);
168 updateEncryptionKeyStatus();
169 }
170
onTextEditLeave()171 void MainWindow::onTextEditLeave()
172 {
173 }
174
updateLineStatus()175 void MainWindow::updateLineStatus()
176 {
177 QTextCursor cursor = textEdit->textCursor();
178 QTextDocument *doc = textEdit->document();
179 int charCount = doc->characterCount();
180 int count = doc->blockCount();
181
182 int lineNumber = cursor.blockNumber();
183
184 if(!fakeVimWrapper)
185 {
186 lineStatus->setText(tr("ln: %1 of %2").arg(QString::number(lineNumber + 1)).arg(QString::number(count)));
187 }
188 charStatus->setText(tr("chars: %1").arg(QString::number(charCount - 1)));
189 }
190
cursorPositionChanged()191 void MainWindow::cursorPositionChanged()
192 {
193 updateLineStatus();
194 }
195
onUrlDrop(QUrl url)196 void MainWindow::onUrlDrop(QUrl url)
197 {
198 activateWindow();
199 QCoreApplication::processEvents();
200 open(url.toLocalFile());
201 }
202
UpdateEncryptedPlainSwitch(bool encrypted)203 void MainWindow::UpdateEncryptedPlainSwitch(bool encrypted)
204 {
205 if(!encrypted)
206 {
207 passphraseSet->setText(QString("<span style=\"color:#FF0000;\">") + tr("Passphrase not set") + QString("</span>"));
208 clearPassphraseAct->setEnabled(false);
209 }
210 else
211 {
212 passphraseSet->setText(QString("<b>") + tr("Passphrase protected") + QString("</b>"));
213 clearPassphraseAct->setEnabled(true);
214 }
215 }
216
closeEvent(QCloseEvent * event)217 void MainWindow::closeEvent(QCloseEvent *event)
218 {
219 if(isBusy)
220 {
221 event->ignore();
222 return;
223 }
224
225 if (maybeSave()) {
226 writeSettings();
227 event->accept();
228 } else {
229 event->ignore();
230 }
231 }
232
233 namespace
234 {
getLastModified(const QString & fileName,QDateTime defaultDateTime)235 QDateTime getLastModified(const QString &fileName, QDateTime defaultDateTime)
236 {
237 QFileInfo fileInfo(fileName);
238 if(!fileInfo.exists())
239 return defaultDateTime;
240
241 return fileInfo.lastModified();
242 }
243
244 // Check if EOL is Windows or Unix. Returns true if EOL is for Windows.
inferWindowsEol(const byte * begin,const byte * end)245 bool inferWindowsEol(const byte *begin, const byte *end)
246 {
247 const byte *it = std::find(begin, end, 0x0D);
248 return (it != end);
249 }
250 }
251
AsyncOperationCompleted()252 void MainWindow::AsyncOperationCompleted()
253 {
254 using namespace EncryptPad;
255 ExitWaitState();
256
257 bool success = false;
258 bool rejected = false;
259 bool request_kf_passphrase = false;
260
261 switch(load_state_machine_.get_load_result())
262 {
263 case EpadResult::Success:
264 success = true;
265 break;
266
267 //CpadFileIOError
268 case EpadResult::UnexpectedError:
269 case EpadResult::IOError:
270 case EpadResult::IOErrorOutput:
271 case EpadResult::IOErrorInput:
272
273 //InvalidCpadFile
274 case EpadResult::UnexpectedFormat:
275 case EpadResult::UnsupportedPacketType:
276 case EpadResult::UnsupportedAlgo:
277 case EpadResult::UnsupportedS2K:
278 case EpadResult::UnsupportedCompressionAlgo:
279 case EpadResult::MDCError:
280 case EpadResult::CompressionError:
281 case EpadResult::InvalidWadFile:
282 QMessageBox::warning(
283 this,
284 "EncryptPad",
285 tr("Cannot open '%1'").arg(load_state_machine_.get_file_name()));
286 rejected = true;
287 break;
288
289 //EncryptionError:
290 case EpadResult::InvalidSurrogateIV:
291 case EpadResult::InvalidPassphrase:
292 case EpadResult::KeyIsRequiredForSaving:
293 if(enc.GetIsPlainText())
294 {
295 // This is not normal. We decrypted without a passphrase.
296 // There must have been a key so we should have got InvalidX2File instead
297 // Process it just in case
298 QMessageBox::warning(
299 this,
300 "EncryptPad",
301 tr("Cannot open '%1'").arg(load_state_machine_.get_file_name()));
302 rejected = true;
303 }
304 else
305 {
306 enc.SetIsPlainText();
307 }
308
309 break;
310
311 case EpadResult::InvalidKeyFilePassphrase:
312 // There will be another request for the key file passphrase
313 enc.ClearKFPassphrase();
314 request_kf_passphrase = true;
315 break;
316
317 case EpadResult::IOErrorKeyFile:
318 QMessageBox::warning(
319 this,
320 "EncryptPad",
321 tr("Cannot open the encryption key"));
322 rejected = true;
323 break;
324
325 case EpadResult::InvalidKeyFile:
326 QMessageBox::warning(
327 this,
328 "EncryptPad",
329 tr("The encryption key is invalid"));
330 rejected = true;
331
332 break;
333 case EpadResult::CurlIsNotFound:
334 QMessageBox::warning(
335 this,
336 "EncryptPad",
337 tr("Cannot download the encryption key. CURL tool is not found."));
338 rejected = true;
339 break;
340
341 case EpadResult::CurlExitNonZero:
342 QMessageBox::warning(
343 this,
344 "EncryptPad",
345 tr("Cannot download the key. CURL returned non zero exit code"));
346 rejected = true;
347 break;
348
349 case EpadResult::KeyFileNotSpecified:
350 rejected = !OpenSetEncryptionKeyDialogue();
351 break;
352
353 default:
354 rejected = true;
355 break;
356 }
357
358 Q_ASSERT(!success || !rejected);
359
360 if(!success)
361 {
362 statusBar()->showMessage(tr("Cannot load the file"), 2000);
363 }
364
365 if(!success && !rejected)
366 {
367 loadFile(load_state_machine_.get_file_name(), request_kf_passphrase);
368 }
369 else if(success)
370 {
371 // need to destroy the string quickly
372 {
373 auto &data = load_state_machine_.get_file_data();
374 setWindowsEol(inferWindowsEol(data.data(), data.data() + data.size()));
375
376 QString str = QString::fromUtf8(
377 reinterpret_cast<const char *>(data.data()),
378 data.size());
379
380 // clear the unencrypted data in the buffer
381 load_state_machine_.ClearBuffer();
382 textEdit->setPlainText(str);
383 if(encryptionKeyFile.length() == 0 && enc.GetX2KeyLocation().length() > 0)
384 {
385 persistEncryptionKeyPath = true;
386 setEncryptionKeyFile(QString::fromStdString(enc.GetX2KeyLocation()));
387 updateEncryptionKeyStatus();
388 }
389 else if(encryptionKeyFile.length() > 0 && enc.GetX2KeyLocation().length() == 0)
390 {
391 persistEncryptionKeyPath = false;
392 setEncryptionKeyFile(QString());
393 updateEncryptionKeyStatus();
394 }
395 }
396 // at this point only textEdit contains unencrypted data
397 setCurrentFile(load_state_machine_.get_file_name());
398
399 if(preferences.enableBakFiles)
400 {
401 takeBakFile = true;
402 }
403
404 // Apply default settings for a just loaded plain-text file
405 if(getEncryptionKeyFile().length() == 0 && enc.GetIsPlainText())
406 {
407 SetDefaultMetadataValues(metadata, preferences.defaultFileProperties);
408 }
409
410 statusBar()->showMessage(tr("File loaded"), 2000);
411
412 recent_files_service_.PushFile(load_state_machine_.get_file_name());
413 textEdit->setFocus();
414 }
415 }
416
UpdateStatus(const QString & text)417 void MainWindow::UpdateStatus(const QString &text)
418 {
419 statusBar()->showMessage(text);
420 }
421
newFile()422 bool MainWindow::newFile()
423 {
424 using namespace EncryptPad;
425 using namespace EncryptMsg;
426 if (maybeSave()) {
427 textEdit->clear();
428 setCurrentFile("");
429
430 CipherAlgo algo_before = metadata.cipher_algo;
431 HashAlgo hash_algo_before = metadata.hash_algo;
432 unsigned int iterations_before = metadata.iterations;
433
434 SetDefaultMetadataValues(metadata, preferences.defaultFileProperties);
435
436 if(algo_before != metadata.cipher_algo || hash_algo_before != metadata.hash_algo
437 || iterations_before != metadata.iterations)
438 {
439 clearPassphrase();
440 }
441
442 setWindowsEol(preferences.windowsEol);
443
444 return true;
445 }
446 else
447 {
448 return false;
449 }
450 }
451
closeAndReset()452 void MainWindow::closeAndReset()
453 {
454 if(newFile())
455 {
456 bool makeFileDirty = false;
457 clearPassphrase(makeFileDirty);
458 clearEncryptionKey(makeFileDirty);
459 }
460 }
461
onApplicationActive()462 void MainWindow::onApplicationActive()
463 {
464 if(isBusy)
465 return;
466
467 FlagResetter isBusyResetter(isBusy);
468
469 if(curFile.isEmpty())
470 return;
471
472 QDateTime modified = getLastModified(curFile, lastModified);
473 if(lastModified == modified)
474 return;
475
476 QString message = tr("The file has been modified by another program. Do you want to reload it?");
477 if(textEdit->document()->isModified())
478 {
479 message = tr("The file has been modified by another program. Do you want to reload it"
480 " and lose the changes made in this application?");
481 }
482
483 auto reply = QMessageBox::question(this, "EncryptPad",
484 message,
485 QMessageBox::Yes | QMessageBox::No,
486 QMessageBox::Yes);
487
488 lastModified = modified;
489
490 if(reply == QMessageBox::No)
491 {
492 return;
493 }
494
495 isBusyResetter.Disconnect();
496
497 loadFile(curFile);
498 }
499
getIsBusy() const500 bool MainWindow::getIsBusy() const
501 {
502 return isBusy;
503 }
504
open(QString fileName)505 void MainWindow::open(QString fileName)
506 {
507 if(isBusy)
508 return;
509
510 FlagResetter isBusyResetter(isBusy);
511
512 if (maybeSave()) {
513 QString selectedFilter;
514 if(fileName.isNull())
515 {
516 FileRequestSelection selection = file_request_service_.RequestExistingFile(
517 this,
518 tr("Open File"),
519 QString(),
520 GetOpenDialogFilter());
521
522 if(!selection.cancelled)
523 {
524 fileName = selection.file_name;
525 selectedFilter = selection.filter;
526 }
527 }
528
529 if (!fileName.isEmpty())
530 {
531 //leave flag in set state
532 isBusyResetter.Disconnect();
533 loadFile(fileName);
534 }
535 }
536 }
537
open()538 void MainWindow::open()
539 {
540 open(QString());
541 }
542
save()543 bool MainWindow::save()
544 {
545 if (curFile.isEmpty()) {
546 return saveAs();
547 } else {
548 return saveFile(curFile);
549 }
550 }
551
saveAs(const QString & path)552 void MainWindow::saveAs(const QString &path)
553 {
554 if(curFile != path && preferences.enableBakFiles)
555 {
556 takeBakFile = true;
557 }
558
559 saveFile(path);
560 }
561
saveAsAndClose(const QString & path)562 void MainWindow::saveAsAndClose(const QString &path)
563 {
564 if(curFile != path && preferences.enableBakFiles)
565 {
566 takeBakFile = true;
567 }
568
569 saveFile(path);
570 close();
571 }
572
saveAs()573 bool MainWindow::saveAs()
574 {
575 QString selectedFilter;
576 if(!curFile.isEmpty())
577 {
578 selectedFilter = GetFileFilterFromFileName(curFile);
579 }
580
581 LOG_INFO << "selectedFilter: " << selectedFilter.toStdString();
582 FileRequestSelection selection = file_request_service_.RequestNewFile(
583 this,
584 tr("Save File As"),
585 curFile,
586 GetSaveDialogFilter(),
587 &selectedFilter);
588
589 if(selection.cancelled)
590 return false;
591
592 LOG_INFO << "selection.file_name: " << selection.file_name;
593 LOG_INFO << "selection.filter: " << selection.filter;
594
595 // Set the bak file flag if the file name has changed
596 if(curFile != selection.file_name && preferences.enableBakFiles)
597 {
598 takeBakFile = true;
599 }
600
601 return saveFile(selection.file_name);
602 }
603
accessRepositoryPath(const QString & fileName)604 QString MainWindow::accessRepositoryPath(const QString &fileName)
605 {
606 QString dirPath = QString::fromStdString(EncryptPad::GetRepositoryPath());
607
608 if(dirPath.isEmpty())
609 {
610 QMessageBox::warning(
611 this,
612 "EncryptPad",
613 tr("Cannot create the repository directory in HOME"));
614 return QString();
615 }
616
617 QDir dir(dirPath);
618 return dir.filePath(fileName);
619 }
620
createNewKey()621 void MainWindow::createNewKey()
622 {
623 using namespace EncryptPad;
624
625 QString fileName;
626 NewKeyDialog dlg(this, file_request_service_);
627 if(dlg.exec() == QDialog::Rejected)
628 return;
629
630 bool isRepo = dlg.isKeyInRepository();
631 fileName = dlg.getKey();
632
633 if (fileName.isEmpty())
634 return;
635
636 QString filePath = fileName;
637 if(isRepo)
638 {
639 if(!fileName.endsWith(".key", Qt::CaseInsensitive))
640 fileName += ".key";
641
642 filePath = accessRepositoryPath(fileName);
643 if(filePath.isEmpty())
644 return;
645 }
646
647 std::string kf_passphrase;
648 if(!loadHandler.OpenPassphraseDialog(true, &kf_passphrase, false, tr("Passphrase for Key File")))
649 return;
650
651 if(kf_passphrase.empty())
652 {
653 auto ret = QMessageBox::warning(
654 this,
655 "EncryptPad",
656 tr("You left the passphrase blank. The key file is going to be UNENCRYPTED. Do you want to continue?"),
657 QMessageBox::Ok | QMessageBox::Cancel
658 );
659
660 if(ret == QMessageBox::Cancel)
661 return;
662 }
663
664 try
665 {
666 EncryptParams kf_encrypt_params;
667 if(!kf_passphrase.empty())
668 {
669 kf_encrypt_params.key_service = &enc.GetKFKeyService();
670 enc.ClearKFPassphrase();
671 PacketMetadata kf_metadata = preferences.keyFileProperties;
672 kf_encrypt_params.key_service->ChangePassphrase(
673 kf_passphrase, kf_metadata.hash_algo, GetAlgoSpec(kf_metadata.cipher_algo).key_size,
674 kf_metadata.iterations);
675 std::fill(kf_passphrase.begin(), kf_passphrase.end(), '\0');
676 GenerateNewKey(filePath.toStdString(), preferences.kfKeyLength, &kf_encrypt_params, &kf_metadata);
677 }
678 else
679 {
680 GenerateNewKey(filePath.toStdString(), preferences.kfKeyLength);
681 }
682 }
683 catch(EncryptPad::IoException&)
684 {
685 QMessageBox::warning(
686 this,
687 "EncryptPad",
688 tr("Cannot generate the key '%1' Check the path and permissions.").arg(fileName));
689
690 statusBar()->showMessage(tr("Cannot generate key"));
691 return;
692 }
693 auto reply = QMessageBox::question(this, "EncryptPad",
694 tr("Do you want to use the generated key for this file?"),
695 QMessageBox::Yes | QMessageBox::No);
696
697 if(reply == QMessageBox::Yes)
698 {
699 bool clearKFKeyService = false;
700 setEncryptionKeyFile(isRepo ? fileName : filePath, clearKFKeyService);
701 this->updateEncryptionKeyStatus();
702 }
703 }
704
about()705 void MainWindow::about()
706 {
707 QString encryptPadText = tr("<b>EncryptPad %1 Beta</b><br/><br/>"
708 "A minimalist secure text editor and file encryptor that implements "
709 "RFC 4880 Open PGP format: "
710 "symmetrically encrypted, compressed and integrity protected. "
711 "The editor can protect files with passphrases, key files or both.<br/><br/>"
712 "%2<br/>"
713 "GNU General Public License v2<br/><br/>"
714 ).arg(VER_PRODUCTVERSION_STR).arg(VER_LEGALCOPYRIGHT_STR) +
715 QString("<a href=\"https://www.evpo.net/encryptpad\">https://www.evpo.net/encryptpad</a>");
716
717 QString libEncryptMsgText = tr(
718 "<b>%1</b>"
719 "<br/>"
720 "<br/>"
721 "OpenPGP implementation."
722 "<br/>"
723 "<br/>"
724 "%2"
725 "<br/>"
726 "Simplified BSD License"
727 "<br/>"
728 "<br/>"
729 )
730 .arg(EncryptPad::EncryptMsgVersion().c_str())
731 .arg(EncryptPad::EncryptMsgCopyright().c_str()) +
732 QString("<a href=\"https://www.evpo.net/libencryptmsg\">https://www.evpo.net/libencryptmsg</a>");
733
734 QMessageBox::about(this, tr("About EncryptPad"),
735 encryptPadText +
736 "<br>" +
737 "<hr>" +
738 "<br>" +
739 libEncryptMsgText);
740 }
741
documentWasModified()742 void MainWindow::documentWasModified()
743 {
744 if(encryptionModified)
745 return;
746 setWindowModified(textEdit->document()->isModified());
747 updateLineStatus();
748 }
749
openPreferences()750 void MainWindow::openPreferences()
751 {
752 bool lastEnableBakFiles = preferences.enableBakFiles;
753
754 auto settings = loadSettings();
755 PersistentPreferences loadedPreferences;
756 SetDefaultPreferences(loadedPreferences);
757 if(settings.get() != nullptr)
758 {
759 ReadPreferences(*settings, loadedPreferences);
760 }
761 else
762 {
763 loadedPreferences = preferences;
764 }
765
766 PreferencesDialog dlg(this);
767 dlg.set(loadedPreferences);
768 if(dlg.exec() == QDialog::Rejected)
769 return;
770
771 dlg.get(preferences);
772
773 WritePreferences(*settings, preferences);
774
775 onUpdatedPreferences();
776
777 if(preferences.enableBakFiles && !lastEnableBakFiles)
778 {
779 takeBakFile = true;
780 }
781
782 if(dlg.getDefaultFilePropertiesChanged())
783 {
784 auto reply = QMessageBox::question(this, "EncryptPad",
785 tr("Do you want to apply the modified default file properties to this file?"),
786 QMessageBox::Yes | QMessageBox::No);
787
788 if(reply == QMessageBox::Yes)
789 {
790 CopyMetadataPreferences(preferences.defaultFileProperties, metadata);
791 clearPassphrase();
792 }
793 }
794 }
795
createActions()796 void MainWindow::createActions()
797 {
798 newAct = new QAction(QIcon(":/images/famfamfam/page_white.png"), tr("&New"), this);
799 newAct->setShortcuts(QKeySequence::New);
800 newAct->setStatusTip(tr("Create a new file"));
801 connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
802
803 openAct = new QAction(QIcon(":/images/famfamfam/folder_page_white.png"), tr("&Open..."), this);
804 openAct->setShortcuts(QKeySequence::Open);
805 openAct->setStatusTip(tr("Open an existing file"));
806 connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
807
808 saveAct = new QAction(QIcon(":/images/famfamfam/disk.png"), tr("&Save"), this);
809 saveAct->setShortcuts(QKeySequence::Save);
810 saveAct->setStatusTip(tr("Save the document to disk"));
811 connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));
812
813 saveAsAct = new QAction(QIcon(":/images/famfamfam/disk_multiple.png"), tr("Save &As..."), this);
814 saveAsAct->setShortcuts(QKeySequence::SaveAs);
815 saveAsAct->setStatusTip(tr("Save the document under a new name"));
816 connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));
817
818 setFilePropertiesAct = new QAction(QIcon(":/images/famfamfam/page_white_gear.png"), tr("File &Properties..."), this);
819 setFilePropertiesAct->setStatusTip(tr("Set file properties"));
820 connect(setFilePropertiesAct, SIGNAL(triggered()), this, SLOT(setFileProperties()));
821
822 closeAndResetAct = new QAction(QIcon(":/images/famfamfam/cross.png"), tr("&Close and Reset"), this);
823 closeAndResetAct->setShortcuts(QKeySequence::Close);
824 closeAndResetAct->setStatusTip(tr("Close and reset security settings"));
825 connect(closeAndResetAct, SIGNAL(triggered()), this, SLOT(closeAndReset()));
826
827 createNewKeyAct = new QAction(QIcon(":/images/famfamfam/key_add.png"), tr("Generate Key..."), this);
828 createNewKeyAct->setStatusTip(tr("Generate a new key file"));
829 connect(createNewKeyAct, SIGNAL(triggered()), this, SLOT(createNewKey()));
830
831 setPassphraseAct = new QAction(QIcon(":/images/famfamfam/lock.png"), tr("Set &Passphrase..."), this);
832 setPassphraseAct->setStatusTip(tr("Set passphrase for encryption and decryption"));
833 connect(setPassphraseAct, SIGNAL(triggered()), this, SLOT(setPassphrase()));
834
835 setEncryptionKeyAct = new QAction(QIcon(":/images/famfamfam/key.png"), tr("Set &Encryption Key..."), this);
836 setEncryptionKeyAct->setStatusTip(tr("Set encryption key file"));
837 connect(setEncryptionKeyAct, SIGNAL(triggered()), this, SLOT(setEncryptionKey()));
838
839 clearEncryptionKeyAct = new QAction(QIcon(":/images/famfamfam/key_delete.png"), tr("Clear &Encryption Key"), this);
840 clearEncryptionKeyAct->setStatusTip(tr("Clear encryption key file. Passphrase only (if set)."));
841 connect(clearEncryptionKeyAct, SIGNAL(triggered()), this, SLOT(clearEncryptionKey()));
842
843 clearPassphraseAct = new QAction(QIcon(":/images/famfamfam/lock_delete.png"), tr("&Clear Passphrase"), this);
844 clearPassphraseAct->setStatusTip(tr("Save without passphrase protected encryption. Encryption with file key only (if enabled)."));
845 connect(clearPassphraseAct, SIGNAL(triggered()), this, SLOT(clearPassphrase()));
846
847 exitAct = new QAction(tr("E&xit"), this);
848 exitAct->setShortcuts(QKeySequence::Quit);
849
850 exitAct->setStatusTip(tr("Exit the application"));
851 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
852
853 undoAct = new QAction(QIcon(":/images/famfamfam/arrow_undo.png"), tr("Undo"), this);
854 undoAct->setShortcuts(QKeySequence::Undo);
855 undoAct->setStatusTip(tr("Undo"));
856 connect(undoAct, SIGNAL(triggered()), textEdit, SLOT(undo()));
857
858 redoAct = new QAction(QIcon(":/images/famfamfam/arrow_redo.png"), tr("Redo"), this);
859 redoAct->setShortcuts(QKeySequence::Redo);
860 redoAct->setStatusTip(tr("Redo"));
861 connect(redoAct, SIGNAL(triggered()), textEdit, SLOT(redo()));
862
863 cutAct = new QAction(QIcon(":/images/famfamfam/cut.png"), tr("Cu&t"), this);
864 cutAct->setShortcuts(QKeySequence::Cut);
865 cutAct->setStatusTip(tr("Cut the current selection's contents to the "
866 "clipboard"));
867 connect(cutAct, SIGNAL(triggered()), textEdit, SLOT(cut()));
868
869 copyAct = new QAction(QIcon(":/images/famfamfam/page_copy.png"), tr("&Copy"), this);
870 copyAct->setShortcuts(QKeySequence::Copy);
871 copyAct->setStatusTip(tr("Copy the current selection's contents to the "
872 "clipboard"));
873 connect(copyAct, SIGNAL(triggered()), textEdit, SLOT(copy()));
874
875 pasteAct = new QAction(QIcon(":/images/famfamfam/page_paste.png"), tr("&Paste"), this);
876 pasteAct->setShortcuts(QKeySequence::Paste);
877 pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
878 "selection"));
879 connect(pasteAct, SIGNAL(triggered()), textEdit, SLOT(paste()));
880
881 selectAllAct = new QAction(tr("&Select All"), this);
882 selectAllAct->setShortcuts(QKeySequence::SelectAll);
883 selectAllAct->setStatusTip(tr("Select all text"));
884 connect(selectAllAct, SIGNAL(triggered()), textEdit, SLOT(selectAll()));
885
886 searchAct = new QAction(QIcon(":/images/famfamfam/find.png"), tr("&Find..."), this);
887 searchAct->setShortcuts(QKeySequence::Find);
888 searchAct->setStatusTip(tr("Find in text"));
889 connect(searchAct, SIGNAL(triggered()), this, SLOT(search()));
890
891 gotoAct = new QAction(tr("&Go to..."), this);
892 gotoAct->setStatusTip(tr("Go to"));
893 gotoAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_G));
894 connect(gotoAct, SIGNAL(triggered()), this, SLOT(gotoTriggered()));
895
896 generatePassphraseAct = new QAction(QIcon(":/images/famfamfam/user_suit.png"), tr("&Generate Passphrase..."), this);
897 generatePassphraseAct->setStatusTip(tr("Generate passphrase"));
898 connect(generatePassphraseAct, SIGNAL(triggered()), this, SLOT(generatePassphrase()));
899
900 replaceAct = new QAction(tr("&Replace..."), this);
901 replaceAct->setShortcut(QKeySequence::Replace);
902 replaceAct->setStatusTip(tr("Find and replace text"));
903 connect(replaceAct, SIGNAL(triggered()), this, SLOT(replace()));
904
905 readOnlyAct = new QAction(QIcon(":/images/famfamfam/read_only.png"), tr("Read Only"), this);
906 readOnlyAct->setCheckable(true);
907 readOnlyAct->setStatusTip(tr("Enable Read Only mode"));
908 connect(readOnlyAct, SIGNAL(toggled(bool)), this, SLOT(readOnlyToggled(bool)));
909 connect(readOnlyAct, SIGNAL(toggled(bool)), replaceAct, SLOT(setDisabled(bool)));
910 connect(readOnlyAct, SIGNAL(toggled(bool)), generatePassphraseAct, SLOT(setDisabled(bool)));
911
912 wordWrapAct = new QAction(QIcon(":/images/famfamfam/wrap.png"), tr("Word Wrap"), this);
913 wordWrapAct->setCheckable(true);
914 wordWrapAct->setStatusTip(tr("Enable Word Wrap"));
915 connect(wordWrapAct, SIGNAL(toggled(bool)), this, SLOT(wordWrapToggled(bool)));
916
917 zoomInAct = new QAction(QIcon(":/images/famfamfam/magnifier_zoom_in.png"), tr("&Zoom In"), this);
918 zoomInAct->setShortcuts(QKeySequence::ZoomIn);
919 zoomInAct->setStatusTip(tr("Zoom In"));
920 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
921
922 zoomOutAct = new QAction(QIcon(":/images/famfamfam/magnifier_zoom_out.png"), tr("&Zoom Out"), this);
923 zoomOutAct->setShortcuts(QKeySequence::ZoomOut);
924 zoomOutAct->setStatusTip(tr("Zoom Out"));
925 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
926
927 resetZoomAct = new QAction(QIcon(":/images/famfamfam/magnifier.png"), tr("&Reset Zoom"), this);
928 resetZoomAct->setShortcut(QKeySequence("Ctrl+0"));
929 resetZoomAct->setStatusTip(tr("Reset Zoom"));
930 connect(resetZoomAct, SIGNAL(triggered()), this, SLOT(resetZoom()));
931
932 aboutAct = new QAction(tr("&About..."), this);
933 aboutAct->setStatusTip(tr("Show the application's About box"));
934 connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
935
936
937 aboutQtAct = new QAction(tr("About &Qt"), this);
938 aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
939 connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
940
941 openPreferencesAct = new QAction(tr("&Preferences..."), this);
942 openPreferencesAct->setStatusTip(tr("Application preferences"));
943 connect(openPreferencesAct, SIGNAL(triggered()), this, SLOT(openPreferences()));
944
945 openFileEncryptionAct = new QAction(QIcon(":/images/famfamfam/arrow_switch.png"), tr("File Encryption..."), this);
946 openFileEncryptionAct->setStatusTip(tr("File encryption"));
947 connect(openFileEncryptionAct, SIGNAL(triggered()), this, SLOT(openFileEncryption()));
948
949 windowsEolAct = new QAction(tr("Windows EOL"), this);
950 windowsEolAct->setStatusTip(tr("\"Windows\" end of line: CR+LF"));
951 windowsEolAct->setCheckable(true);
952 connect(windowsEolAct, SIGNAL(toggled(bool)), this, SLOT(windowsEolToggled(bool)));
953 setWindowsEol(false);
954
955 cutAct->setEnabled(false);
956 copyAct->setEnabled(false);
957 undoAct->setEnabled(false);
958 redoAct->setEnabled(false);
959 connect(textEdit, SIGNAL(copyAvailable(bool)),
960 cutAct, SLOT(setEnabled(bool)));
961 connect(textEdit, SIGNAL(copyAvailable(bool)),
962 copyAct, SLOT(setEnabled(bool)));
963
964 connect(textEdit, SIGNAL(undoAvailable(bool)),
965 undoAct, SLOT(setEnabled(bool)));
966 connect(textEdit, SIGNAL(redoAvailable(bool)),
967 redoAct, SLOT(setEnabled(bool)));
968
969 findDialog = new FindDialog(this);
970 connect(findDialog, SIGNAL(findNext(QString,bool,bool,bool)),
971 this, SLOT(findNext(QString,bool,bool,bool)));
972
973 replaceDialog = new FindAndReplace(this);
974 connect(replaceDialog, SIGNAL(findNext(QString,bool,bool,bool)),
975 this, SLOT(findNext(QString,bool,bool,bool)));
976 connect(replaceDialog, SIGNAL(replaceAll(QString,QString,bool,bool)),
977 this, SLOT(replaceAll(QString,QString,bool,bool)));
978 connect(replaceDialog, SIGNAL(replaceOne(QString,QString,bool,bool)),
979 this, SLOT(replaceOne(QString,QString,bool,bool)));
980 connect(replaceDialog, SIGNAL(finished(int)),
981 this, SLOT(clearReplaceContext()));
982 }
983
replaceAll(QString text,QString replaceWith,bool matchCase,bool wholeWord)984 void MainWindow::replaceAll(QString text, QString replaceWith, bool matchCase, bool wholeWord)
985 {
986 QTextDocument::FindFlags flags = 0;
987 if(matchCase)
988 flags |= QTextDocument::FindCaseSensitively;
989
990 if(wholeWord)
991 flags |= QTextDocument::FindWholeWords;
992
993 QTextCursor cursor = textEdit->textCursor();
994 cursor.beginEditBlock();
995 textEdit->moveCursor(QTextCursor::MoveOperation::Start);
996 while(textEdit->find(text, flags))
997 {
998 QTextCursor foundTextCur = textEdit->textCursor();
999 if(foundTextCur.hasSelection())
1000 foundTextCur.insertText(replaceWith);
1001 }
1002 cursor.endEditBlock();
1003 }
1004
replaceOne(QString text,QString replaceWith,bool matchCase,bool wholeWord)1005 void MainWindow::replaceOne(QString text, QString replaceWith, bool matchCase, bool wholeWord)
1006 {
1007 QTextCursor cursor = textEdit->textCursor();
1008 if(!cursor.hasSelection() ||
1009 cursor.selectedText() != replaceContext.foundText ||
1010 text != replaceContext.enteredText ||
1011 replaceContext.matchCase != matchCase ||
1012 replaceContext.wholeWord != wholeWord)
1013 {
1014 findNext(text, true, matchCase, wholeWord);
1015 return;
1016 }
1017
1018 cursor.beginEditBlock();
1019 cursor.insertText(replaceWith);
1020 cursor.endEditBlock();
1021
1022 findNext(text, true, matchCase, wholeWord);
1023 }
1024
clearReplaceContext()1025 void MainWindow::clearReplaceContext()
1026 {
1027 replaceContext.enteredText.clear();
1028 replaceContext.foundText.clear();
1029 replaceContext.matchCase = false;
1030 replaceContext.wholeWord = false;
1031 }
1032
findNext(QString text,bool down,bool matchCase,bool wholeWord)1033 void MainWindow::findNext(QString text, bool down, bool matchCase, bool wholeWord)
1034 {
1035 QTextDocument::FindFlags flags = 0;
1036 if(!down)
1037 flags |= QTextDocument::FindBackward;
1038
1039 if(matchCase)
1040 flags |= QTextDocument::FindCaseSensitively;
1041
1042 if(wholeWord)
1043 flags |= QTextDocument::FindWholeWords;
1044
1045 QString msgForWrapping = tr("End of file reached. Started from the top.");
1046 bool found = false;
1047 bool wrapped = false;
1048 clearReplaceContext();
1049
1050 QTextCursor cursor = textEdit->textCursor();
1051 found = textEdit->find(text, flags);
1052 if(!found)
1053 {
1054 statusBar()->showMessage(msgForWrapping);
1055 textEdit->moveCursor(down ? QTextCursor::MoveOperation::Start : QTextCursor::MoveOperation::End);
1056 wrapped = true;
1057 found = textEdit->find(text, flags);
1058 if(!found)
1059 {
1060 textEdit->setTextCursor(cursor);
1061 statusBar()->showMessage(tr("Text not found."));
1062 }
1063 }
1064
1065 if(found)
1066 {
1067 replaceContext.foundText = textEdit->textCursor().selectedText();
1068 replaceContext.enteredText = text;
1069 replaceContext.matchCase = matchCase;
1070 replaceContext.wholeWord = wholeWord;
1071 QString message;
1072 if(wrapped)
1073 message = msgForWrapping + " ";
1074
1075 message += tr("Text found.");
1076 statusBar()->showMessage(message);
1077 }
1078 }
1079
createMenus()1080 void MainWindow::createMenus()
1081 {
1082 fileMenu = menuBar()->addMenu(tr("&File"));
1083 fileMenu->addAction(newAct);
1084
1085 fileMenu->addAction(openAct);
1086
1087 fileMenu->addAction(saveAct);
1088
1089 fileMenu->addAction(saveAsAct);
1090 fileMenu->addAction(setFilePropertiesAct);
1091 fileMenu->addAction(closeAndResetAct);
1092 QAction *separator = fileMenu->addSeparator();
1093 fileMenu->addAction(openFileEncryptionAct);
1094
1095 separator = fileMenu->addSeparator();
1096 fileMenu->addAction(exitAct);
1097 recent_files_service_.Init(fileMenu, separator, exitAct);
1098
1099 editMenu = menuBar()->addMenu(tr("&Edit"));
1100 editMenu->addAction(undoAct);
1101 editMenu->addAction(redoAct);
1102 editMenu->addSeparator();
1103 editMenu->addAction(cutAct);
1104 editMenu->addAction(copyAct);
1105 editMenu->addAction(pasteAct);
1106 editMenu->addAction(selectAllAct);
1107 editMenu->addSeparator();
1108 editMenu->addAction(searchAct);
1109 editMenu->addAction(replaceAct);
1110 editMenu->addAction(gotoAct);
1111 editMenu->addSeparator();
1112 editMenu->addAction(windowsEolAct);
1113 editMenu->addAction(generatePassphraseAct);
1114 editMenu->addSeparator();
1115 editMenu->addAction(readOnlyAct);
1116
1117 viewMenu = menuBar()->addMenu(tr("&View"));
1118 viewMenu->addAction(wordWrapAct);
1119 viewMenu->addSeparator();
1120 viewMenu->addAction(zoomInAct);
1121 viewMenu->addAction(zoomOutAct);
1122 viewMenu->addAction(resetZoomAct);
1123
1124 menuBar()->addSeparator();
1125
1126 encMenu = menuBar()->addMenu(tr("En&cryption"));
1127 encMenu->addAction(setPassphraseAct);
1128 encMenu->addAction(clearPassphraseAct);
1129 encMenu->addSeparator();
1130 encMenu->addAction(createNewKeyAct);
1131 encMenu->addAction(setEncryptionKeyAct);
1132 encMenu->addAction(clearEncryptionKeyAct);
1133
1134 settingsMenu = menuBar()->addMenu(tr("&Settings"));
1135 settingsMenu->addAction(openPreferencesAct);
1136
1137 helpMenu = menuBar()->addMenu(tr("&Help"));
1138 helpMenu->addAction(aboutAct);
1139 }
1140
createToolBars()1141 void MainWindow::createToolBars()
1142 {
1143 fileToolBar = addToolBar(tr("File"));
1144 fileToolBar->addAction(newAct);
1145
1146 fileToolBar->addAction(openAct);
1147
1148 fileToolBar->addAction(saveAct);
1149 fileToolBar->addAction(saveAsAct);
1150 fileToolBar->addAction(openFileEncryptionAct);
1151 fileToolBar->addAction(setFilePropertiesAct);
1152 fileToolBar->addAction(closeAndResetAct);
1153
1154 editToolBar = addToolBar(tr("Edit"));
1155 editToolBar->addAction(cutAct);
1156 editToolBar->addAction(copyAct);
1157 editToolBar->addAction(pasteAct);
1158 editToolBar->addAction(searchAct);
1159 editToolBar->addAction(generatePassphraseAct);
1160 editToolBar->addAction(readOnlyAct);
1161
1162 encToolBar = addToolBar(tr("Encryption"));
1163 encToolBar->addAction(setPassphraseAct);
1164 encToolBar->addAction(clearPassphraseAct);
1165 encToolBar->addAction(createNewKeyAct);
1166 encToolBar->addAction(setEncryptionKeyAct);
1167 encToolBar->addAction(clearEncryptionKeyAct);
1168
1169 zoomToolBar = addToolBar(tr("View"));
1170 zoomToolBar->addAction(wordWrapAct);
1171 zoomToolBar->addAction(zoomInAct);
1172 zoomToolBar->addAction(zoomOutAct);
1173 zoomToolBar->addAction(resetZoomAct);
1174
1175 }
1176
createStatusBar()1177 void MainWindow::createStatusBar()
1178 {
1179 statusBar()->showMessage(tr("Ready"));
1180
1181 lineStatus = new QLabel("", this);
1182 statusBar()->addPermanentWidget(lineStatus);
1183
1184 charStatus = new QLabel("", this);
1185 statusBar()->addPermanentWidget(charStatus);
1186
1187 passphraseSet = new QLabel("", this);
1188 statusBar()->addPermanentWidget(passphraseSet);
1189
1190 encryptionKeySet = new QLabel("", this);
1191 statusBar()->addPermanentWidget(encryptionKeySet);
1192 }
1193
onUpdatedPreferences()1194 void MainWindow::onUpdatedPreferences()
1195 {
1196 wordWrapAct->setChecked(preferences.wordWrap);
1197 wordWrapToggled(preferences.wordWrap);
1198 enc.SetLibcurlPath(preferences.libCurlPath.toStdString());
1199 enc.SetLibcurlParams(preferences.libCurlParameters.toStdString());
1200 textEdit->setFont(preferences.font);
1201 QFontMetrics metrics(textEdit->font());
1202
1203 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
1204 int width = preferences.tabSize * metrics.width(' ');
1205 textEdit->setTabStopWidth(width);
1206 #else
1207 int width = preferences.tabSize * metrics.horizontalAdvance(' ');
1208 textEdit->setTabStopDistance(width);
1209 #endif
1210
1211 recent_files_service_.SetMaxFiles(preferences.recentFiles);
1212 resetZoom();
1213
1214 if(enc.GetKeyService().get_key_count() != preferences.s2kResultsPoolSize)
1215 enc.GetKeyService().set_key_count(preferences.s2kResultsPoolSize);
1216 }
1217
readSettings()1218 void MainWindow::readSettings()
1219 {
1220 SetDefaultPreferences(preferences);
1221
1222 QString configFile = accessRepositoryPath(kConfigFileName);
1223 if(configFile.isEmpty())
1224 return;
1225
1226 QSettings settings(configFile, QSettings::IniFormat);
1227
1228 ReadPreferences(settings, preferences);
1229 onUpdatedPreferences();
1230
1231 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
1232 QSize size = settings.value("size", QSize(640, 480)).toSize();
1233 bool readOnly = settings.value("read_only", QVariant(false)).toBool();
1234 readOnlyAct->setChecked(readOnly);
1235 readOnlyToggled(readOnly);
1236 recent_files_service_.Deserialize(
1237 settings.value("recent_file_list", QVariant().toStringList()).toStringList(),
1238 preferences.recentFiles);
1239 passphraseGenerationSettings = settings.value(
1240 "passphrase_generation", QVariant().toStringList()).toStringList();
1241 file_request_service_.set_current_directory(
1242 preferences.saveLastUsedDirectory ? settings.value("last_used_directory", QVariant(QString())).toString()
1243 : QString());
1244
1245 resize(size);
1246 move(pos);
1247 }
1248
loadSettings()1249 std::unique_ptr<QSettings> MainWindow::loadSettings()
1250 {
1251 QString configFile = accessRepositoryPath(kConfigFileName);
1252 if(configFile.isEmpty())
1253 return std::unique_ptr<QSettings>();
1254
1255 std::unique_ptr<QSettings> settings(new QSettings(configFile, QSettings::IniFormat));
1256 return settings;
1257 }
1258
writeSettings()1259 void MainWindow::writeSettings()
1260 {
1261 auto settings = loadSettings();
1262 if(settings.get() == nullptr)
1263 return;
1264 settings->setValue("word_wrap", QVariant(wordWrapAct->isChecked()));
1265 settings->setValue("pos", pos());
1266 settings->setValue("size", size());
1267 QStringList list;
1268 recent_files_service_.Serialize(list);
1269 settings->setValue("recent_file_list", QVariant(list));
1270 settings->setValue("read_only", QVariant(readOnlyAct->isChecked()));
1271 settings->setValue("passphrase_generation", QVariant(passphraseGenerationSettings));
1272 settings->setValue("last_used_directory",
1273 QVariant(preferences.saveLastUsedDirectory ? file_request_service_.get_current_directory()
1274 : QString()));
1275 }
1276
maybeSave()1277 bool MainWindow::maybeSave()
1278 {
1279 if (isWindowModified()) {
1280 QMessageBox::StandardButton ret;
1281 ret = QMessageBox::warning(this, "EncryptPad",
1282 tr("The document has been modified.") + QString("\n") +
1283 tr("Do you want to save your changes?"),
1284 QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
1285 if (ret == QMessageBox::Save)
1286 return save();
1287 else if (ret == QMessageBox::Cancel)
1288 return false;
1289 }
1290 return true;
1291 }
1292
makeDirty()1293 void MainWindow::makeDirty()
1294 {
1295 if(curFile.isEmpty())
1296 return;
1297 setWindowModified(true);
1298 encryptionModified = true;
1299 }
1300
OpenPassphraseDialog(bool confirmationEnabled,std::string * passphrase)1301 bool MainWindow::OpenPassphraseDialog(bool confirmationEnabled, std::string *passphrase)
1302 {
1303 return loadHandler.OpenPassphraseDialog(confirmationEnabled, passphrase);
1304 }
1305
EnterWaitState()1306 void MainWindow::EnterWaitState()
1307 {
1308 isBusy = true;
1309 setAcceptDrops(false);
1310 this->setEnabled(false);
1311 }
1312
ExitWaitState()1313 void MainWindow::ExitWaitState()
1314 {
1315 this->setEnabled(true);
1316 isBusy = false;
1317 setAcceptDrops(true);
1318 }
1319
ConvertToWindowsEOL(QString & in,QByteArray & out)1320 void MainWindow::ConvertToWindowsEOL(QString &in, QByteArray &out)
1321 {
1322 QTextDocument *doc = textEdit->document();
1323 int count = doc->blockCount();
1324 out.reserve(in.size() + count - 1);
1325 QTextStream stm(&in);
1326
1327 QString line = stm.readLine();
1328 out.append(line.toUtf8());
1329 while(!stm.atEnd())
1330 {
1331 out.push_back(0x0D);
1332 out.push_back(0x0A);
1333
1334 QString line = stm.readLine();
1335 out.append(line.toUtf8());
1336 }
1337
1338 // QTextStream behavior: if the last line is empty, the stream is atEnd
1339 // and we have no way of knowing if there was an empty line. So we add a line manually
1340 if(in.endsWith('\n'))
1341 {
1342 out.push_back(0x0D);
1343 out.push_back(0x0A);
1344 }
1345 }
1346
startLoad(const QString & fileName,const QString & encryptionKeyFile,std::string & passphrase,EncryptPad::PacketMetadata & metadata,std::string & kf_passphrase)1347 void MainWindow::startLoad(const QString &fileName, const QString &encryptionKeyFile,
1348 std::string &passphrase, EncryptPad::PacketMetadata &metadata, std::string &kf_passphrase)
1349 {
1350 EnterWaitState();
1351 load_state_machine_.Set(fileName, encryptionKeyFile, passphrase, metadata, kf_passphrase);
1352 load_state_machine_.BeginLoad();
1353 std::fill(std::begin(passphrase), std::end(passphrase), '0');
1354 std::fill(std::begin(kf_passphrase), std::end(kf_passphrase), '0');
1355 }
1356
startSave(const QString & fileName,std::string & kf_passphrase)1357 void MainWindow::startSave(const QString &fileName, std::string &kf_passphrase)
1358 {
1359 using namespace EncryptPad;
1360 EpadResult result = EpadResult::Success;
1361
1362 {
1363 QString str = textEdit->toPlainText();
1364 LOG_INFO << "took the string from the control";
1365 QByteArray byteArr;
1366
1367 if(!windowsEol)
1368 {
1369 byteArr = str.toUtf8();
1370 }
1371 else
1372 {
1373 ConvertToWindowsEOL(str, byteArr);
1374 }
1375 LOG_INFO << "converted to bytes";
1376
1377 Botan::SecureVector<byte> secureVect;
1378 secureVect.resize(byteArr.size());
1379 LOG_INFO << "resized secure vector";
1380 std::copy_n(reinterpret_cast<const byte*>(byteArr.constData()), byteArr.size(), secureVect.data());
1381 LOG_INFO << "copied the bytes to the secure vector";
1382 if(takeBakFile)
1383 {
1384 LOG_INFO << "taking bak file";
1385 if(!TakeBakFile(fileName))
1386 {
1387 LOG_ERROR << "taking bak file failed";
1388 result = EpadResult::BakFileMoveFailed;
1389 }
1390 takeBakFile = false;
1391 }
1392
1393 if(result != EpadResult::BakFileMoveFailed)
1394 {
1395 metadata.file_date = static_cast<EncryptPad::FileDate>(time(NULL));
1396 result = enc.Save(fileName.toUtf8().constData(), secureVect,
1397 encryptionKeyFile.toStdString(), persistEncryptionKeyPath,
1398 &metadata, !kf_passphrase.empty() ? &kf_passphrase : nullptr);
1399 }
1400 }
1401
1402 QString warningMessage;
1403 switch(result)
1404 {
1405 case EpadResult::Success:
1406 break;
1407
1408 //CpadFileIOError
1409 case EpadResult::UnexpectedError:
1410 case EpadResult::IOError:
1411 case EpadResult::IOErrorOutput:
1412 case EpadResult::IOErrorInput:
1413
1414 //InvalidCpadFile
1415 case EpadResult::UnexpectedFormat:
1416 case EpadResult::UnsupportedPacketType:
1417 case EpadResult::UnsupportedAlgo:
1418 case EpadResult::UnsupportedS2K:
1419 case EpadResult::UnsupportedCompressionAlgo:
1420 case EpadResult::MDCError:
1421 case EpadResult::CompressionError:
1422 case EpadResult::InvalidWadFile:
1423 warningMessage = tr("Cannot save '%1'").arg(fileName);
1424 break;
1425 case EpadResult::IOErrorKeyFile:
1426 warningMessage = tr("Cannot open the specified encryption key");
1427 break;
1428 case EpadResult::InvalidKeyFile:
1429 warningMessage = tr("The specified encryption key is invalid");
1430 break;
1431 case EpadResult::CurlIsNotFound:
1432 warningMessage = tr("Cannot download the encryption key. CURL tool is not found");
1433 break;
1434 case EpadResult::CurlExitNonZero:
1435 warningMessage = tr("Cannot download the encryption key. CURL returned non zero exit code");
1436 break;
1437
1438 //EncryptionError:
1439 case EpadResult::InvalidSurrogateIV:
1440 case EpadResult::InvalidPassphrase:
1441 case EpadResult::KeyIsRequiredForSaving:
1442 warningMessage = tr("Unknown encryption error");
1443 break;
1444
1445 case EpadResult::BakFileMoveFailed:
1446 warningMessage = tr("Cannot create bak file");
1447 break;
1448
1449 case EpadResult::InvalidKeyFilePassphrase:
1450 // Ask for the passphrase again
1451 enc.ClearKFPassphrase();
1452 saveFile(fileName);
1453 return;
1454 default:
1455 warningMessage = tr("Unknown error");
1456 }
1457
1458 if(result != EpadResult::Success)
1459 {
1460 QMessageBox::warning(
1461 this,
1462 "EncryptPad",
1463 warningMessage
1464 );
1465
1466 statusBar()->showMessage(tr("Cannot save file"));
1467 saveSuccess = false;
1468 return;
1469 }
1470
1471 setCurrentFile(fileName);
1472 statusBar()->showMessage(tr("File saved"), 2000);
1473 recent_files_service_.PushFile(fileName);
1474 saveSuccess = true;
1475 return;
1476 }
1477
loadFile(const QString & fileName,bool force_kf_passphrase_request)1478 void MainWindow::loadFile(const QString &fileName, bool force_kf_passphrase_request)
1479 {
1480 isBusy = true;
1481 if(!loadHandler.LoadFile(fileName, force_kf_passphrase_request))
1482 isBusy = false;
1483 }
1484
clearPassphrase(bool makeFileDirty)1485 void MainWindow::clearPassphrase(bool makeFileDirty)
1486 {
1487 enc.SetIsPlainText();
1488 if(makeFileDirty)
1489 makeDirty();
1490 }
1491
clearEncryptionKey(bool makeFileDirty)1492 void MainWindow::clearEncryptionKey(bool makeFileDirty)
1493 {
1494 persistEncryptionKeyPath = false;
1495 setEncryptionKeyFile("");
1496 updateEncryptionKeyStatus();
1497 if(makeFileDirty)
1498 makeDirty();
1499 }
1500
setPassphrase()1501 void MainWindow::setPassphrase()
1502 {
1503 bool result = OpenPassphraseDialog(true);
1504 if(result)
1505 makeDirty();
1506 }
1507
OpenSetEncryptionKeyDialogue()1508 bool MainWindow::OpenSetEncryptionKeyDialogue()
1509 {
1510 return loadHandler.OpenSetEncryptionKeyDialogue();
1511 }
1512
setEncryptionKeyFile(const QString & file,bool clearKFKeyService)1513 void MainWindow::setEncryptionKeyFile(const QString &file, bool clearKFKeyService)
1514 {
1515 if(file != encryptionKeyFile && clearKFKeyService)
1516 enc.ClearKFPassphrase();
1517
1518 encryptionKeyFile = file;
1519 }
1520
getEncryptionKeyFile() const1521 const QString &MainWindow::getEncryptionKeyFile() const
1522 {
1523 return encryptionKeyFile;
1524 }
1525
setFileProperties()1526 void MainWindow::setFileProperties()
1527 {
1528 FilePropertiesDialog dlg(this);
1529 dlg.SetUiFromMetadata(metadata);
1530 if(dlg.exec() == QDialog::Rejected || !dlg.GetIsDirty())
1531 return;
1532
1533 dlg.UpdateMetadataFromUi(metadata);
1534 // if an algorithm has changed, the keys need to be regenerated.
1535 clearPassphrase();
1536 }
1537
setEncryptionKey()1538 void MainWindow::setEncryptionKey()
1539 {
1540 bool result = OpenSetEncryptionKeyDialogue();
1541 if(result)
1542 makeDirty();
1543 }
1544
replace()1545 void MainWindow::replace()
1546 {
1547 if(findDialog->isVisible())
1548 {
1549 findDialog->activateWindow();
1550 return;
1551 }
1552
1553 if(replaceDialog->isVisible())
1554 {
1555 replaceDialog->activateWindow();
1556 return;
1557 }
1558
1559 clearReplaceContext();
1560 QString selectedText = textEdit->textCursor().selectedText();
1561 if(!selectedText.isEmpty())
1562 replaceDialog->SetFind(selectedText);
1563
1564 replaceDialog->show();
1565 replaceDialog->setFindFocus();
1566 }
1567
readOnlyToggled(bool flag)1568 void MainWindow::readOnlyToggled(bool flag)
1569 {
1570 textEdit->setReadOnly(flag);
1571 }
1572
wordWrapToggled(bool flag)1573 void MainWindow::wordWrapToggled(bool flag)
1574 {
1575 textEdit->setWordWrapMode(flag ? QTextOption::WordWrap : QTextOption::NoWrap);
1576 }
1577
windowsEolToggled(bool flag)1578 void MainWindow::windowsEolToggled(bool flag)
1579 {
1580 windowsEol = flag;
1581 }
1582
search()1583 void MainWindow::search()
1584 {
1585 if(findDialog->isVisible())
1586 {
1587 findDialog->activateWindow();
1588 return;
1589 }
1590
1591 if(replaceDialog->isVisible())
1592 {
1593 replaceDialog->activateWindow();
1594 return;
1595 }
1596
1597 QString selectedText = textEdit->textCursor().selectedText();
1598 if(!selectedText.isEmpty())
1599 findDialog->setFindWhat(selectedText);
1600
1601 findDialog->show();
1602 findDialog->setFindFocus();
1603 }
1604
generatePassphrase()1605 void MainWindow::generatePassphrase()
1606 {
1607 PassphraseGenerationDialog dlg(this);
1608 dlg.setSettings(passphraseGenerationSettings);
1609 auto result = dlg.exec();
1610 if(result == QDialog::Rejected)
1611 return;
1612
1613 passphraseGenerationSettings = dlg.getSettings();
1614
1615 QTextCursor cursor = textEdit->textCursor();
1616 if(!dlg.getAllPassphrases())
1617 {
1618 cursor.insertText(dlg.getCurrentPassphrase());
1619 }
1620 else
1621 {
1622 QStringList list = dlg.getPassphrases();
1623 QString text;
1624 foreach(QString pwd, list)
1625 {
1626 if(!text.isEmpty())
1627 text.append(QChar::CarriageReturn);
1628 text.append(pwd);
1629 }
1630 cursor.insertText(text);
1631 }
1632 }
1633
gotoTriggered()1634 void MainWindow::gotoTriggered()
1635 {
1636 QTextCursor cursor = textEdit->textCursor();
1637 QTextDocument *doc = textEdit->document();
1638 int count = doc->blockCount();
1639
1640 int lineNumber = cursor.blockNumber();
1641
1642
1643 int newLineNumber = QInputDialog::getInt(this, tr("Go to"), tr("Line number:"),
1644 lineNumber + 1, 1, count, 1, 0, kDefaultWindowFlags);
1645
1646 newLineNumber = newLineNumber - 1;
1647
1648 if(newLineNumber == lineNumber)
1649 return;
1650
1651 QTextBlock block = doc->findBlockByNumber(newLineNumber);
1652 cursor = QTextCursor(block);
1653 textEdit->setTextCursor(cursor);
1654 textEdit->centerCursor();
1655 textEdit->setFocus();
1656 }
1657
zoomIn()1658 void MainWindow::zoomIn()
1659 {
1660 int pointSize = textEdit->font().pointSize();
1661 if(pointSize >= maxZoomIn)
1662 {
1663 statusBar()->showMessage(tr("Maximum zoom"));
1664 return;
1665 }
1666
1667 textEdit->zoomIn();
1668 currentZoom++;
1669 statusBar()->showMessage(tr("Font size: %1").arg(QString::number(++pointSize)));
1670 }
1671
zoomOut()1672 void MainWindow::zoomOut()
1673 {
1674 int pointSize = textEdit->font().pointSize();
1675 if(pointSize <= minZoomOut)
1676 {
1677 statusBar()->showMessage(tr("Minimum zoom"));
1678 return;
1679 }
1680
1681 textEdit->zoomOut();
1682 currentZoom--;
1683 statusBar()->showMessage(tr("Font size: ").arg(QString::number(--pointSize)));
1684 }
1685
resetZoom()1686 void MainWindow::resetZoom()
1687 {
1688 if(currentZoom == 0)
1689 return;
1690
1691 else if(currentZoom > 0)
1692 textEdit->zoomOut(currentZoom);
1693 else if(currentZoom < 0)
1694 textEdit->zoomIn(-currentZoom);
1695
1696 currentZoom = 0;
1697 int pointSize = textEdit->font().pointSize();
1698 statusBar()->showMessage(tr("Font size: ").arg(QString::number(pointSize)));
1699 }
1700
saveFile(const QString & fileName)1701 bool MainWindow::saveFile(const QString &fileName)
1702 {
1703 saveSuccess = false;
1704 // SaveFile will call startSave that sets the saveSuccess flag
1705 if(!loadHandler.SaveFile(fileName))
1706 return false;
1707
1708 return saveSuccess;
1709 }
1710
setCurrentFile(const QString & fileName)1711 void MainWindow::setCurrentFile(const QString &fileName)
1712 {
1713 curFile = fileName;
1714 if(fileName.length() > 0)
1715 {
1716 QFileInfo fileInfo(fileName);
1717 lastModified = fileInfo.lastModified();
1718 encryptionModified = false;
1719 }
1720
1721 textEdit->document()->setModified(false);
1722 setWindowModified(false);
1723
1724 QString shownName = curFile;
1725 if (curFile.isEmpty())
1726 {
1727 shownName = tr("untitled");
1728 shownName += ".epd";
1729 }
1730 setWindowFilePath(shownName);
1731 updateLineStatus();
1732 }
1733
updateEncryptionKeyStatus()1734 void MainWindow::updateEncryptionKeyStatus()
1735 {
1736 QString key_protected = tr("Key protected");
1737 QString key_not_set = tr("Key not set");
1738 QString persistent = tr("persistent");
1739
1740 QString str = encryptionKeyFile.length() > 0 ?
1741 (QString("<b>") + key_protected) :
1742 (QString("<span style=\"color:#FF0000;\">") + key_not_set + QString("</span>"));
1743
1744 str += persistEncryptionKeyPath ? (QString(" (") + persistent + QString(")</b>"))
1745 : QString("</b>");
1746
1747 if(encryptionKeyFile.length() == 0)
1748 clearEncryptionKeyAct->setEnabled(false);
1749 else
1750 clearEncryptionKeyAct->setEnabled(true);
1751 encryptionKeySet->setText(str);
1752 }
1753
strippedName(const QString & fullFileName)1754 QString MainWindow::strippedName(const QString &fullFileName)
1755 {
1756 return QFileInfo(fullFileName).fileName();
1757 }
1758
setWindowsEol(bool flag)1759 void MainWindow::setWindowsEol(bool flag)
1760 {
1761 windowsEol = flag;
1762 windowsEolAct->setChecked(flag);
1763 }
1764
openFileEncryption()1765 void MainWindow::openFileEncryption()
1766 {
1767 FileEncryptionDialog dlg(this, file_request_service_);
1768 dlg.SetDefaultFileParameters(preferences.defaultFileProperties);
1769 dlg.exec();
1770 }
1771
eventFilter(QObject * obj,QEvent * event)1772 bool MainWindow::eventFilter(QObject *obj, QEvent *event)
1773 {
1774 return false;
1775 }
1776