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