-rw-r--r-- | pwmanager/pwmanager/csv.cpp | 428 | ||||
-rw-r--r-- | pwmanager/pwmanager/csv.h | 91 | ||||
-rw-r--r-- | pwmanager/pwmanager/pwm.cpp | 85 | ||||
-rw-r--r-- | pwmanager/pwmanager/pwm.h | 4 | ||||
-rw-r--r-- | pwmanager/pwmanager/pwmanagerE.pro | 2 |
5 files changed, 608 insertions, 2 deletions
diff --git a/pwmanager/pwmanager/csv.cpp b/pwmanager/pwmanager/csv.cpp new file mode 100644 index 0000000..194edf2 --- a/dev/null +++ b/pwmanager/pwmanager/csv.cpp @@ -0,0 +1,428 @@ +/*************************************************************************** + * * + * copyright (C) 2004 by Michael Buesch * + * email: mbuesch@freenet.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 * + * as published by the Free Software Foundation. * + * * + ***************************************************************************/ + +/*************************************************************************** + * copyright (C) 2004 by Ulf Schenk + * This file is originaly based on version 1.1 of pwmanager + * and was modified to run on embedded devices that run microkde + * The original file version was 1.2 + * $Id$ + **************************************************************************/ + +#include "csv.h" +#include "pwmdoc.h" +#include "pwmexception.h" + +#include <kmessagebox.h> +#include <klocale.h> + +#define MAX_CSV_FILE_SIZE (50 * 1024 * 1024) // bytes + + +Csv::Csv(QWidget *_parent) + : parent (_parent) +{ +} + +Csv::~Csv() +{ +} + +bool Csv::importData(const QString &filepath, + PwMDoc *doc) +{ + bool ret = true; + QByteArray d; + QFile f(filepath); + if (!f.open(IO_ReadOnly)) { + KMessageBox::error(parent, + i18n("Could not open file.\n" + "Does the file exist?"), + i18n("Open error.")); + ret = false; + goto out; + } + if (f.size() > MAX_CSV_FILE_SIZE) { + KMessageBox::error(parent, + i18n("File too big.\nMaximum file size is 1 Byte.", "File too big.\nMaximum file size is %n Bytes.", MAX_CSV_FILE_SIZE), + i18n("File too big.")); + ret = false; + goto out_close; + } + d = f.readAll(); + if (d.isEmpty()) { + KMessageBox::error(parent, + i18n("Could not read file or file empty."), + i18n("Reading failed.")); + ret = false; + goto out_close; + } + if (!doImport(d, doc)) { + KMessageBox::error(parent, + i18n("Import failed.\n" + "Corrupt CSV data format."), + i18n("File corrupt.")); + ret = false; + goto out_close; + } + +out_close: + f.close(); +out: + return ret; +} + +bool Csv::doImport(const QByteArray &d, + PwMDoc *doc) +{ + PwMDataItem di; + //US ENH: initialize all members: + di.clear(); + + int refIndex = 0; + int ret; + QCString s, curCat; + int fieldIndex = 0; + bool inRecord = false; + /* fieldIndex is a reference count to see which + * value we are attaching to di. + * Valid counts are: + * 0 -> category + * 1 -> desc + * 2 -> name + * 3 -> pw + * 4 -> url + * 5 -> launcher + * 6 -> comment + */ + + while (1) { + ret = nextField(&s, d, inRecord, &refIndex); + switch (ret) { + case 0: + // successfully got next field. + inRecord = true; + switch (fieldIndex) { + case 0: // category + if (s.isEmpty()) { + /* This is special case. It's the category + * list terminating empty field. + */ + ++fieldIndex; + } else + curCat = s; + break; + case 1: // desc + di.desc = s; + ++fieldIndex; + break; + case 2: // name + di.name = s; + ++fieldIndex; + break; + case 3: // pw + di.pw = s; + ++fieldIndex; + break; + case 4: // url + di.url = s; + ++fieldIndex; + break; + case 5: // launcher + di.launcher = s; + ++fieldIndex; + break; + case 6: // comment + di.comment = s; + ++fieldIndex; + break; + default: + /* Too many fields in a record. + * We simply throw it away. + */ + break; + } + break; + case 1: + // record complete. + if (fieldIndex == 6) + di.comment = s; + inRecord = false; + fieldIndex = 0; + doc->addEntry(curCat, &di, true); + //US ENH: clear di for the next row + di.clear(); + break; + case 2: + // data completely parsed. + doc->flagDirty(); + return true; + case -1: + // parse error + doc->flagDirty(); + return false; + } + } + BUG(); + return false; +} + +int Csv::nextField(QCString *ret, + const QByteArray &in, + bool inRecord, + int *_refIndex) +{ + int rv = -2; + char c; + bool inField = false; + bool isQuoted = false; + bool searchingTerminator = false; + int refIndex; + int inSize = static_cast<int>(in.size()); + ret->truncate(0); + + for (refIndex = *_refIndex; refIndex < inSize; ++refIndex) { + c = in.at(refIndex); + if (!inField) { + // we have to search the field beginning, now. + switch (c) { + case ' ': // space + case ' ': // tab + // hm, still not the beginning. Go on.. + break; + case '\r': + case '\n': + if (inRecord) { + /* This is the end of the last field in + * the record. + */ + PWM_ASSERT(ret->isEmpty()); + rv = 1; // record end + goto out; + } else { + // hm, still not the beginning. Go on.. + break; + } + case ',': + // Oh, an empty field. How sad. + PWM_ASSERT(ret->isEmpty()); + rv = 0; // field end + goto out; + case '\"': + // this is the quoted beginning. + inField = true; + isQuoted = true; + if (refIndex + 1 >= inSize) + goto unexp_eof; + break; + default: + // this is the unquoted beginning. + inField = true; + isQuoted = false; + *ret += c; + break; + } + } else { + // we are in the field now. Search the end. + if (isQuoted) { + if (searchingTerminator) { + switch (c) { + case '\r': + case '\n': + rv = 1; // record end + goto out; + case ',': + // found it. + rv = 0; // field end + goto out; + default: + // go on. + continue; + } + } + switch (c) { + case '\"': + /* check if this is the end of the + * entry, or just an inline escaped quote. + */ + char next; + if (refIndex + 1 >= inSize) { + // This is the last char, so it's the end. + rv = 2; // data end + goto out; + } + next = in.at(refIndex + 1); + switch (next) { + case '\"': + // This is an escaped double quote. + // So skip next iteration. + refIndex += 1; + *ret += c; + break; + default: + /* end of field. + * We have to search the comma (or newline...), + * which officially terminates the entry. + */ + searchingTerminator = true; + break; + } + break; + default: + // nothing special about the char. Go on! + *ret += c; + break; + } + } else { + switch (c) { + case '\"': + // This is not allowed here. + return -1; // parser error + case '\r': + case '\n': + rv = 1; // record end + goto out; + case ',': + rv = 0; // field end + goto out; + default: + // nothing special about the char. Go on! + *ret += c; + break; + } + } + } + } + // we are at the end of the stream, now! + if (searchingTerminator) { + /* Ok, there's no terminating comma (or newline...), + * because we are at the end. That's perfectly fine. + */ + PWM_ASSERT(inField); + rv = 2; // data end + goto out; + } + if (!isQuoted && inField) { + // That's the end of the last unquoted field. + rv = 2; // data end + goto out; + } + if (!inField) { + // This is expected EOF + rv = 2; // data end + goto out; + } + +unexp_eof: + printDebug("unexpected EOF :("); + return -1; // parser error + +out: + if (!isQuoted) + *ret = ret->stripWhiteSpace(); + *_refIndex = refIndex + 1; + return rv; +} + +bool Csv::exportData(const QString &filepath, + PwMDoc *doc) +{ + PWM_ASSERT(!doc->isDocEmpty()); + bool ret = true; + if (QFile::exists(filepath)) { + int ret; + ret = KMessageBox::questionYesNo(parent, + i18n("This file does already exist.\n" + "Do you want to overwrite it?"), + i18n("Overwrite file?")); + if (ret == KMessageBox::No) + return false; + if (!QFile::remove(filepath)) { + KMessageBox::error(parent, + i18n("Could not delete the old file."), + i18n("Delete error.")); + return false; + } + } + QFile f(filepath); + if (!f.open(IO_ReadWrite)) { + KMessageBox::error(parent, + i18n("Could not open file for writing."), + i18n("Open error.")); + ret = false; + goto out; + } + doc->unlockAll_tempoary(); + if (!doExport(f, doc)) + ret = false; + doc->unlockAll_tempoary(true); + f.close(); +out: + return ret; +} + +bool Csv::doExport(QFile &f, + PwMDoc *doc) +{ + unsigned int numCat = doc->numCategories(); + unsigned int numEntr; + unsigned int i, j; + PwMDataItem d; + QCString s, catName; + QByteArray b; + + for (i = 0; i < numCat; ++i) { + numEntr = doc->numEntries(i); + catName = newField(doc->getCategory(i)->c_str()); + for (j = 0; j < numEntr; ++j) { + doc->getEntry(i, j, &d); + s = catName; + s += ",,"; + s += newField(d.desc.c_str()); + s += ","; + s += newField(d.name.c_str()); + s += ","; + s += newField(d.pw.c_str()); + s += ","; + s += newField(d.url.c_str()); + s += ","; + s += newField(d.launcher.c_str()); + s += ","; + s += newField(d.comment.c_str()); + s += "\r\n"; + b = s; + // remove \0 termination +#ifndef PWM_EMBEDDED + b.resize(b.size() - 1, QGArray::SpeedOptim); +#else + b.resize(b.size() - 1); +#endif + if (!f.writeBlock(b)) + return false; + } + } + return true; +} + +QCString Csv::newField(QCString s) +{ + if (s.isEmpty()) + return QCString(); + QCString ret("\""); +#ifndef PWM_EMBEDDED + s.replace('\"', "\"\""); +#else + s.replace(QRegExp("\""), "\"\""); +#endif + ret += s; + ret += "\""; + return ret; +} diff --git a/pwmanager/pwmanager/csv.h b/pwmanager/pwmanager/csv.h new file mode 100644 index 0000000..6f3c1e1 --- a/dev/null +++ b/pwmanager/pwmanager/csv.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * * + * copyright (C) 2004 by Michael Buesch * + * email: mbuesch@freenet.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 * + * as published by the Free Software Foundation. * + * * + ***************************************************************************/ + +/*************************************************************************** + * copyright (C) 2004 by Ulf Schenk + * This file is originaly based on version 1.1 of pwmanager + * and was modified to run on embedded devices that run microkde + * The original file version was 1.2 + * $Id$ + **************************************************************************/ + + +#ifndef __PWMANAGER_CSV_H +#define __PWMANAGER_CSV_H + +#include <qcstring.h> +#include <qfile.h> + + +class PwMDoc; +class QString; +class QWidget; + +/** class for CSV (Comma Separated Value) export and import. + * + * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm + * http://en.wikipedia.org/wiki/Comma-separated_values + * + * There are two types of CSV output we can produce. + * One with Category support (recommended): + * "Category 1",, "Desc 1", "Username 1", "Password 1", "URL 1", "Launcher 1", "Comment 1" + * "Category 1",, "Desc 2", "Username 2", "Password 2", "URL 2", "Launcher 2", "Comment 2" + * ... + * The empty "" is neccessary, because in future versions we will + * support nested Categories. We want to be future compatible, now. + * + * And one without Category support: + *FIXME: it's not implemented, yet. ;) + * "Desc 1", "Username 1", "Password 1", "URL 1", "Launcher 1", "Comment 1" + * "Desc 2", "Username 2", "Password 2", "URL 2", "Launcher 2", "Comment 2" + * ... + * + */ +class Csv +{ +public: + Csv(QWidget *_parent); + ~Csv(); + + /** import data from "filepath" into "doc" */ + bool importData(const QString &filepath, + PwMDoc *doc); + /** export data from "doc" to "filepath" */ + bool exportData(const QString &filepath, + PwMDoc *doc); + +protected: + /** do the import process. */ + bool doImport(const QByteArray &d, + PwMDoc *doc); + /** parse for the next field. + * @return Return values are: + * 0 -> successfully got the next field. + * 1 -> record end. + * 2 -> data end. + * -1 -> parser error. + */ + int nextField(QCString *ret, + const QByteArray &in, + bool inRecord, + int *_refIndex); + /** do the export process. */ + bool doExport(QFile &f, + PwMDoc *doc); + /** generate a new data field string. */ + QCString newField(QCString s); + +protected: + /** current parent widget. */ + QWidget *parent; +}; + +#endif // __PWMANAGER_CSV_H diff --git a/pwmanager/pwmanager/pwm.cpp b/pwmanager/pwmanager/pwm.cpp index 66d26d6..ac0c978 100644 --- a/pwmanager/pwmanager/pwm.cpp +++ b/pwmanager/pwmanager/pwm.cpp @@ -55,8 +55,9 @@ #include "pwmprint.h" #include "addentrywndimpl.h" #include "globalstuff.h" #include "findwndimpl.h" +#include "csv.h" #ifdef CONFIG_KWALLETIF # include "kwalletif.h" # include "kwalletemu.h" @@ -112,17 +113,19 @@ enum { }; // Button IDs for "export" popup menu (in "file" popup menu) enum { BUTTON_POPUP_EXPORT_TEXT = 0, - BUTTON_POPUP_EXPORT_GPASMAN + BUTTON_POPUP_EXPORT_GPASMAN, + BUTTON_POPUP_EXPORT_CSV #ifdef CONFIG_KWALLETIF ,BUTTON_POPUP_EXPORT_KWALLET #endif }; // Button IDs for "import" popup menu (in "file" popup menu) enum { BUTTON_POPUP_IMPORT_TEXT = 0, - BUTTON_POPUP_IMPORT_GPASMAN + BUTTON_POPUP_IMPORT_GPASMAN, + BUTTON_POPUP_IMPORT_CSV #ifdef CONFIG_KWALLETIF ,BUTTON_POPUP_IMPORT_KWALLET #endif }; @@ -180,12 +183,14 @@ PwM::PwM(PwMInit *_init, PwMDoc *doc, } PwM::~PwM() { + //qDebug("PwM::~PwM()"); disconnect(curDoc(), SIGNAL(docClosed(PwMDoc *)), this, SLOT(docClosed(PwMDoc *))); conf()->confWndMainWndSize(size()); emit closed(this); + //qDebug("PwM::~PwM() emited closed(this)"); delete view; } void PwM::initMenubar() @@ -240,8 +245,10 @@ void PwM::initMenubar() exportPopup->insertItem(i18n("&Text-file..."), this, SLOT(exportToText()), 0, BUTTON_POPUP_EXPORT_TEXT); exportPopup->insertItem(i18n("&Gpasman / Kpasman ..."), this, SLOT(exportToGpasman()), 0, BUTTON_POPUP_EXPORT_GPASMAN); + exportPopup->insertItem(i18n("&CSV (Comma Separated Value) ..."), this, + SLOT(exportToCsv()), 0, BUTTON_POPUP_EXPORT_CSV); #ifdef CONFIG_KWALLETIF exportPopup->insertItem(i18n("&KWallet..."), this, SLOT(exportToKWallet()), 0, BUTTON_POPUP_EXPORT_KWALLET); #endif @@ -252,8 +259,10 @@ void PwM::initMenubar() importPopup->insertItem(i18n("&Text-file..."), this, SLOT(importFromText()), 0, BUTTON_POPUP_IMPORT_TEXT); importPopup->insertItem(i18n("&Gpasman / Kpasman ..."), this, SLOT(importFromGpasman()), 0, BUTTON_POPUP_IMPORT_GPASMAN); + importPopup->insertItem(i18n("&CSV (Comma Separated Value) ..."), this, + SLOT(importCsv()), 0, BUTTON_POPUP_IMPORT_CSV); #ifdef CONFIG_KWALLETIF importPopup->insertItem(i18n("&KWallet..."), this, SLOT(importKWallet()), 0, BUTTON_POPUP_IMPORT_KWALLET); #endif @@ -1015,8 +1024,80 @@ void PwM::exportToGpasman() showStatMsg(i18n("Successfully exported data.")); curDoc()->timer()->putLock(DocTimer::id_autoLockTimer); } + + +void PwM::exportToCsv() +{ + PWM_ASSERT(curDoc()); + if (curDoc()->isDocEmpty()) { + KMessageBox::information(this, + i18n + ("Sorry, there is nothing to export;\n" + "please add some passwords first."), + i18n("Nothing to Do")); + return; + } + + curDoc()->timer()->getLock(DocTimer::id_autoLockTimer); + QString fn(KFileDialog::getSaveFileName("*.csv", i18n("*|CSV Text File"), this)); + if (fn.isEmpty()) { + curDoc()->timer()->putLock(DocTimer::id_autoLockTimer); + return; + } + + Csv csv(this); + if (!csv.exportData(fn, curDoc())) { + curDoc()->timer()->putLock(DocTimer::id_autoLockTimer); + showStatMsg(i18n("CSV file export failed.")); + return; + } + showStatMsg(i18n("Successfully exported data.")); + curDoc()->timer()->putLock(DocTimer::id_autoLockTimer); +} + +bool PwM::importCsv() +{ + Csv csv(this); + if (!isVirgin()) { + if (KMessageBox::questionYesNo(this, + i18n("Do you want to import the data\n" + "into the current document? (If you\n" + "select \"no\", a new document will be\n" + "opened.)"), + i18n("Import into This Document?")) + == KMessageBox::No) { + // import the data to a new window. + PwM *newInstance = init->createMainWnd(); + bool ok = newInstance->importCsv(); + if (!ok) { + newInstance->setForceQuit(true); + delete_and_null(newInstance); + } + return ok; + } + } + + QString filename = KFileDialog::getOpenFileName("*.csv", i18n("*|CSV Text File"), this); + if (filename.isEmpty()) + return false; + curDoc()->timer()->getLock(DocTimer::id_autoLockTimer); + if (!csv.importData(filename, curDoc())) { + curDoc()->timer()->putLock(DocTimer::id_autoLockTimer); + showStatMsg(i18n("CSV file import failed.")); + return false; + } + curDoc()->timer()->putLock(DocTimer::id_autoLockTimer); + KMessageBox::information(this, + i18n("Successfully imported the CSV data\n" + "into the current document."), i18n("Successfully Imported")); + showStatMsg(i18n("Successfully imported")); + setVirgin(false); + return true; +} + + void PwM::exportToKWallet() { #ifdef CONFIG_KWALLETIF if (!checkAndAskForKWalletEmu()) diff --git a/pwmanager/pwmanager/pwm.h b/pwmanager/pwmanager/pwm.h index 6ab9d6b..5822d59 100644 --- a/pwmanager/pwmanager/pwm.h +++ b/pwmanager/pwmanager/pwm.h @@ -123,14 +123,18 @@ public slots: /** file/export/gpasman triggered */ void exportToGpasman(); /** file/export/kwallet triggered */ void exportToKWallet(); + /** file/export/csv triggered */ + void exportToCsv(); /** file/import/text triggered */ bool importFromText(); /** file/import/gpasman triggered */ bool importFromGpasman(); /** file/import/kwallet triggered */ bool importKWallet(); + /** file/import/csv triggered */ + bool importCsv(); /** file/print triggered */ void print_slot(); /** manage/add triggered */ //US ENH : changed code to run with older MOC diff --git a/pwmanager/pwmanager/pwmanagerE.pro b/pwmanager/pwmanager/pwmanagerE.pro index 1445bcf..c46e937 100644 --- a/pwmanager/pwmanager/pwmanagerE.pro +++ b/pwmanager/pwmanager/pwmanagerE.pro @@ -61,8 +61,9 @@ binentrygen.h \ blowfish.h \ commentbox.h \ compiler.h \ compressgzip.h \ +csv.h \ findwnd_emb.h \ findwndimpl.h \ genpasswd.h \ getkeycardwnd.h \ @@ -126,8 +127,9 @@ base64.cpp \ binentrygen.cpp \ blowfish.cpp \ commentbox.cpp \ compressgzip.cpp \ +csv.cpp \ findwnd_emb.cpp \ findwndimpl.cpp \ genpasswd.cpp \ getkeycardwnd.cpp \ |