--- /dev/null
+/*
+ * This file is part of Secret Notes.
+ * Copyright (C) 2010 Janusz Sobczak
+ *
+ * Secret Notes is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Secret Notes is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Secret Notes. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <QDir>
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <qmessagebox.h>
+#include <qtimer.h>
+#include <qfile.h>
+#include <qdatastream.h>
+#include <qinputdialog.h>
+#include <openssl/blowfish.h>
+#include <openssl/sha.h>
+#include "passworddialog.h"
+
+const char filename[] = "secretnotes.scn";
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow),
+ password(NULL),
+ hasPasswordChanged(false),
+ hasReadFile(false)
+{
+ ui->setupUi(this);
+ enableTextEdit(false);
+ undoResetAction = ui->menuOptions->addAction(tr("Undo reset"));
+ connect(undoResetAction, SIGNAL(triggered()),this, SLOT(undoReset()));
+ enableUndoReset(false);
+
+ undoEditAction = ui->menuOptions->addAction(tr("Undo edit"));
+ connect(undoEditAction,SIGNAL(triggered()), this, SLOT(undoEdit()));
+ on_textEdit_undoAvailable(false);
+
+ /* the secret notes file is stored in user home directory */
+ filePath = QDir::fromNativeSeparators(QDir::homePath());
+
+ /* or in $HOME/DOCUMENTS */
+#if defined(DOCUMENTS)
+#define DOC_DIR_(x) #x
+#define DOC_DIR(x) DOC_DIR_(x)
+ QString doc(filePath + QString("/") + QString(DOC_DIR(DOCUMENTS)));
+ QDir dir(doc);
+ if (dir.exists())
+ filePath = doc;
+#endif
+ QTimer::singleShot(1, this, SLOT(readFile()));
+}
+
+MainWindow::~MainWindow()
+{
+ plaintext.fill(0);
+ undoText.fill(0);
+ delete ui;
+ if (password)
+ memset(password,0,passLength);
+ delete[] password;
+}
+
+void MainWindow::changeEvent(QEvent *e)
+{
+ QMainWindow::changeEvent(e);
+ switch (e->type()) {
+ case QEvent::LanguageChange:
+ ui->retranslateUi(this);
+ break;
+ default:
+ break;
+ }
+}
+
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+ QFile file(filePath + QString("/") + filename);
+ QString text = ui->textEdit->toPlainText();
+
+ /* don't save the file if content hasn't changed */
+ if (((text == plaintext) && (!hasPasswordChanged)) ||
+ (!ui->textEdit->isEnabled()) ||
+ (text.length() < 1)) {
+ text.fill(0);
+ return;
+ }
+
+ QByteArray encoded;
+ if (!password)
+ askNewPassword();
+
+ /* has user provided a password */
+ if (!password) {
+ text.fill(0);
+ return;
+ }
+
+ encode(encoded, text);
+ text.fill(0);
+
+ if (encoded.length() < 1)
+ return;
+
+ if (!file.open(QIODevice::WriteOnly)) {
+ QMessageBox::warning(this, tr("File open failed"),
+ tr("Can't open %1").arg(filename),QMessageBox::Ok);
+ } else {
+ QDataStream stream(&file);
+ stream << QString("SECRETNOTES");
+ stream << (short int)1;
+ stream << encoded;
+ hasPasswordChanged = false;
+ }
+ text.fill(0);
+ encoded.fill(0);
+ event->accept();
+}
+
+void MainWindow::readFile()
+{
+ /* we want to call readFile only once */
+ if (hasReadFile)
+ return;
+ hasReadFile = true;
+
+ QFile file(filePath + QString("/") + filename);
+ if (!file.open(QIODevice::ReadOnly)) {
+ ui->textEdit->setPlainText("");
+ enableTextEdit(true);
+ askNewPassword();
+ QMessageBox::information(this,tr("Welcome"),
+ tr("Welcome to Secret Notes.\n"
+ "Your data will be saved automatically\n"
+ "when you close Secret Notes."));
+ } else {
+ QString decoded;
+ QString head;
+ short int version;
+ QByteArray text;
+ QDataStream stream(&file);
+ stream >> head;
+ stream >> version;
+ if ((head != QString("SECRETNOTES")) || (version != 1))
+ return;
+ stream >> text;
+ if (text.length() > 0) {
+ queryPassword(false);
+ if (!decode(decoded, text)) {
+ int i;
+ bool ok;
+ for (i=0; i<2; i++) {
+ queryPassword(true);
+ ok = decode(decoded, text);
+ if (ok)
+ break;
+ }
+ if (!ok)
+ return;
+ }
+ plaintext.fill(0);
+ plaintext = decoded;
+ ui->textEdit->setPlainText(plaintext);
+ enableTextEdit(true);
+ }
+ decoded.fill(0);
+ }
+}
+
+void MainWindow::queryPassword(bool retry)
+{
+ if (password && !retry)
+ return;
+ bool ok;
+ QString pass = QInputDialog::getText(this,
+ tr("Password"),
+ tr("Enter passphrase:"),
+ QLineEdit::PasswordEchoOnEdit,"", &ok);
+ if (ok) {
+ setPassword(pass);
+ pass.fill(0);
+ }
+}
+
+bool MainWindow::decode(QString &output, const QByteArray &input)
+{
+ int size = input.length();
+ int length = size - 8 - SHA_DIGEST_LENGTH;
+ if (length < 0)
+ return false;
+ if (!password)
+ return false;
+ char *out = new char[size+1];
+ out[size] = 0;
+
+ blowfish((unsigned char*)out, (unsigned char*)input.constData(), size, 0);
+
+ const char *text = out + 8 + SHA_DIGEST_LENGTH;
+ /* verify SHA checksum */
+ unsigned char *md =
+ SHA1((unsigned char *)text, length, NULL);
+ unsigned char *digest = (unsigned char *)out + 8;
+ for (int i=0; i<SHA_DIGEST_LENGTH; i++) {
+ if (md[i] != digest[i]) {
+ memset(out, 0, size);
+ delete[] out;
+ return false;
+ }
+ }
+
+ output = QString::fromUtf8(qUncompress(QByteArray(text, length)));
+ memset(out, 0, size);
+ delete[] out;
+ return true;
+}
+
+void MainWindow::encode(QByteArray &output, const QString &input)
+{
+ QByteArray bytes = qCompress(input.toUtf8());
+ int length = bytes.size();
+ int size = 8 + SHA_DIGEST_LENGTH + length;
+ QByteArray in;
+ char *out = new char[size];
+ int i;
+
+ in.reserve(size);
+ /* add 8 psuedo random bytes */
+ qsrand((uint)&bytes + size);
+ for(i=0; i<8; i++)
+ in.append((char)qrand());
+
+ /* add SHA checksum */
+ unsigned char *md = SHA1((unsigned char*)bytes.constData(), length, NULL);
+ for(i=0; i<SHA_DIGEST_LENGTH; i++)
+ in.append((char)md[i]);
+
+ /* add input text */
+ in.append(bytes);
+
+ blowfish((unsigned char*)out, (unsigned char*)in.constData(), size, 1);
+ output = QByteArray(out, size);
+ bytes.fill(0);
+ in.fill(0);
+ delete[] out;
+}
+
+void MainWindow::blowfish(unsigned char *output, const unsigned char *input,
+ int length, int enc)
+{
+ BF_KEY key;
+ unsigned char ivec[8];
+ int num = 0;
+
+ BF_set_key(&key, passLength, password);
+ BF_cfb64_encrypt(input, output, length, &key, ivec, &num, enc);
+ memset(&key, 0, sizeof(BF_KEY));
+}
+
+void MainWindow::on_actionChange_password_triggered()
+{
+ PasswordDialog dialog(this, (char*)password);
+ if (dialog.exec() == QDialog::Accepted) {
+ QString pass = dialog.getPassword();
+ setPassword(pass);
+ pass.fill(0);
+ }
+}
+
+void MainWindow::on_actionReset_secret_notes_triggered()
+{
+ if (QMessageBox::question(this, tr("Reset secret notes"),
+ tr("Do you want to reset Secret Notes?\n"
+ "This will destroy all contents!"),
+ QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+ if (password)
+ memset(password, 0, passLength);
+ delete[] password;
+ password = NULL;
+ if (plaintext.length() > 0) {
+ undoText.fill(0);
+ undoText = plaintext;
+ }
+
+ if (undoText.length() > 0)
+ enableUndoReset(true);
+ ui->textEdit->clear();
+ enableTextEdit(true);
+ askNewPassword();
+}
+
+void MainWindow::setPassword(const QString &text)
+{
+ int length = text.length();
+ if (length > 0) {
+ if (password)
+ memset(password, 0, passLength);
+ delete[] password;
+ password = new unsigned char[length+1];
+ int i;
+ for(i=0; i<length; i++)
+ password[i] = text.constData()[i].toAscii();
+ password[i] = 0;
+ passLength = length;
+ hasPasswordChanged = true;
+ }
+}
+
+void MainWindow::askNewPassword()
+{
+ int retries = 3;
+ PasswordDialog dialog(this);
+ dialog.hideOldPassword(true);
+ while (retries--) {
+ if (dialog.exec() == QDialog::Accepted) {
+ QString pass = dialog.getPassword();
+ setPassword(pass);
+ pass.fill(0);
+ return;
+ }
+ }
+}
+
+void MainWindow::enableTextEdit(bool ena)
+{
+ ui->textEdit->setEnabled(ena);
+ if (!ena) {
+ ui->textEdit->setPlainText(tr("Secret Notes disabled"));
+ }
+}
+
+void MainWindow::enableUndoReset(bool ena)
+{
+ undoResetAction->setVisible(ena);
+}
+
+void MainWindow::undoReset()
+{
+ plaintext.fill(0);
+ plaintext = undoText;
+ undoText.fill(0);
+ undoText = "";
+ ui->textEdit->setPlainText(plaintext);
+ enableUndoReset(false);
+ enableTextEdit(true);
+}
+
+void MainWindow::undoEdit()
+{
+ ui->textEdit->undo();
+}
+
+void MainWindow::on_textEdit_undoAvailable(bool b)
+{
+ undoEditAction->setVisible(b);
+}