File indexing completed on 2023-09-24 04:11:27
0001 /* 0002 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include "config.h" 0007 #include "kateglobal.h" 0008 0009 #include "katetextbuffer.h" 0010 #include "katetextloader.h" 0011 0012 #include "katedocument.h" 0013 0014 // this is unfortunate, but needed for performance 0015 #include "katepartdebug.h" 0016 #include "kateview.h" 0017 0018 0019 #ifndef Q_OS_WIN 0020 #include <cerrno> 0021 #include <unistd.h> 0022 // sadly there seems to be no possibility in Qt to determine detailed error 0023 // codes about e.g. file open errors, so we need to resort to evaluating 0024 // errno directly on platforms that support this 0025 #define CAN_USE_ERRNO 0026 #endif 0027 0028 #include <QBuffer> 0029 #include <QCryptographicHash> 0030 #include <QFile> 0031 #include <QFileInfo> 0032 #include <QTemporaryFile> 0033 0034 #if HAVE_KAUTH 0035 #include "katesecuretextbuffer_p.h" 0036 #include <KAuth/Action> 0037 #include <KAuth/ExecuteJob> 0038 #endif 0039 0040 #if 0 0041 #define BUFFER_DEBUG qCDebug(LOG_KTE) 0042 #else 0043 #define BUFFER_DEBUG \ 0044 if (0) \ 0045 qCDebug(LOG_KTE) 0046 #endif 0047 0048 namespace Kate 0049 { 0050 TextBuffer::TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize, bool alwaysUseKAuth) 0051 : QObject(parent) 0052 , m_document(parent) 0053 , m_history(*this) 0054 , m_blockSize(blockSize) 0055 , m_lines(0) 0056 , m_lastUsedBlock(0) 0057 , m_revision(0) 0058 , m_editingTransactions(0) 0059 , m_editingLastRevision(0) 0060 , m_editingLastLines(0) 0061 , m_editingMinimalLineChanged(-1) 0062 , m_editingMaximalLineChanged(-1) 0063 , m_encodingProberType(KEncodingProber::Universal) 0064 , m_fallbackTextCodec(nullptr) 0065 , m_textCodec(nullptr) 0066 , m_generateByteOrderMark(false) 0067 , m_endOfLineMode(eolUnix) 0068 , m_lineLengthLimit(4096) 0069 , m_alwaysUseKAuthForSave(alwaysUseKAuth) 0070 { 0071 // minimal block size must be > 0 0072 Q_ASSERT(m_blockSize > 0); 0073 0074 // create initial state 0075 clear(); 0076 } 0077 0078 TextBuffer::~TextBuffer() 0079 { 0080 // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect 0081 m_document = nullptr; 0082 0083 // not allowed during editing 0084 Q_ASSERT(m_editingTransactions == 0); 0085 0086 // kill all ranges, work on copy, they will remove themself from the hash 0087 QSet<TextRange *> copyRanges = m_ranges; 0088 qDeleteAll(copyRanges); 0089 Q_ASSERT(m_ranges.empty()); 0090 0091 // clean out all cursors and lines, only cursors belonging to range will survive 0092 for (TextBlock *block : std::as_const(m_blocks)) { 0093 block->deleteBlockContent(); 0094 } 0095 0096 // delete all blocks, now that all cursors are really deleted 0097 // else asserts in destructor of blocks will fail! 0098 qDeleteAll(m_blocks); 0099 m_blocks.clear(); 0100 0101 // kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks 0102 QSet<TextCursor *> copyCursors = m_invalidCursors; 0103 qDeleteAll(copyCursors); 0104 Q_ASSERT(m_invalidCursors.empty()); 0105 } 0106 0107 void TextBuffer::invalidateRanges() 0108 { 0109 // invalidate all ranges, work on copy, they might delete themself... 0110 const QSet<TextRange *> copyRanges = m_ranges; 0111 for (TextRange *range : copyRanges) { 0112 range->setRange(KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid()); 0113 } 0114 } 0115 0116 void TextBuffer::clear() 0117 { 0118 // not allowed during editing 0119 Q_ASSERT(m_editingTransactions == 0); 0120 0121 invalidateRanges(); 0122 0123 // new block for empty buffer 0124 TextBlock *newBlock = new TextBlock(this, 0); 0125 newBlock->appendLine(QString()); 0126 0127 // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range 0128 for (TextBlock *block : std::as_const(m_blocks)) { 0129 block->clearBlockContent(newBlock); 0130 } 0131 0132 // kill all buffer blocks 0133 qDeleteAll(m_blocks); 0134 m_blocks.clear(); 0135 0136 // insert one block with one empty line 0137 m_blocks.push_back(newBlock); 0138 0139 // reset lines and last used block 0140 m_lines = 1; 0141 m_lastUsedBlock = 0; 0142 0143 // reset revision 0144 m_revision = 0; 0145 0146 // reset bom detection 0147 m_generateByteOrderMark = false; 0148 0149 // reset the filter device 0150 m_mimeTypeForFilterDev = QStringLiteral("text/plain"); 0151 0152 // clear edit history 0153 m_history.clear(); 0154 0155 // we got cleared 0156 Q_EMIT cleared(); 0157 } 0158 0159 TextLine TextBuffer::line(int line) const 0160 { 0161 // get block, this will assert on invalid line 0162 int blockIndex = blockForLine(line); 0163 0164 // get line 0165 return m_blocks.at(blockIndex)->line(line); 0166 } 0167 0168 QString TextBuffer::text() const 0169 { 0170 QString text; 0171 0172 // combine all blocks 0173 for (TextBlock *block : std::as_const(m_blocks)) { 0174 block->text(text); 0175 } 0176 0177 // return generated string 0178 return text; 0179 } 0180 0181 bool TextBuffer::startEditing() 0182 { 0183 // increment transaction counter 0184 ++m_editingTransactions; 0185 0186 // if not first running transaction, do nothing 0187 if (m_editingTransactions > 1) { 0188 return false; 0189 } 0190 0191 // reset information about edit... 0192 m_editingLastRevision = m_revision; 0193 m_editingLastLines = m_lines; 0194 m_editingMinimalLineChanged = -1; 0195 m_editingMaximalLineChanged = -1; 0196 0197 // transaction has started 0198 Q_EMIT editingStarted(); 0199 if (m_document) { 0200 Q_EMIT m_document->KTextEditor::Document::editingStarted(m_document); 0201 } 0202 0203 // first transaction started 0204 return true; 0205 } 0206 0207 bool TextBuffer::finishEditing() 0208 { 0209 // only allowed if still transactions running 0210 Q_ASSERT(m_editingTransactions > 0); 0211 0212 // decrement counter 0213 --m_editingTransactions; 0214 0215 // if not last running transaction, do nothing 0216 if (m_editingTransactions > 0) { 0217 return false; 0218 } 0219 0220 // assert that if buffer changed, the line ranges are set and valid! 0221 Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1)); 0222 Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged)); 0223 Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines)); 0224 Q_ASSERT(!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines)); 0225 0226 // transaction has finished 0227 Q_EMIT editingFinished(); 0228 if (m_document) { 0229 Q_EMIT m_document->KTextEditor::Document::editingFinished(m_document); 0230 } 0231 0232 // last transaction finished 0233 return true; 0234 } 0235 0236 void TextBuffer::wrapLine(const KTextEditor::Cursor position) 0237 { 0238 // debug output for REAL low-level debugging 0239 BUFFER_DEBUG << "wrapLine" << position; 0240 0241 // only allowed if editing transaction running 0242 Q_ASSERT(m_editingTransactions > 0); 0243 0244 // get block, this will assert on invalid line 0245 int blockIndex = blockForLine(position.line()); 0246 0247 // let the block handle the wrapLine 0248 // this can only lead to one more line in this block 0249 // no other blocks will change 0250 // this call will trigger fixStartLines 0251 ++m_lines; // first alter the line counter, as functions called will need the valid one 0252 m_blocks.at(blockIndex)->wrapLine(position, blockIndex); 0253 0254 // remember changes 0255 ++m_revision; 0256 0257 // update changed line interval 0258 if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { 0259 m_editingMinimalLineChanged = position.line(); 0260 } 0261 0262 if (position.line() <= m_editingMaximalLineChanged) { 0263 ++m_editingMaximalLineChanged; 0264 } else { 0265 m_editingMaximalLineChanged = position.line() + 1; 0266 } 0267 0268 // balance the changed block if needed 0269 balanceBlock(blockIndex); 0270 0271 // emit signal about done change 0272 Q_EMIT lineWrapped(position); 0273 if (m_document) { 0274 Q_EMIT m_document->KTextEditor::Document::lineWrapped(m_document, position); 0275 } 0276 } 0277 0278 void TextBuffer::unwrapLine(int line) 0279 { 0280 // debug output for REAL low-level debugging 0281 BUFFER_DEBUG << "unwrapLine" << line; 0282 0283 // only allowed if editing transaction running 0284 Q_ASSERT(m_editingTransactions > 0); 0285 0286 // line 0 can't be unwrapped 0287 Q_ASSERT(line > 0); 0288 0289 // get block, this will assert on invalid line 0290 int blockIndex = blockForLine(line); 0291 0292 // is this the first line in the block? 0293 bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine()); 0294 0295 // let the block handle the unwrapLine 0296 // this can either lead to one line less in this block or the previous one 0297 // the previous one could even end up with zero lines 0298 // this call will trigger fixStartLines 0299 m_blocks.at(blockIndex)->unwrapLine(line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) : nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex); 0300 --m_lines; 0301 0302 // decrement index for later fixup, if we modified the block in front of the found one 0303 if (firstLineInBlock) { 0304 --blockIndex; 0305 } 0306 0307 // remember changes 0308 ++m_revision; 0309 0310 // update changed line interval 0311 if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { 0312 m_editingMinimalLineChanged = line - 1; 0313 } 0314 0315 if (line <= m_editingMaximalLineChanged) { 0316 --m_editingMaximalLineChanged; 0317 } else { 0318 m_editingMaximalLineChanged = line - 1; 0319 } 0320 0321 // balance the changed block if needed 0322 balanceBlock(blockIndex); 0323 0324 // emit signal about done change 0325 Q_EMIT lineUnwrapped(line); 0326 if (m_document) { 0327 Q_EMIT m_document->KTextEditor::Document::lineUnwrapped(m_document, line); 0328 } 0329 } 0330 0331 void TextBuffer::insertText(const KTextEditor::Cursor position, const QString &text) 0332 { 0333 // debug output for REAL low-level debugging 0334 BUFFER_DEBUG << "insertText" << position << text; 0335 0336 // only allowed if editing transaction running 0337 Q_ASSERT(m_editingTransactions > 0); 0338 0339 // skip work, if no text to insert 0340 if (text.isEmpty()) { 0341 return; 0342 } 0343 0344 // get block, this will assert on invalid line 0345 int blockIndex = blockForLine(position.line()); 0346 0347 // let the block handle the insertText 0348 m_blocks.at(blockIndex)->insertText(position, text); 0349 0350 // remember changes 0351 ++m_revision; 0352 0353 // update changed line interval 0354 if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { 0355 m_editingMinimalLineChanged = position.line(); 0356 } 0357 0358 if (position.line() > m_editingMaximalLineChanged) { 0359 m_editingMaximalLineChanged = position.line(); 0360 } 0361 0362 // emit signal about done change 0363 Q_EMIT textInserted(position, text); 0364 if (m_document) { 0365 Q_EMIT m_document->KTextEditor::Document::textInserted(m_document, position, text); 0366 } 0367 } 0368 0369 void TextBuffer::removeText(KTextEditor::Range range) 0370 { 0371 // debug output for REAL low-level debugging 0372 BUFFER_DEBUG << "removeText" << range; 0373 0374 // only allowed if editing transaction running 0375 Q_ASSERT(m_editingTransactions > 0); 0376 0377 // only ranges on one line are supported 0378 Q_ASSERT(range.start().line() == range.end().line()); 0379 0380 // start column <= end column and >= 0 0381 Q_ASSERT(range.start().column() <= range.end().column()); 0382 Q_ASSERT(range.start().column() >= 0); 0383 0384 // skip work, if no text to remove 0385 if (range.isEmpty()) { 0386 return; 0387 } 0388 0389 // get block, this will assert on invalid line 0390 int blockIndex = blockForLine(range.start().line()); 0391 0392 // let the block handle the removeText, retrieve removed text 0393 QString text; 0394 m_blocks.at(blockIndex)->removeText(range, text); 0395 0396 // remember changes 0397 ++m_revision; 0398 0399 // update changed line interval 0400 if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { 0401 m_editingMinimalLineChanged = range.start().line(); 0402 } 0403 0404 if (range.start().line() > m_editingMaximalLineChanged) { 0405 m_editingMaximalLineChanged = range.start().line(); 0406 } 0407 0408 // emit signal about done change 0409 Q_EMIT textRemoved(range, text); 0410 if (m_document) { 0411 Q_EMIT m_document->KTextEditor::Document::textRemoved(m_document, range, text); 0412 } 0413 } 0414 0415 int TextBuffer::blockForLine(int line) const 0416 { 0417 // only allow valid lines 0418 if ((line < 0) || (line >= lines())) { 0419 qFatal("out of range line requested in text buffer (%d out of [0, %d])", line, lines()); 0420 } 0421 0422 // we need blocks and last used block should not be negative 0423 Q_ASSERT(!m_blocks.empty()); 0424 Q_ASSERT(m_lastUsedBlock >= 0); 0425 0426 // shortcut: try last block first 0427 if (m_lastUsedBlock < (int)m_blocks.size()) { 0428 // check if block matches 0429 // if yes, just return again this block 0430 TextBlock *block = m_blocks[m_lastUsedBlock]; 0431 const int start = block->startLine(); 0432 const int lines = block->lines(); 0433 if (start <= line && line < (start + lines)) { 0434 return m_lastUsedBlock; 0435 } 0436 } 0437 0438 // search for right block 0439 // use binary search 0440 // if we leave this loop not by returning the found element we have an error 0441 int blockStart = 0; 0442 int blockEnd = m_blocks.size() - 1; 0443 while (blockEnd >= blockStart) { 0444 // get middle and ensure it is OK 0445 int middle = blockStart + ((blockEnd - blockStart) / 2); 0446 Q_ASSERT(middle >= 0); 0447 Q_ASSERT(middle < (int)m_blocks.size()); 0448 0449 // facts bout this block 0450 TextBlock *block = m_blocks[middle]; 0451 const int start = block->startLine(); 0452 const int lines = block->lines(); 0453 0454 // right block found, remember it and return it 0455 if (start <= line && line < (start + lines)) { 0456 m_lastUsedBlock = middle; 0457 return middle; 0458 } 0459 0460 // half our stuff ;) 0461 if (line < start) { 0462 blockEnd = middle - 1; 0463 } else { 0464 blockStart = middle + 1; 0465 } 0466 } 0467 0468 // we should always find a block 0469 qFatal("line requested in text buffer (%d out of [0, %d[), no block found", line, lines()); 0470 return -1; 0471 } 0472 0473 void TextBuffer::fixStartLines(int startBlock) 0474 { 0475 // only allow valid start block 0476 Q_ASSERT(startBlock >= 0); 0477 Q_ASSERT(startBlock < (int)m_blocks.size()); 0478 0479 // new start line for next block 0480 TextBlock *block = m_blocks.at(startBlock); 0481 int newStartLine = block->startLine() + block->lines(); 0482 0483 // fixup block 0484 for (size_t index = startBlock + 1; index < m_blocks.size(); ++index) { 0485 // set new start line 0486 block = m_blocks.at(index); 0487 block->setStartLine(newStartLine); 0488 0489 // calculate next start line 0490 newStartLine += block->lines(); 0491 } 0492 } 0493 0494 void TextBuffer::balanceBlock(int index) 0495 { 0496 // two cases, too big or too small block 0497 TextBlock *blockToBalance = m_blocks.at(index); 0498 0499 // first case, too big one, split it 0500 if (blockToBalance->lines() >= 2 * m_blockSize) { 0501 // half the block 0502 int halfSize = blockToBalance->lines() / 2; 0503 0504 // create and insert new block behind current one, already set right start line 0505 TextBlock *newBlock = blockToBalance->splitBlock(halfSize); 0506 Q_ASSERT(newBlock); 0507 m_blocks.insert(m_blocks.begin() + index + 1, newBlock); 0508 0509 // split is done 0510 return; 0511 } 0512 0513 // second case: possibly too small block 0514 0515 // if only one block, no chance to unite 0516 // same if this is first block, we always append to previous one 0517 if (index == 0) { 0518 return; 0519 } 0520 0521 // block still large enough, do nothing 0522 if (2 * blockToBalance->lines() > m_blockSize) { 0523 return; 0524 } 0525 0526 // unite small block with predecessor 0527 TextBlock *targetBlock = m_blocks.at(index - 1); 0528 0529 // merge block 0530 blockToBalance->mergeBlock(targetBlock); 0531 0532 // delete old block 0533 delete blockToBalance; 0534 m_blocks.erase(m_blocks.begin() + index); 0535 } 0536 0537 void TextBuffer::debugPrint(const QString &title) const 0538 { 0539 // print header with title 0540 printf("%s (lines: %d bs: %d)\n", qPrintable(title), m_lines, m_blockSize); 0541 0542 // print all blocks 0543 for (size_t i = 0; i < m_blocks.size(); ++i) { 0544 m_blocks.at(i)->debugPrint(i); 0545 } 0546 } 0547 0548 bool TextBuffer::load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec) 0549 { 0550 // fallback codec must exist 0551 Q_ASSERT(m_fallbackTextCodec); 0552 0553 // codec must be set! 0554 Q_ASSERT(m_textCodec); 0555 0556 // first: clear buffer in any case! 0557 clear(); 0558 0559 // construct the file loader for the given file, with correct prober type 0560 Kate::TextLoader file(filename, m_encodingProberType); 0561 0562 // triple play, maximal three loading rounds 0563 // 0) use the given encoding, be done, if no encoding errors happen 0564 // 1) use BOM to decided if Unicode or if that fails, use encoding prober, if no encoding errors happen, be done 0565 // 2) use fallback encoding, be done, if no encoding errors happen 0566 // 3) use again given encoding, be done in any case 0567 for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) { 0568 // kill all blocks beside first one 0569 for (size_t b = 1; b < m_blocks.size(); ++b) { 0570 TextBlock *block = m_blocks.at(b); 0571 block->clearLines(); 0572 delete block; 0573 } 0574 m_blocks.resize(1); 0575 0576 // remove lines in first block 0577 m_blocks.back()->clearLines(); 0578 m_lines = 0; 0579 0580 // try to open file, with given encoding 0581 // in round 0 + 3 use the given encoding from user 0582 // in round 1 use 0, to trigger detection 0583 // in round 2 use fallback 0584 QTextCodec *codec = m_textCodec; 0585 if (i == 1) { 0586 codec = nullptr; 0587 } else if (i == 2) { 0588 codec = m_fallbackTextCodec; 0589 } 0590 0591 if (!file.open(codec)) { 0592 // create one dummy textline, in any case 0593 m_blocks.back()->appendLine(QString()); 0594 m_lines++; 0595 return false; 0596 } 0597 0598 // read in all lines... 0599 encodingErrors = false; 0600 while (!file.eof()) { 0601 // read line 0602 int offset = 0; 0603 int length = 0; 0604 bool currentError = !file.readLine(offset, length); 0605 encodingErrors = encodingErrors || currentError; 0606 0607 // bail out on encoding error, if not last round! 0608 if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) { 0609 BUFFER_DEBUG << "Failed try to load file" << filename << "with codec" << (file.textCodec() ? file.textCodec()->name() : "(null)"); 0610 break; 0611 } 0612 0613 // get Unicode data for this line 0614 const QChar *unicodeData = file.unicode() + offset; 0615 0616 if (longestLineLoaded < length) { 0617 longestLineLoaded = length; 0618 } 0619 0620 // split lines, if too large 0621 do { 0622 // calculate line length 0623 int lineLength = length; 0624 if ((m_lineLengthLimit > 0) && (lineLength > m_lineLengthLimit)) { 0625 // search for place to wrap 0626 int spacePosition = m_lineLengthLimit - 1; 0627 for (int testPosition = m_lineLengthLimit - 1; (testPosition >= 0) && (testPosition >= (m_lineLengthLimit - (m_lineLengthLimit / 10))); 0628 --testPosition) { 0629 // wrap place found? 0630 if (unicodeData[testPosition].isSpace() || unicodeData[testPosition].isPunct()) { 0631 spacePosition = testPosition; 0632 break; 0633 } 0634 } 0635 0636 // wrap the line 0637 lineLength = spacePosition + 1; 0638 length -= lineLength; 0639 tooLongLinesWrapped = true; 0640 } else { 0641 // be done after this round 0642 length = 0; 0643 } 0644 0645 // construct new text line with content from file 0646 // move data pointer 0647 QString textLine(unicodeData, lineLength); 0648 unicodeData += lineLength; 0649 0650 // ensure blocks aren't too large 0651 if (m_blocks.back()->lines() >= m_blockSize) { 0652 m_blocks.push_back(new TextBlock(this, m_blocks.back()->startLine() + m_blocks.back()->lines())); 0653 } 0654 0655 // append line to last block 0656 m_blocks.back()->appendLine(textLine); 0657 ++m_lines; 0658 } while (length > 0); 0659 } 0660 0661 // if no encoding error, break out of reading loop 0662 if (!encodingErrors) { 0663 // remember used codec, might change bom setting 0664 setTextCodec(file.textCodec()); 0665 break; 0666 } 0667 } 0668 0669 // save checksum of file on disk 0670 setDigest(file.digest()); 0671 0672 // remember if BOM was found 0673 if (file.byteOrderMarkFound()) { 0674 setGenerateByteOrderMark(true); 0675 } 0676 0677 // remember eol mode, if any found in file 0678 if (file.eol() != eolUnknown) { 0679 setEndOfLineMode(file.eol()); 0680 } 0681 0682 // remember mime type for filter device 0683 m_mimeTypeForFilterDev = file.mimeTypeForFilterDev(); 0684 0685 // assert that one line is there! 0686 Q_ASSERT(m_lines > 0); 0687 0688 // report CODEC + ERRORS 0689 BUFFER_DEBUG << "Loaded file " << filename << "with codec" << m_textCodec->name() << (encodingErrors ? "with" : "without") << "encoding errors"; 0690 0691 // report BOM 0692 BUFFER_DEBUG << (file.byteOrderMarkFound() ? "Found" : "Didn't find") << "byte order mark"; 0693 0694 // report filter device mime-type 0695 BUFFER_DEBUG << "used filter device for mime-type" << m_mimeTypeForFilterDev; 0696 0697 // emit success 0698 Q_EMIT loaded(filename, encodingErrors); 0699 0700 // file loading worked, modulo encoding problems 0701 return true; 0702 } 0703 0704 const QByteArray &TextBuffer::digest() const 0705 { 0706 return m_digest; 0707 } 0708 0709 void TextBuffer::setDigest(const QByteArray &checksum) 0710 { 0711 m_digest = checksum; 0712 } 0713 0714 void TextBuffer::setTextCodec(QTextCodec *codec) 0715 { 0716 m_textCodec = codec; 0717 0718 // enforce bom for some encodings 0719 int mib = m_textCodec->mibEnum(); 0720 if (mib == 1013 || mib == 1014 || mib == 1015) { // utf16 0721 setGenerateByteOrderMark(true); 0722 } 0723 if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32 0724 setGenerateByteOrderMark(true); 0725 } 0726 } 0727 0728 bool TextBuffer::save(const QString &filename) 0729 { 0730 // codec must be set, else below we fail! 0731 Q_ASSERT(m_textCodec); 0732 0733 SaveResult saveRes = saveBufferUnprivileged(filename); 0734 0735 if (saveRes == SaveResult::Failed) { 0736 return false; 0737 } else if (saveRes == SaveResult::MissingPermissions) { 0738 // either unit-test mode or we're missing permissions to write to the 0739 // file => use temporary file and try to use authhelper 0740 if (!saveBufferEscalated(filename)) { 0741 return false; 0742 } 0743 } 0744 0745 // remember this revision as last saved 0746 m_history.setLastSavedRevision(); 0747 0748 // inform that we have saved the state 0749 markModifiedLinesAsSaved(); 0750 0751 // emit that file was saved and be done 0752 Q_EMIT saved(filename); 0753 return true; 0754 } 0755 0756 bool TextBuffer::saveBuffer(const QString &filename, KCompressionDevice &saveFile) 0757 { 0758 std::unique_ptr<QTextEncoder> encoder(m_textCodec->makeEncoder(generateByteOrderMark() ? QTextCodec::DefaultConversion : QTextCodec::IgnoreHeader)); 0759 0760 // our loved eol string ;) 0761 QString eol = QStringLiteral("\n"); 0762 if (endOfLineMode() == eolDos) { 0763 eol = QStringLiteral("\r\n"); 0764 } else if (endOfLineMode() == eolMac) { 0765 eol = QStringLiteral("\r"); 0766 } 0767 0768 // just dump the lines out ;) 0769 for (int i = 0; i < m_lines; ++i) { 0770 // dump current line 0771 saveFile.write(encoder->fromUnicode(line(i)->text())); 0772 0773 // append correct end of line string 0774 if ((i + 1) < m_lines) { 0775 saveFile.write(encoder->fromUnicode(eol)); 0776 } 0777 0778 // early out on stream errors 0779 if (saveFile.error() != QFileDevice::NoError) { 0780 return false; 0781 } 0782 } 0783 0784 // TODO: this only writes bytes when there is text. This is a fine optimization for most cases, but this makes saving 0785 // an empty file with the BOM set impossible (results to an empty file with 0 bytes, no BOM) 0786 0787 // close the file, we might want to read from underlying buffer below 0788 saveFile.close(); 0789 0790 // did save work? 0791 if (saveFile.error() != QFileDevice::NoError) { 0792 BUFFER_DEBUG << "Saving file " << filename << "failed with error" << saveFile.errorString(); 0793 return false; 0794 } 0795 0796 return true; 0797 } 0798 0799 TextBuffer::SaveResult TextBuffer::saveBufferUnprivileged(const QString &filename) 0800 { 0801 if (m_alwaysUseKAuthForSave) { 0802 // unit-testing mode, simulate we need privileges 0803 return SaveResult::MissingPermissions; 0804 } 0805 0806 // construct correct filter device 0807 // we try to use the same compression as for opening 0808 const KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(m_mimeTypeForFilterDev); 0809 auto saveFile = std::make_unique<KCompressionDevice>(filename, type); 0810 0811 if (!saveFile->open(QIODevice::WriteOnly)) { 0812 #ifdef CAN_USE_ERRNO 0813 if (errno != EACCES) { 0814 return SaveResult::Failed; 0815 } 0816 #endif 0817 return SaveResult::MissingPermissions; 0818 } 0819 0820 if (!saveBuffer(filename, *saveFile)) { 0821 return SaveResult::Failed; 0822 } 0823 0824 return SaveResult::Success; 0825 } 0826 0827 bool TextBuffer::saveBufferEscalated(const QString &filename) 0828 { 0829 #if HAVE_KAUTH 0830 // construct correct filter device 0831 // we try to use the same compression as for opening 0832 const KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(m_mimeTypeForFilterDev); 0833 auto saveFile = std::make_unique<KCompressionDevice>(filename, type); 0834 uint ownerId = -2; 0835 uint groupId = -2; 0836 std::unique_ptr<QIODevice> temporaryBuffer; 0837 0838 // Memorize owner and group. 0839 const QFileInfo fileInfo(filename); 0840 if (fileInfo.exists()) { 0841 ownerId = fileInfo.ownerId(); 0842 groupId = fileInfo.groupId(); 0843 } 0844 0845 // if that fails we need more privileges to save this file 0846 // -> we write to a temporary file and then send its path to KAuth action for privileged save 0847 temporaryBuffer = std::make_unique<QBuffer>(); 0848 0849 // open buffer for write and read (read is used for checksum computing and writing to temporary file) 0850 if (!temporaryBuffer->open(QIODevice::ReadWrite)) { 0851 return false; 0852 } 0853 0854 // we are now saving to a temporary buffer with potential compression proxy 0855 saveFile = std::make_unique<KCompressionDevice>(temporaryBuffer.get(), false, type); 0856 if (!saveFile->open(QIODevice::WriteOnly)) { 0857 return false; 0858 } 0859 0860 if (!saveBuffer(filename, *saveFile)) { 0861 return false; 0862 } 0863 0864 // temporary buffer was used to save the file 0865 // -> computing checksum 0866 // -> saving to temporary file 0867 // -> copying the temporary file to the original file location with KAuth action 0868 QTemporaryFile tempFile; 0869 if (!tempFile.open()) { 0870 return false; 0871 } 0872 0873 // go to QBuffer start 0874 temporaryBuffer->seek(0); 0875 0876 // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile 0877 char buffer[bufferLength]; 0878 qint64 read = -1; 0879 QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm); 0880 while ((read = temporaryBuffer->read(buffer, bufferLength)) > 0) { 0881 cryptographicHash.addData(buffer, read); 0882 if (tempFile.write(buffer, read) == -1) { 0883 return false; 0884 } 0885 } 0886 if (!tempFile.flush()) { 0887 return false; 0888 } 0889 0890 // prepare data for KAuth action 0891 QVariantMap kAuthActionArgs; 0892 kAuthActionArgs.insert(QStringLiteral("sourceFile"), tempFile.fileName()); 0893 kAuthActionArgs.insert(QStringLiteral("targetFile"), filename); 0894 kAuthActionArgs.insert(QStringLiteral("checksum"), cryptographicHash.result()); 0895 kAuthActionArgs.insert(QStringLiteral("ownerId"), ownerId); 0896 kAuthActionArgs.insert(QStringLiteral("groupId"), groupId); 0897 0898 // call save with elevated privileges 0899 if (KTextEditor::EditorPrivate::unitTestMode()) { 0900 // unit testing purposes only 0901 if (!SecureTextBuffer::savefile(kAuthActionArgs).succeeded()) { 0902 return false; 0903 } 0904 } else { 0905 KAuth::Action kAuthSaveAction(QStringLiteral("org.kde.ktexteditor.katetextbuffer.savefile")); 0906 kAuthSaveAction.setHelperId(QStringLiteral("org.kde.ktexteditor.katetextbuffer")); 0907 kAuthSaveAction.setArguments(kAuthActionArgs); 0908 KAuth::ExecuteJob *job = kAuthSaveAction.execute(); 0909 if (!job->exec()) { 0910 return false; 0911 } 0912 } 0913 0914 return true; 0915 #else 0916 Q_UNUSED(filename); 0917 return false; 0918 #endif 0919 } 0920 0921 void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, KTextEditor::LineRange lineRange, bool needsRepaint) 0922 { 0923 // ignore calls if no document is around 0924 if (!m_document) { 0925 return; 0926 } 0927 0928 // update all views, this IS ugly and could be a signal, but I profiled and a signal is TOO slow, really 0929 // just create 20k ranges in a go and you wait seconds on a decent machine 0930 const QList<KTextEditor::View *> views = m_document->views(); 0931 for (KTextEditor::View *curView : views) { 0932 // filter wrong views 0933 if (view && view != curView) { 0934 continue; 0935 } 0936 0937 // notify view, it is really a kate view 0938 static_cast<KTextEditor::ViewPrivate *>(curView)->notifyAboutRangeChange(lineRange, needsRepaint); 0939 } 0940 } 0941 0942 void TextBuffer::markModifiedLinesAsSaved() 0943 { 0944 for (TextBlock *block : std::as_const(m_blocks)) { 0945 block->markModifiedLinesAsSaved(); 0946 } 0947 } 0948 0949 } 0950 0951 #include "moc_katetextbuffer.cpp"