diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,7 @@ # KTextEditor interface sources set(ktexteditor_LIB_SRCS # text buffer & buffer helpers +buffer/katetextbuffer_secure.cpp buffer/katetextbuffer.cpp buffer/katetextblock.cpp buffer/katetextline.cpp @@ -334,5 +335,17 @@ ecm_generate_pri_file(BASE_NAME KTextEditor LIB_NAME KF5TextEditor DEPS "KParts" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextEditor) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) + +add_executable(katetextbuffer_helper + buffer/katetextbuffer_secure.cpp + buffer/katetextline.cpp) +target_link_libraries(katetextbuffer_helper + KF5::Archive + KF5::Auth +) +install(TARGETS katetextbuffer_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR} ) +kauth_install_helper_files(katetextbuffer_helper org.kde.ktexteditor.katetextbuffer root) +kauth_install_actions(org.kde.ktexteditor.katetextbuffer buffer/org.kde.ktexteditor.katetextbuffer.actions) + # add part add_subdirectory(part) diff --git a/src/buffer/katetextbuffer.cpp b/src/buffer/katetextbuffer.cpp --- a/src/buffer/katetextbuffer.cpp +++ b/src/buffer/katetextbuffer.cpp @@ -21,6 +21,7 @@ #include "config.h" #include "katetextbuffer.h" +#include "katetextbuffer_secure.h" #include "katetextloader.h" // this is unfortunate, but needed for performance @@ -763,111 +764,45 @@ // codec must be set! Q_ASSERT(m_textCodec); -#ifndef Q_OS_WIN - const bool newFile = !QFile::exists(filename); -#endif - - /** - * use QSaveFile for save write + rename - */ - QSaveFile saveFile(filename); - saveFile.setDirectWriteFallback(true); - - if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - return false; - } - - /** - * construct correct filter device and try to open - */ - KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(m_mimeTypeForFilterDev); - KCompressionDevice file(&saveFile, false, type); - - if (!file.open(QIODevice::WriteOnly)) { - return false; - } - - /** - * construct stream + disable Unicode headers - */ - QTextStream stream(&file); - stream.setCodec(QTextCodec::codecForName("UTF-16")); - - // set the correct codec - stream.setCodec(m_textCodec); - - // generate byte order mark? - stream.setGenerateByteOrderMark(generateByteOrderMark()); - - // our loved eol string ;) - QString eol = QStringLiteral("\n"); //m_doc->config()->eolString (); - if (endOfLineMode() == eolDos) { - eol = QStringLiteral("\r\n"); - } else if (endOfLineMode() == eolMac) { - eol = QStringLiteral("\r"); - } - - // just dump the lines out ;) + // prepare parameters for calling helper method + QString textCodec = QString::fromLatin1(m_textCodec->name()); + int eolMode = static_cast(endOfLineMode()); + QList dataToWrite; for (int i = 0; i < m_lines; ++i) { - // get line to save - Kate::TextLine textline = line(i); - - stream << textline->text(); - - // append correct end of line string - if ((i + 1) < m_lines) { - stream << eol; - } + const Kate::TextLine textline = line(i); + QString line = textline->string(); + dataToWrite.push_back(QVariant(line)); } - if (m_newLineAtEof) { - Q_ASSERT(m_lines > 0); // see .h file - const Kate::TextLine lastLine = line(m_lines - 1); - const int firstChar = lastLine->firstChar(); - if (firstChar > -1 || lastLine->length() > 0) { - stream << eol; - } - } - - // flush stream - stream.flush(); + // trying regular save + bool ok = TextBufferSecure().saveInternal(filename, m_mimeTypeForFilterDev, textCodec, generateByteOrderMark(), eolMode, m_newLineAtEof, dataToWrite); - // close and delete file - file.close(); + // save was not successful, trying with elevated privileges + if (!ok) { - // flush file - if (!saveFile.flush()) { - return false; - } + // prepare parameter map for helper + QVariantMap args; + args[QLatin1String("filename")] = filename; + args[QLatin1String("mimeTypeForFilterDev")] = m_mimeTypeForFilterDev; + args[QLatin1String("textCodec")] = textCodec; + args[QLatin1String("generateByteOrderMark")] = generateByteOrderMark(); + args[QLatin1String("endOfLineMode")] = eolMode; + args[QLatin1String("newLineAtEof")] = m_newLineAtEof; + args[QLatin1String("dataToWrite")] = dataToWrite; -#ifndef Q_OS_WIN - // ensure that the file is written to disk -#ifdef HAVE_FDATASYNC - fdatasync(saveFile.handle()); -#else - fsync(saveFile.handle()); -#endif -#endif + // call save with elevated privileges + Action readAction(QLatin1String("org.kde.ktexteditor.katetextbuffer.save")); + readAction.setHelperId(QLatin1String("org.kde.ktexteditor.katetextbuffer")); + readAction.setArguments(args); + ExecuteJob *job = readAction.execute(); + ok = job->exec(); - // did save work? - // only finalize if stream status == OK - bool ok = (stream.status() == QTextStream::Ok) && saveFile.commit(); + } - // remember this revision as last saved if we had success! if (ok) { m_history.setLastSavedRevision(); } -#ifndef Q_OS_WIN - if (ok && newFile) { // QTemporaryFile sets permissions to 0600, so fixing this - const mode_t mask = umask(0); - umask(mask); - - const mode_t fileMode = 0666 & ~mask; - chmod(QFile::encodeName(filename).constData(), fileMode); - } -#endif - // report CODEC + ERRORS BUFFER_DEBUG << "Saved file " << filename << "with codec" << m_textCodec->name() << (ok ? "without" : "with") << "errors"; diff --git a/src/buffer/katetextbuffer_secure.h b/src/buffer/katetextbuffer_secure.h new file mode 100644 --- /dev/null +++ b/src/buffer/katetextbuffer_secure.h @@ -0,0 +1,30 @@ +#ifndef KATE_TEXTBUFFER_SECURE_H +#define KATE_TEXTBUFFER_SECURE_H + +#include +#include +#include + +#include + +using namespace KAuth; + +class TextBufferSecure : public QObject +{ + Q_OBJECT + +public: + + TextBufferSecure() {}; + + ~TextBufferSecure() {}; + + bool saveInternal(const QString &filename, const QString &mimeTypeForFilterDev, const QString &textCodec, const bool &generateByteOrderMark, + const int &endOfLineMode, const bool &newLineAtEof, const QList &dataToWrite); + +public Q_SLOTS: + ActionReply save(const QVariantMap &args); + +}; + +#endif diff --git a/src/buffer/katetextbuffer_secure.cpp b/src/buffer/katetextbuffer_secure.cpp new file mode 100644 --- /dev/null +++ b/src/buffer/katetextbuffer_secure.cpp @@ -0,0 +1,144 @@ +#include "katetextbuffer_secure.h" +#include "katetextline.h" + +#ifndef Q_OS_WIN +#include + +// needed for umask application +#include +#include +#endif + +//#include +#include +#include +#include +#include +#include +#include +#include +#include + +KAUTH_HELPER_MAIN("org.kde.ktexteditor.katetextbuffer", TextBufferSecure) + +ActionReply TextBufferSecure::save(const QVariantMap& args) +{ + QString filename = args[QLatin1String("filename")].toString(); + QString mimeTypeForFilterDev = args[QLatin1String("mimeTypeForFilterDev")].toString(); + QString textCodec = args[QLatin1String("textCodec")].toString(); + bool generateByteOrderMark = args[QLatin1String("generateByteOrderMark")].toBool(); + int endOfLineMode = args[QLatin1String("endOfLineMode")].toInt(); + bool newLineAtEof = args[QLatin1String("newLineAtEof")].toBool(); + QList dataToWrite = args[QLatin1String("dataToWrite")].toList(); + + bool ok = saveInternal(filename, mimeTypeForFilterDev, textCodec, generateByteOrderMark, endOfLineMode, newLineAtEof, dataToWrite); + + return ok ? ActionReply::SuccessReply() : ActionReply::HelperErrorReply(); +} + +bool TextBufferSecure::saveInternal(const QString &filename, const QString &mimeTypeForFilterDev, const QString &textCodec, + const bool &generateByteOrderMark, const int &endOfLineMode, const bool &newLineAtEof, const QList &dataToWrite) +{ +#ifndef Q_OS_WIN + const bool newFile = !QFile::exists(filename); +#endif + + /** + * use QSaveFile for save write + rename + */ + QSaveFile saveFile(filename); + saveFile.setDirectWriteFallback(true); + + if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + + /** + * construct correct filter device and try to open + */ + KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(mimeTypeForFilterDev); + KCompressionDevice file(&saveFile, false, type); + + if (!file.open(QIODevice::WriteOnly)) { + return false; + } + + /** + * construct stream + disable Unicode headers + */ + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-16")); + + // set the correct codec + stream.setCodec(QTextCodec::codecForName(textCodec.toLatin1())); + + // generate byte order mark? + stream.setGenerateByteOrderMark(generateByteOrderMark); + + // our loved eol string ;) + QString eol = QStringLiteral("\n"); //m_doc->config()->eolString (); + if (endOfLineMode == 1) { + eol = QStringLiteral("\r\n"); + } else if (endOfLineMode == 2) { + eol = QStringLiteral("\r"); + } + + // just dump the lines out ;) + int lineCount = dataToWrite.count(); + for (int i = 0; i < lineCount; ++i) { + // get line to save + Kate::TextLine textline(new Kate::TextLineData(dataToWrite[i].toString())); + + stream << textline->text(); + + // append correct end of line string + if ((i + 1) < lineCount) { + stream << eol; + } + } + + if (newLineAtEof) { + Q_ASSERT(lineCount > 0); // see .h file + Kate::TextLine lastLine(new Kate::TextLineData(dataToWrite[lineCount - 1].toString())); + const int firstChar = lastLine->firstChar(); + if (firstChar > -1 || lastLine->length() > 0) { + stream << eol; + } + } + + // flush stream + stream.flush(); + + // close and delete file + file.close(); + + // flush file + if (!saveFile.flush()) { + return false; + } + +#ifndef Q_OS_WIN + // ensure that the file is written to disk +#ifdef HAVE_FDATASYNC + fdatasync(saveFile.handle()); +#else + fsync(saveFile.handle()); +#endif +#endif + + // did save work? + // only finalize if stream status == OK + bool ok = (stream.status() == QTextStream::Ok) && saveFile.commit(); + +#ifndef Q_OS_WIN + if (ok && newFile) { // QTemporaryFile sets permissions to 0600, so fixing this + const mode_t mask = umask(0); + umask(mask); + + const mode_t fileMode = 0666 & ~mask; + chmod(QFile::encodeName(filename).constData(), fileMode); + } +#endif + + return ok; +} diff --git a/src/buffer/org.kde.ktexteditor.katetextbuffer.actions b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions new file mode 100644 --- /dev/null +++ b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions @@ -0,0 +1,10 @@ +[Domain] +Name=Document Actions +Policy=auth_admin +Persistence=session + +[org.kde.ktexteditor.katetextbuffer.save] +Name=Save Document +Description=Root privileges are needed save this document +Policy=auth_admin +Persistence=session