Changeset View
Changeset View
Standalone View
Standalone View
src/buffer/katetextbuffer.cpp
Show All 28 Lines | |||||
29 | #include "katedocument.h" | 29 | #include "katedocument.h" | ||
30 | #include "kateview.h" | 30 | #include "kateview.h" | ||
31 | #include "katepartdebug.h" | 31 | #include "katepartdebug.h" | ||
32 | 32 | | |||
33 | #ifndef Q_OS_WIN | 33 | #ifndef Q_OS_WIN | ||
34 | #include <unistd.h> | 34 | #include <unistd.h> | ||
35 | #endif | 35 | #endif | ||
36 | 36 | | |||
37 | #include <QSaveFile> | 37 | #include <QFile> | ||
38 | #include <QTemporaryFile> | 38 | #include <QTemporaryFile> | ||
39 | #include <QFileInfo> | 39 | #include <QFileInfo> | ||
40 | #include <QCryptographicHash> | 40 | #include <QCryptographicHash> | ||
41 | #include <QBuffer> | 41 | #include <QBuffer> | ||
42 | 42 | | |||
43 | #if 0 | 43 | #if 0 | ||
44 | #define BUFFER_DEBUG qCDebug(LOG_KTE) | 44 | #define BUFFER_DEBUG qCDebug(LOG_KTE) | ||
45 | #else | 45 | #else | ||
▲ Show 20 Lines • Show All 712 Lines • ▼ Show 20 Line(s) | 751 | { | |||
758 | } | 758 | } | ||
759 | if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32 | 759 | if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32 | ||
760 | setGenerateByteOrderMark(true); | 760 | setGenerateByteOrderMark(true); | ||
761 | } | 761 | } | ||
762 | } | 762 | } | ||
763 | 763 | | |||
764 | bool TextBuffer::save(const QString &filename) | 764 | bool TextBuffer::save(const QString &filename) | ||
765 | { | 765 | { | ||
766 | // codec must be set! | 766 | /** | ||
767 | * codec must be set, else below we fail! | ||||
768 | */ | ||||
767 | Q_ASSERT(m_textCodec); | 769 | Q_ASSERT(m_textCodec); | ||
768 | 770 | | |||
769 | const bool newFile = !QFile::exists(filename); | 771 | /** | ||
772 | * construct correct filter device | ||||
773 | * we try to use the same compression as for opening | ||||
774 | * for compression none, we just skip the wrapping and directly write to the file | ||||
775 | */ | ||||
776 | const KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(m_mimeTypeForFilterDev); | ||||
777 | QScopedPointer<QIODevice> saveFile((type == KCompressionDevice::None) ? static_cast<QIODevice*>(new QFile(filename)) : static_cast<QIODevice*>(new KCompressionDevice(filename, type))); | ||||
770 | 778 | | |||
771 | /** | 779 | /** | ||
772 | * Memorize owner and group. Due to design of QSaveFile we will have to re-set them after save is complete. | 780 | * if we want to use kauth anyways or we can open the file write only => use temporary file and try to use authhelper | ||
773 | */ | 781 | */ | ||
774 | uint ownerId = -2; | 782 | uint ownerId = -2; | ||
775 | uint groupId = -2; | 783 | uint groupId = -2; | ||
776 | if (!newFile) { | 784 | QScopedPointer<QIODevice> temporaryBuffer; | ||
777 | QFileInfo fileInfo(filename); | 785 | if (m_alwaysUseKAuthForSave || !saveFile->open(QIODevice::WriteOnly)) { | ||
786 | /** | ||||
787 | * Memorize owner and group. | ||||
788 | */ | ||||
789 | const QFileInfo fileInfo(filename); | ||||
790 | if (fileInfo.exists()) { | ||||
778 | ownerId = fileInfo.ownerId(); | 791 | ownerId = fileInfo.ownerId(); | ||
779 | groupId = fileInfo.groupId(); | 792 | groupId = fileInfo.groupId(); | ||
780 | } | 793 | } | ||
781 | 794 | | |||
782 | /** | | |||
783 | * use QSaveFile for file saving | | |||
784 | */ | | |||
785 | QScopedPointer<QIODevice> saveFile(new QSaveFile(filename)); | | |||
786 | static_cast<QSaveFile *>(saveFile.data())->setDirectWriteFallback(true); | | |||
787 | | ||||
788 | bool usingTemporaryBuffer = false; | | |||
789 | | ||||
790 | // open QSaveFile for write | | |||
791 | if (m_alwaysUseKAuthForSave || !saveFile->open(QIODevice::WriteOnly)) { | | |||
792 | | ||||
793 | // if that fails we need more privileges to save this file | 795 | // if that fails we need more privileges to save this file | ||
794 | // -> we write to a temporary file and then send its path to KAuth action for privileged save | 796 | // -> we write to a temporary file and then send its path to KAuth action for privileged save | ||
795 | 797 | temporaryBuffer.reset(new QBuffer()); | |||
796 | usingTemporaryBuffer = true; | | |||
797 | | ||||
798 | // we are now saving to a temporary buffer | | |||
799 | saveFile.reset(new QBuffer()); | | |||
800 | 798 | | |||
801 | // open buffer for write and read (read is used for checksum computing and writing to temporary file) | 799 | // open buffer for write and read (read is used for checksum computing and writing to temporary file) | ||
802 | if (!saveFile->open(QIODevice::ReadWrite)) { | 800 | if (!temporaryBuffer->open(QIODevice::ReadWrite)) { | ||
803 | return false; | 801 | return false; | ||
804 | } | 802 | } | ||
805 | } | | |||
806 | 803 | | |||
807 | /** | 804 | // we are now saving to a temporary buffer with potential compression proxy | ||
808 | * construct correct filter device and try to open | 805 | saveFile.reset(new KCompressionDevice(temporaryBuffer.data(), false, type)); | ||
809 | */ | 806 | if (!saveFile->open(QIODevice::WriteOnly)) { | ||
810 | KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(m_mimeTypeForFilterDev); | | |||
811 | KCompressionDevice file(saveFile.data(), false, type); | | |||
812 | | ||||
813 | if (!file.open(QIODevice::WriteOnly)) { | | |||
814 | return false; | 807 | return false; | ||
815 | } | 808 | } | ||
809 | } | ||||
816 | 810 | | |||
817 | /** | 811 | /** | ||
818 | * construct stream + disable Unicode headers | 812 | * construct stream + disable Unicode headers | ||
819 | */ | 813 | */ | ||
820 | QTextStream stream(&file); | 814 | QTextStream stream(saveFile.data()); | ||
821 | stream.setCodec(QTextCodec::codecForName("UTF-16")); | 815 | stream.setCodec(QTextCodec::codecForName("UTF-16")); | ||
822 | 816 | | |||
823 | // set the correct codec | 817 | // set the correct codec | ||
824 | stream.setCodec(m_textCodec); | 818 | stream.setCodec(m_textCodec); | ||
825 | 819 | | |||
826 | // generate byte order mark? | 820 | // generate byte order mark? | ||
827 | stream.setGenerateByteOrderMark(generateByteOrderMark()); | 821 | stream.setGenerateByteOrderMark(generateByteOrderMark()); | ||
828 | 822 | | |||
Show All 25 Lines | 844 | if (m_newLineAtEof) { | |||
854 | if (firstChar > -1 || lastLine->length() > 0) { | 848 | if (firstChar > -1 || lastLine->length() > 0) { | ||
855 | stream << eol; | 849 | stream << eol; | ||
856 | } | 850 | } | ||
857 | } | 851 | } | ||
858 | 852 | | |||
859 | // flush stream | 853 | // flush stream | ||
860 | stream.flush(); | 854 | stream.flush(); | ||
861 | 855 | | |||
862 | // close and delete file | 856 | // close the file, we might want to read from underlying buffer below | ||
863 | file.close(); | 857 | saveFile->close(); | ||
864 | | ||||
865 | // flush file | | |||
866 | if (!usingTemporaryBuffer) { | | |||
867 | static_cast<QSaveFile *>(saveFile.data())->flush(); | | |||
868 | } | | |||
869 | 858 | | |||
870 | // did save work? | 859 | // did save work? | ||
871 | // only finalize if stream status == OK | 860 | // only finalize if stream status == OK | ||
872 | bool ok = (stream.status() == QTextStream::Ok); | 861 | if (stream.status() != QTextStream::Ok) { | ||
873 | 862 | return false; | |||
874 | // commit changes | 863 | } | ||
875 | if (ok) { | | |||
876 | | ||||
877 | if (usingTemporaryBuffer) { | | |||
878 | 864 | | |||
865 | /** | ||||
866 | * if we used a QBuffer for kauth, try to move that to the real location with the helper | ||||
867 | */ | ||||
868 | if (temporaryBuffer) { | ||||
879 | // temporary buffer was used to save the file | 869 | // temporary buffer was used to save the file | ||
880 | // -> computing checksum | 870 | // -> computing checksum | ||
881 | // -> saving to temporary file | 871 | // -> saving to temporary file | ||
882 | // -> copying the temporary file to the original file location with KAuth action | 872 | // -> copying the temporary file to the original file location with KAuth action | ||
883 | | ||||
884 | QTemporaryFile tempFile; | 873 | QTemporaryFile tempFile; | ||
885 | if (!tempFile.open()) { | 874 | if (!tempFile.open()) { | ||
886 | return false; | 875 | return false; | ||
887 | } | 876 | } | ||
888 | QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm); | | |||
889 | 877 | | |||
890 | // go to QBuffer start | 878 | // go to QBuffer start | ||
891 | saveFile->seek(0); | 879 | temporaryBuffer->seek(0); | ||
892 | 880 | | |||
893 | // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile | 881 | // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile | ||
894 | char buffer[bufferLength]; | 882 | char buffer[bufferLength]; | ||
895 | qint64 read = -1; | 883 | qint64 read = -1; | ||
896 | while ((read = saveFile->read(buffer, bufferLength)) > 0) { | 884 | QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm); | ||
885 | while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) { | ||||
897 | cryptographicHash.addData(buffer, read); | 886 | cryptographicHash.addData(buffer, read); | ||
898 | if (tempFile.write(buffer, read) == -1) { | 887 | if (tempFile.write(buffer, read) == -1) { | ||
899 | return false; | 888 | return false; | ||
900 | } | 889 | } | ||
901 | } | 890 | } | ||
902 | if (!tempFile.flush()) { | 891 | if (!tempFile.flush()) { | ||
903 | return false; | 892 | return false; | ||
904 | } | 893 | } | ||
905 | 894 | | |||
906 | // compute checksum | | |||
907 | QByteArray checksum = cryptographicHash.result(); | | |||
908 | | ||||
909 | // prepare data for KAuth action | 895 | // prepare data for KAuth action | ||
910 | QVariantMap kAuthActionArgs; | 896 | QVariantMap kAuthActionArgs; | ||
911 | kAuthActionArgs.insert(QLatin1String("sourceFile"), tempFile.fileName()); | 897 | kAuthActionArgs.insert(QLatin1String("sourceFile"), tempFile.fileName()); | ||
912 | kAuthActionArgs.insert(QLatin1String("targetFile"), filename); | 898 | kAuthActionArgs.insert(QLatin1String("targetFile"), filename); | ||
913 | kAuthActionArgs.insert(QLatin1String("checksum"), checksum); | 899 | kAuthActionArgs.insert(QLatin1String("checksum"), cryptographicHash.result()); | ||
914 | kAuthActionArgs.insert(QLatin1String("ownerId"), ownerId); | 900 | kAuthActionArgs.insert(QLatin1String("ownerId"), ownerId); | ||
915 | kAuthActionArgs.insert(QLatin1String("groupId"), groupId); | 901 | kAuthActionArgs.insert(QLatin1String("groupId"), groupId); | ||
916 | 902 | | |||
917 | // call save with elevated privileges | 903 | // call save with elevated privileges | ||
918 | if (KTextEditor::EditorPrivate::unitTestMode()) { | 904 | if (KTextEditor::EditorPrivate::unitTestMode()) { | ||
919 | | ||||
920 | // unit testing purposes only | 905 | // unit testing purposes only | ||
921 | ok = SecureTextBuffer::savefile(kAuthActionArgs).succeeded(); | 906 | if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) { | ||
922 | 907 | return false; | |||
908 | } | ||||
923 | } else { | 909 | } else { | ||
924 | | ||||
925 | KAuth::Action kAuthSaveAction(QLatin1String("org.kde.ktexteditor.katetextbuffer.savefile")); | 910 | KAuth::Action kAuthSaveAction(QLatin1String("org.kde.ktexteditor.katetextbuffer.savefile")); | ||
926 | kAuthSaveAction.setHelperId(QLatin1String("org.kde.ktexteditor.katetextbuffer")); | 911 | kAuthSaveAction.setHelperId(QLatin1String("org.kde.ktexteditor.katetextbuffer")); | ||
927 | kAuthSaveAction.setArguments(kAuthActionArgs); | 912 | kAuthSaveAction.setArguments(kAuthActionArgs); | ||
928 | KAuth::ExecuteJob *job = kAuthSaveAction.execute(); | 913 | KAuth::ExecuteJob *job = kAuthSaveAction.execute(); | ||
929 | ok = job->exec(); | 914 | if (!job->exec()) { | ||
930 | 915 | return false; | |||
931 | } | | |||
932 | | ||||
933 | } else { | | |||
934 | | ||||
935 | // standard save without elevated privileges | | |||
936 | | ||||
937 | QSaveFile *saveFileLocal = static_cast<QSaveFile *>(saveFile.data()); | | |||
938 | | ||||
939 | if (!newFile) { | | |||
940 | // ensure correct owner | | |||
941 | SecureTextBuffer::setOwner(saveFileLocal->handle(), ownerId, groupId); | | |||
942 | } | 916 | } | ||
943 | | ||||
944 | ok = saveFileLocal->commit(); | | |||
945 | | ||||
946 | } | 917 | } | ||
947 | } | 918 | } | ||
948 | 919 | | |||
949 | // remember this revision as last saved if we had success! | 920 | // remember this revision as last saved | ||
950 | if (ok) { | | |||
951 | m_history.setLastSavedRevision(); | 921 | m_history.setLastSavedRevision(); | ||
952 | } | | |||
953 | 922 | | |||
954 | // report CODEC + ERRORS | 923 | // inform that we have saved the state | ||
955 | BUFFER_DEBUG << "Saved file " << filename << "with codec" << m_textCodec->name() << (ok ? "without" : "with") << "errors"; | | |||
956 | | ||||
957 | if (ok) { | | |||
958 | markModifiedLinesAsSaved(); | 924 | markModifiedLinesAsSaved(); | ||
959 | } | | |||
960 | 925 | | |||
961 | // emit signal on success | 926 | // emit that file was saved and be done | ||
962 | if (ok) { | | |||
963 | emit saved(filename); | 927 | emit saved(filename); | ||
964 | } | 928 | return true; | ||
965 | | ||||
966 | // return success or not | | |||
967 | return ok; | | |||
968 | } | 929 | } | ||
969 | 930 | | |||
970 | void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute) | 931 | void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute) | ||
971 | { | 932 | { | ||
972 | /** | 933 | /** | ||
973 | * ignore calls if no document is around | 934 | * ignore calls if no document is around | ||
974 | */ | 935 | */ | ||
975 | if (!m_document) { | 936 | if (!m_document) { | ||
▲ Show 20 Lines • Show All 70 Lines • Show Last 20 Lines |