File indexing completed on 2024-05-12 05:46:49
0001 /* 0002 This file is part of the KDE libraries 0003 Copyright (C) 2000 David Smith <dsmith@algonet.se> 0004 Copyright (C) 2004 Scott Wheeler <wheeler@kde.org> 0005 0006 This class was inspired by a previous KUrlCompletion by 0007 Henner Zeller <zeller@think.de> 0008 0009 This library is free software; you can redistribute it and/or 0010 modify it under the terms of the GNU Library General Public 0011 License as published by the Free Software Foundation; either 0012 version 2 of the License, or (at your option) any later version. 0013 0014 This library is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 Library General Public License for more details. 0018 0019 You should have received a copy of the GNU Library General Public License 0020 along with this library; see the file COPYING.LIB. If not, write to 0021 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0022 Boston, MA 02110-1301, USA. 0023 */ 0024 0025 #include "kurlcompletion.h" 0026 #include "../pathhelpers_p.h" 0027 0028 #include <stdlib.h> 0029 #include <assert.h> 0030 #include <limits.h> 0031 0032 #include <QMutex> 0033 #include <QRegExp> 0034 #include <QDir> 0035 #include <QDirIterator> 0036 #include <QFile> 0037 #include <QThread> 0038 #include <QUrl> 0039 #include <QDebug> 0040 #include <QMimeDatabase> 0041 #include <QProcessEnvironment> 0042 #include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF 0043 0044 #include <kurlauthorized.h> 0045 #include <kio/job.h> 0046 #include <kprotocolmanager.h> 0047 #include <kconfig.h> 0048 #include <ksharedconfig.h> 0049 #include <kcompletion.h> 0050 #include <kioglobal_p.h> 0051 #include <KUser> 0052 #include <kio_widgets_debug.h> 0053 0054 #include <time.h> 0055 #include <kconfiggroup.h> 0056 0057 #ifdef Q_OS_WIN 0058 #include <qt_windows.h> 0059 #else 0060 #include <sys/param.h> 0061 #include <pwd.h> 0062 #endif 0063 0064 static bool expandTilde(QString &); 0065 static bool expandEnv(QString &); 0066 0067 static QString unescape(const QString &text); 0068 0069 // Permission mask for files that are executable by 0070 // user, group or other 0071 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) 0072 0073 // Constants for types of completion 0074 enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; 0075 0076 class CompletionThread; 0077 0078 // Ensure that we don't end up with "//". 0079 static QUrl addPathToUrl(const QUrl &url, const QString &relPath) 0080 { 0081 QUrl u(url); 0082 u.setPath(concatPaths(url.path(), relPath)); 0083 return u; 0084 } 0085 0086 0087 static QBasicAtomicInt s_waitDuration = Q_BASIC_ATOMIC_INITIALIZER(-1); 0088 0089 static int initialWaitDuration() 0090 { 0091 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0092 if (s_waitDuration.load() == -1) { 0093 #else 0094 if (s_waitDuration.loadRelaxed() == -1) { 0095 #endif 0096 const QByteArray envVar = qgetenv("KURLCOMPLETION_WAIT"); 0097 if (envVar.isEmpty()) { 0098 s_waitDuration = 200; // default: 200 ms 0099 } else { 0100 s_waitDuration = envVar.toInt(); 0101 } 0102 } 0103 return s_waitDuration; 0104 } 0105 0106 /////////////////////////////////////////////////////// 0107 /////////////////////////////////////////////////////// 0108 // KUrlCompletionPrivate 0109 // 0110 class KUrlCompletionPrivate 0111 { 0112 public: 0113 explicit KUrlCompletionPrivate(KUrlCompletion *parent) 0114 : q(parent), 0115 url_auto_completion(true), 0116 userListThread(nullptr), 0117 dirListThread(nullptr) 0118 { 0119 } 0120 0121 ~KUrlCompletionPrivate(); 0122 0123 void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); 0124 void _k_slotIOFinished(KJob *); 0125 void slotCompletionThreadDone(QThread *thread, const QStringList &matches); 0126 0127 class MyURL; 0128 bool userCompletion(const MyURL &url, QString *match); 0129 bool envCompletion(const MyURL &url, QString *match); 0130 bool exeCompletion(const MyURL &url, QString *match); 0131 bool fileCompletion(const MyURL &url, QString *match); 0132 bool urlCompletion(const MyURL &url, QString *match); 0133 0134 bool isAutoCompletion(); 0135 0136 // List the next dir in m_dirs 0137 QString listDirectories(const QStringList &, 0138 const QString &, 0139 bool only_exe = false, 0140 bool only_dir = false, 0141 bool no_hidden = false, 0142 bool stat_files = true); 0143 0144 void listUrls(const QList<QUrl> &urls, 0145 const QString &filter = QString(), 0146 bool only_exe = false, 0147 bool no_hidden = false); 0148 0149 void addMatches(const QStringList &); 0150 QString finished(); 0151 0152 void init(); 0153 0154 void setListedUrl(ComplType compl_type, 0155 const QString &dir = QString(), 0156 const QString &filter = QString(), 0157 bool no_hidden = false); 0158 0159 bool isListedUrl(ComplType compl_type, 0160 const QString &dir = QString(), 0161 const QString &filter = QString(), 0162 bool no_hidden = false); 0163 0164 KUrlCompletion * const q; 0165 QList<QUrl> list_urls; 0166 0167 bool onlyLocalProto; 0168 0169 // urlCompletion() in Auto/Popup mode? 0170 bool url_auto_completion; 0171 0172 // Append '/' to directories in Popup mode? 0173 // Doing that stat's all files and is slower 0174 bool popup_append_slash; 0175 0176 // Keep track of currently listed files to avoid reading them again 0177 bool last_no_hidden; 0178 QString last_path_listed; 0179 QString last_file_listed; 0180 QString last_prepend; 0181 ComplType last_compl_type; 0182 0183 QUrl cwd; // "current directory" = base dir for completion 0184 0185 KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion 0186 bool replace_env; 0187 bool replace_home; 0188 bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path 0189 0190 KIO::ListJob *list_job; // kio job to list directories 0191 0192 QString prepend; // text to prepend to listed items 0193 QString compl_text; // text to pass on to KCompletion 0194 0195 // Filters for files read with kio 0196 bool list_urls_only_exe; // true = only list executables 0197 bool list_urls_no_hidden; 0198 QString list_urls_filter; // filter for listed files 0199 0200 CompletionThread *userListThread; 0201 CompletionThread *dirListThread; 0202 0203 QStringList mimeTypeFilters; 0204 }; 0205 0206 class CompletionThread : public QThread 0207 { 0208 Q_OBJECT 0209 protected: 0210 CompletionThread(KUrlCompletionPrivate *receiver) : 0211 QThread(), 0212 m_prepend(receiver->prepend), 0213 m_complete_url(receiver->complete_url), 0214 m_terminationRequested(false) 0215 {} 0216 0217 public: 0218 void requestTermination() 0219 { 0220 if (!isFinished()) { 0221 qCDebug(KIO_WIDGETS) << "stopping thread" << this; 0222 } 0223 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0224 m_terminationRequested.store(true); 0225 #else 0226 m_terminationRequested.storeRelaxed(true); 0227 #endif 0228 wait(); 0229 } 0230 0231 QStringList matches() const 0232 { 0233 QMutexLocker locker(&m_mutex); 0234 return m_matches; 0235 } 0236 0237 Q_SIGNALS: 0238 void completionThreadDone(QThread *thread, const QStringList &matches); 0239 0240 protected: 0241 void addMatch(const QString &match) 0242 { 0243 QMutexLocker locker(&m_mutex); 0244 m_matches.append(match); 0245 } 0246 bool terminationRequested() const 0247 { 0248 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0249 return m_terminationRequested.load(); 0250 #else 0251 return m_terminationRequested.loadRelaxed(); 0252 #endif 0253 } 0254 void done() 0255 { 0256 if (!terminationRequested()) { 0257 qCDebug(KIO_WIDGETS) << "done, emitting signal with" << m_matches.count() << "matches"; 0258 emit completionThreadDone(this, m_matches); 0259 } 0260 } 0261 0262 const QString m_prepend; 0263 const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path 0264 0265 private: 0266 mutable QMutex m_mutex; // protects m_matches 0267 QStringList m_matches; // written by secondary thread, read by the matches() method 0268 QAtomicInt m_terminationRequested; // used as a bool 0269 }; 0270 0271 /** 0272 * A simple thread that fetches a list of tilde-completions and returns this 0273 * to the caller via the completionThreadDone signal. 0274 */ 0275 0276 class UserListThread : public CompletionThread 0277 { 0278 Q_OBJECT 0279 public: 0280 UserListThread(KUrlCompletionPrivate *receiver) : 0281 CompletionThread(receiver) 0282 {} 0283 0284 protected: 0285 void run() override 0286 { 0287 #ifndef Q_OS_ANDROID 0288 const QChar tilde = QLatin1Char('~'); 0289 0290 // we don't need to handle prepend here, right? ~user is always at pos 0 0291 assert(m_prepend.isEmpty()); 0292 #pragma message("TODO: add KUser::allUserNames() with a std::function<bool()> shouldTerminate parameter") 0293 #ifndef Q_OS_WIN 0294 struct passwd *pw; 0295 ::setpwent(); 0296 while ((pw = ::getpwent()) && !terminationRequested()) { 0297 addMatch(tilde + QString::fromLocal8Bit(pw->pw_name)); 0298 } 0299 ::endpwent(); 0300 #else 0301 //currently terminationRequested is ignored on Windows 0302 const QStringList allUsers = KUser::allUserNames(); 0303 for (const QString& s : allUsers) { 0304 addMatch(tilde + s); 0305 } 0306 #endif 0307 addMatch(QString(tilde)); 0308 #endif 0309 done(); 0310 } 0311 }; 0312 0313 class DirectoryListThread : public CompletionThread 0314 { 0315 Q_OBJECT 0316 public: 0317 DirectoryListThread(KUrlCompletionPrivate *receiver, 0318 const QStringList &dirList, 0319 const QString &filter, 0320 const QStringList &mimeTypeFilters, 0321 bool onlyExe, 0322 bool onlyDir, 0323 bool noHidden, 0324 bool appendSlashToDir) : 0325 CompletionThread(receiver), 0326 m_dirList(dirList), 0327 m_filter(filter), 0328 m_mimeTypeFilters(mimeTypeFilters), 0329 m_onlyExe(onlyExe), 0330 m_onlyDir(onlyDir), 0331 m_noHidden(noHidden), 0332 m_appendSlashToDir(appendSlashToDir) 0333 {} 0334 0335 void run() override; 0336 0337 private: 0338 QStringList m_dirList; 0339 QString m_filter; 0340 QStringList m_mimeTypeFilters; 0341 bool m_onlyExe; 0342 bool m_onlyDir; 0343 bool m_noHidden; 0344 bool m_appendSlashToDir; 0345 }; 0346 0347 void DirectoryListThread::run() 0348 { 0349 //qDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size(); 0350 0351 QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot; 0352 if (m_onlyExe) { 0353 iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable); 0354 } else if (m_onlyDir) { 0355 iterator_filter |= QDir::Dirs; 0356 } else { 0357 iterator_filter |= (QDir::Dirs | QDir::Files); 0358 } 0359 0360 QMimeDatabase mimeTypes; 0361 0362 const QStringList::const_iterator end = m_dirList.constEnd(); 0363 for (QStringList::const_iterator it = m_dirList.constBegin(); 0364 it != end && !terminationRequested(); 0365 ++it) { 0366 //qDebug() << "Scanning directory" << *it; 0367 0368 QDirIterator current_dir_iterator(*it, iterator_filter); 0369 0370 while (current_dir_iterator.hasNext() && !terminationRequested()) { 0371 current_dir_iterator.next(); 0372 0373 QFileInfo file_info = current_dir_iterator.fileInfo(); 0374 const QString file_name = file_info.fileName(); 0375 0376 //qDebug() << "Found" << file_name; 0377 0378 if (!m_filter.isEmpty() && !file_name.startsWith(m_filter)) { 0379 continue; 0380 } 0381 0382 if (!m_mimeTypeFilters.isEmpty() && !file_info.isDir()) { 0383 auto mimeType = mimeTypes.mimeTypeForFile(file_info); 0384 if (!m_mimeTypeFilters.contains(mimeType.name())) { 0385 continue; 0386 } 0387 } 0388 0389 QString toAppend = file_name; 0390 // Add '/' to directories 0391 if (m_appendSlashToDir && file_info.isDir()) { 0392 toAppend.append(QLatin1Char('/')); 0393 } 0394 0395 if (m_complete_url) { 0396 QUrl info(m_prepend); 0397 info = addPathToUrl(info, toAppend); 0398 addMatch(info.toDisplayString()); 0399 } else { 0400 addMatch(m_prepend + toAppend); 0401 } 0402 } 0403 } 0404 0405 done(); 0406 } 0407 0408 KUrlCompletionPrivate::~KUrlCompletionPrivate() 0409 { 0410 } 0411 0412 /////////////////////////////////////////////////////// 0413 /////////////////////////////////////////////////////// 0414 // MyURL - wrapper for QUrl with some different functionality 0415 // 0416 0417 class KUrlCompletionPrivate::MyURL 0418 { 0419 public: 0420 MyURL(const QString &url, const QUrl &cwd); 0421 MyURL(const MyURL &url); 0422 ~MyURL(); 0423 0424 QUrl kurl() const 0425 { 0426 return m_kurl; 0427 } 0428 0429 bool isLocalFile() const 0430 { 0431 return m_kurl.isLocalFile(); 0432 } 0433 QString scheme() const 0434 { 0435 return m_kurl.scheme(); 0436 } 0437 // The directory with a trailing '/' 0438 QString dir() const 0439 { 0440 return m_kurl.adjusted(QUrl::RemoveFilename).path(); 0441 } 0442 QString file() const 0443 { 0444 return m_kurl.fileName(); 0445 } 0446 0447 // The initial, unparsed, url, as a string. 0448 QString url() const 0449 { 0450 return m_url; 0451 } 0452 0453 // Is the initial string a URL, or just a path (whether absolute or relative) 0454 bool isURL() const 0455 { 0456 return m_isURL; 0457 } 0458 0459 void filter(bool replace_user_dir, bool replace_env); 0460 0461 private: 0462 void init(const QString &url, const QUrl &cwd); 0463 0464 QUrl m_kurl; 0465 QString m_url; 0466 bool m_isURL; 0467 }; 0468 0469 KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QUrl &cwd) 0470 { 0471 init(_url, cwd); 0472 } 0473 0474 KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url) 0475 : m_kurl(_url.m_kurl) 0476 { 0477 m_url = _url.m_url; 0478 m_isURL = _url.m_isURL; 0479 } 0480 0481 void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QUrl &cwd) 0482 { 0483 // Save the original text 0484 m_url = _url; 0485 0486 // Non-const copy 0487 QString url_copy = _url; 0488 0489 // Special shortcuts for "man:" and "info:" 0490 if (url_copy.startsWith(QLatin1Char('#'))) { 0491 if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) { 0492 url_copy.replace(0, 2, QStringLiteral("info:")); 0493 } else { 0494 url_copy.replace(0, 1, QStringLiteral("man:")); 0495 } 0496 } 0497 0498 // Look for a protocol in 'url' 0499 QRegExp protocol_regex = QRegExp(QStringLiteral("^(?![A-Za-z]:)[^/\\s\\\\]*:")); 0500 0501 // Assume "file:" or whatever is given by 'cwd' if there is 0502 // no protocol. (QUrl does this only for absolute paths) 0503 if (protocol_regex.indexIn(url_copy) == 0) { 0504 m_kurl = QUrl(url_copy); 0505 m_isURL = true; 0506 } else { // relative path or ~ or $something 0507 m_isURL = false; 0508 if (!QDir::isRelativePath(url_copy) || 0509 url_copy.startsWith(QLatin1Char('~')) || 0510 url_copy.startsWith(QLatin1Char('$'))) { 0511 m_kurl = QUrl::fromLocalFile(url_copy); 0512 } else { 0513 // Relative path 0514 if (cwd.isEmpty()) { 0515 m_kurl = QUrl(url_copy); 0516 } else { 0517 m_kurl = cwd; 0518 m_kurl.setPath(concatPaths(m_kurl.path(), url_copy)); 0519 } 0520 } 0521 } 0522 } 0523 0524 KUrlCompletionPrivate::MyURL::~MyURL() 0525 { 0526 } 0527 0528 void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env) 0529 { 0530 QString d = dir() + file(); 0531 if (replace_user_dir) { 0532 expandTilde(d); 0533 } 0534 if (replace_env) { 0535 expandEnv(d); 0536 } 0537 m_kurl.setPath(d); 0538 } 0539 0540 /////////////////////////////////////////////////////// 0541 /////////////////////////////////////////////////////// 0542 // KUrlCompletion 0543 // 0544 0545 KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this)) 0546 { 0547 d->init(); 0548 } 0549 0550 KUrlCompletion::KUrlCompletion(Mode _mode) 0551 : KCompletion(), 0552 d(new KUrlCompletionPrivate(this)) 0553 { 0554 d->init(); 0555 setMode(_mode); 0556 } 0557 0558 KUrlCompletion::~KUrlCompletion() 0559 { 0560 stop(); 0561 delete d; 0562 } 0563 0564 void KUrlCompletionPrivate::init() 0565 { 0566 cwd = QUrl::fromLocalFile(QDir::homePath()); 0567 0568 replace_home = true; 0569 replace_env = true; 0570 last_no_hidden = false; 0571 last_compl_type = CTNone; 0572 list_job = nullptr; 0573 mode = KUrlCompletion::FileCompletion; 0574 0575 // Read settings 0576 KConfigGroup cg(KSharedConfig::openConfig(), "URLCompletion"); 0577 0578 url_auto_completion = cg.readEntry("alwaysAutoComplete", true); 0579 popup_append_slash = cg.readEntry("popupAppendSlash", true); 0580 onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false); 0581 0582 q->setIgnoreCase(true); 0583 } 0584 0585 void KUrlCompletion::setDir(const QUrl &dir) 0586 { 0587 d->cwd = dir; 0588 } 0589 0590 QUrl KUrlCompletion::dir() const 0591 { 0592 return d->cwd; 0593 } 0594 0595 KUrlCompletion::Mode KUrlCompletion::mode() const 0596 { 0597 return d->mode; 0598 } 0599 0600 void KUrlCompletion::setMode(Mode _mode) 0601 { 0602 d->mode = _mode; 0603 } 0604 0605 bool KUrlCompletion::replaceEnv() const 0606 { 0607 return d->replace_env; 0608 } 0609 0610 void KUrlCompletion::setReplaceEnv(bool replace) 0611 { 0612 d->replace_env = replace; 0613 } 0614 0615 bool KUrlCompletion::replaceHome() const 0616 { 0617 return d->replace_home; 0618 } 0619 0620 void KUrlCompletion::setReplaceHome(bool replace) 0621 { 0622 d->replace_home = replace; 0623 } 0624 0625 /* 0626 * makeCompletion() 0627 * 0628 * Entry point for file name completion 0629 */ 0630 QString KUrlCompletion::makeCompletion(const QString &text) 0631 { 0632 qCDebug(KIO_WIDGETS) << text << "d->cwd=" << d->cwd; 0633 0634 KUrlCompletionPrivate::MyURL url(text, d->cwd); 0635 0636 d->compl_text = text; 0637 0638 // Set d->prepend to the original URL, with the filename [and ref/query] stripped. 0639 // This is what gets prepended to the directory-listing matches. 0640 if (url.isURL()) { 0641 QUrl directoryUrl(url.kurl()); 0642 directoryUrl.setQuery(QString()); 0643 directoryUrl.setFragment(QString()); 0644 directoryUrl.setPath(url.dir()); 0645 d->prepend = directoryUrl.toString(); 0646 } else { 0647 d->prepend = text.left(text.length() - url.file().length()); 0648 } 0649 0650 d->complete_url = url.isURL(); 0651 0652 QString aMatch; 0653 0654 // Environment variables 0655 // 0656 if (d->replace_env && d->envCompletion(url, &aMatch)) { 0657 return aMatch; 0658 } 0659 0660 // User directories 0661 // 0662 if (d->replace_home && d->userCompletion(url, &aMatch)) { 0663 return aMatch; 0664 } 0665 0666 // Replace user directories and variables 0667 url.filter(d->replace_home, d->replace_env); 0668 0669 //qDebug() << "Filtered: proto=" << url.scheme() 0670 // << ", dir=" << url.dir() 0671 // << ", file=" << url.file() 0672 // << ", kurl url=" << *url.kurl(); 0673 0674 if (d->mode == ExeCompletion) { 0675 // Executables 0676 // 0677 if (d->exeCompletion(url, &aMatch)) { 0678 return aMatch; 0679 } 0680 0681 // KRun can run "man:" and "info:" etc. so why not treat them 0682 // as executables... 0683 0684 if (d->urlCompletion(url, &aMatch)) { 0685 return aMatch; 0686 } 0687 } else { 0688 // Local files, directories 0689 // 0690 if (d->fileCompletion(url, &aMatch)) { 0691 return aMatch; 0692 } 0693 0694 // All other... 0695 // 0696 if (d->urlCompletion(url, &aMatch)) { 0697 return aMatch; 0698 } 0699 } 0700 0701 d->setListedUrl(CTNone); 0702 stop(); 0703 0704 return QString(); 0705 } 0706 0707 /* 0708 * finished 0709 * 0710 * Go on and call KCompletion. 0711 * Called when all matches have been added 0712 */ 0713 QString KUrlCompletionPrivate::finished() 0714 { 0715 if (last_compl_type == CTInfo) { 0716 return q->KCompletion::makeCompletion(compl_text.toLower()); 0717 } else { 0718 return q->KCompletion::makeCompletion(compl_text); 0719 } 0720 } 0721 0722 /* 0723 * isRunning 0724 * 0725 * Return true if either a KIO job or a thread is running 0726 */ 0727 bool KUrlCompletion::isRunning() const 0728 { 0729 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()) || (d->userListThread && !d->userListThread->isFinished()); 0730 } 0731 0732 /* 0733 * stop 0734 * 0735 * Stop and delete a running KIO job or the DirLister 0736 */ 0737 void KUrlCompletion::stop() 0738 { 0739 if (d->list_job) { 0740 d->list_job->kill(); 0741 d->list_job = nullptr; 0742 } 0743 0744 if (d->dirListThread) { 0745 d->dirListThread->requestTermination(); 0746 delete d->dirListThread; 0747 d->dirListThread = nullptr; 0748 } 0749 0750 if (d->userListThread) { 0751 d->userListThread->requestTermination(); 0752 delete d->userListThread; 0753 d->userListThread = nullptr; 0754 } 0755 } 0756 0757 /* 0758 * Keep track of the last listed directory 0759 */ 0760 void KUrlCompletionPrivate::setListedUrl(ComplType complType, 0761 const QString &directory, 0762 const QString &filter, 0763 bool no_hidden) 0764 { 0765 last_compl_type = complType; 0766 last_path_listed = directory; 0767 last_file_listed = filter; 0768 last_no_hidden = no_hidden; 0769 last_prepend = prepend; 0770 } 0771 0772 bool KUrlCompletionPrivate::isListedUrl(ComplType complType, 0773 const QString &directory, 0774 const QString &filter, 0775 bool no_hidden) 0776 { 0777 return last_compl_type == complType 0778 && (last_path_listed == directory 0779 || (directory.isEmpty() && last_path_listed.isEmpty())) 0780 && (filter.startsWith(last_file_listed) 0781 || (filter.isEmpty() && last_file_listed.isEmpty())) 0782 && last_no_hidden == no_hidden 0783 && last_prepend == prepend; // e.g. relative path vs absolute 0784 } 0785 0786 /* 0787 * isAutoCompletion 0788 * 0789 * Returns true if completion mode is Auto or Popup 0790 */ 0791 bool KUrlCompletionPrivate::isAutoCompletion() 0792 { 0793 return q->completionMode() == KCompletion::CompletionAuto 0794 || q->completionMode() == KCompletion::CompletionPopup 0795 || q->completionMode() == KCompletion::CompletionMan 0796 || q->completionMode() == KCompletion::CompletionPopupAuto; 0797 } 0798 ////////////////////////////////////////////////// 0799 ////////////////////////////////////////////////// 0800 // User directories 0801 // 0802 0803 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) 0804 { 0805 if (url.scheme() != QLatin1String("file") 0806 || !url.dir().isEmpty() 0807 || !url.file().startsWith(QLatin1Char('~')) 0808 || !prepend.isEmpty()) { 0809 return false; 0810 } 0811 0812 if (!isListedUrl(CTUser)) { 0813 q->stop(); 0814 q->clear(); 0815 setListedUrl(CTUser); 0816 0817 Q_ASSERT(!userListThread); // caller called stop() 0818 userListThread = new UserListThread(this); 0819 QObject::connect(userListThread, &CompletionThread::completionThreadDone, 0820 q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); 0821 userListThread->start(); 0822 0823 // If the thread finishes quickly make sure that the results 0824 // are added to the first matching case. 0825 0826 userListThread->wait(initialWaitDuration()); 0827 const QStringList l = userListThread->matches(); 0828 addMatches(l); 0829 } 0830 *pMatch = finished(); 0831 return true; 0832 } 0833 0834 ///////////////////////////////////////////////////// 0835 ///////////////////////////////////////////////////// 0836 // Environment variables 0837 // 0838 0839 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) 0840 { 0841 if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) { 0842 return false; 0843 } 0844 0845 if (!isListedUrl(CTEnv)) { 0846 q->stop(); 0847 q->clear(); 0848 0849 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0850 const QStringList keys = env.keys(); 0851 0852 QStringList l; 0853 l.reserve(keys.size()); 0854 for (const QString &key : keys) { 0855 l.append(prepend + QLatin1Char('$') + key); 0856 } 0857 0858 addMatches(l); 0859 } 0860 0861 setListedUrl(CTEnv); 0862 0863 *pMatch = finished(); 0864 return true; 0865 } 0866 0867 ////////////////////////////////////////////////// 0868 ////////////////////////////////////////////////// 0869 // Executables 0870 // 0871 0872 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) 0873 { 0874 if (!url.isLocalFile()) { 0875 return false; 0876 } 0877 0878 QString directory = unescape(url.dir()); // remove escapes 0879 0880 // Find directories to search for completions, either 0881 // 0882 // 1. complete path given in url 0883 // 2. current directory (d->cwd) 0884 // 3. $PATH 0885 // 4. no directory at all 0886 0887 QStringList dirList; 0888 0889 if (!url.file().isEmpty()) { 0890 // $PATH 0891 dirList = QString::fromLocal8Bit(qgetenv("PATH")).split( 0892 QDir::listSeparator(), QString::SkipEmptyParts); 0893 0894 QStringList::Iterator it = dirList.begin(); 0895 0896 for (; it != dirList.end(); ++it) { 0897 it->append(QLatin1Char('/')); 0898 } 0899 } else if (!QDir::isRelativePath(directory)) { 0900 // complete path in url 0901 dirList.append(directory); 0902 } else if (!directory.isEmpty() && !cwd.isEmpty()) { 0903 // current directory 0904 dirList.append(cwd.toLocalFile() + QLatin1Char('/') + directory); 0905 } 0906 0907 // No hidden files unless the user types "." 0908 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.'); 0909 0910 // List files if needed 0911 // 0912 if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) { 0913 q->stop(); 0914 q->clear(); 0915 0916 setListedUrl(CTExe, directory, url.file(), no_hidden_files); 0917 0918 *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files); 0919 } else { 0920 *pMatch = finished(); 0921 } 0922 0923 return true; 0924 } 0925 0926 ////////////////////////////////////////////////// 0927 ////////////////////////////////////////////////// 0928 // Local files 0929 // 0930 0931 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) 0932 { 0933 if (!url.isLocalFile()) { 0934 return false; 0935 } 0936 0937 QString directory = unescape(url.dir()); 0938 0939 if (url.url() == QLatin1String("..")) { 0940 *pMatch = QStringLiteral(".."); 0941 return true; 0942 } 0943 0944 //qDebug() << "fileCompletion" << url << "dir=" << dir; 0945 0946 // Find directories to search for completions, either 0947 // 0948 // 1. complete path given in url 0949 // 2. current directory (d->cwd) 0950 // 3. no directory at all 0951 0952 QStringList dirList; 0953 0954 if (!QDir::isRelativePath(directory)) { 0955 // complete path in url 0956 dirList.append(directory); 0957 } else if (!cwd.isEmpty()) { 0958 // current directory 0959 QString dirToAdd = cwd.toLocalFile(); 0960 if (!directory.isEmpty()) { 0961 if (!dirToAdd.endsWith(QLatin1Char('/'))) { 0962 dirToAdd.append(QLatin1Char('/')); 0963 } 0964 dirToAdd.append(directory); 0965 } 0966 dirList.append(dirToAdd); 0967 } 0968 0969 // No hidden files unless the user types "." 0970 bool no_hidden_files = !url.file().startsWith(QLatin1Char('.')); 0971 0972 // List files if needed 0973 // 0974 if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) { 0975 q->stop(); 0976 q->clear(); 0977 0978 setListedUrl(CTFile, directory, QString(), no_hidden_files); 0979 0980 // Append '/' to directories in Popup mode? 0981 bool append_slash = (popup_append_slash 0982 && (q->completionMode() == KCompletion::CompletionPopup || 0983 q->completionMode() == KCompletion::CompletionPopupAuto)); 0984 0985 bool only_dir = (mode == KUrlCompletion::DirCompletion); 0986 0987 *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, 0988 append_slash); 0989 } else { 0990 *pMatch = finished(); 0991 } 0992 0993 return true; 0994 } 0995 0996 ////////////////////////////////////////////////// 0997 ////////////////////////////////////////////////// 0998 // URLs not handled elsewhere... 0999 // 1000 1001 static bool isLocalProtocol(const QString &protocol) 1002 { 1003 return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")); 1004 } 1005 1006 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) 1007 { 1008 //qDebug() << *url.kurl(); 1009 if (onlyLocalProto && isLocalProtocol(url.scheme())) { 1010 return false; 1011 } 1012 1013 // Use d->cwd as base url in case url is not absolute 1014 QUrl url_dir = url.kurl(); 1015 if (url_dir.isRelative() && !cwd.isEmpty()) { 1016 // Create an URL with the directory to be listed 1017 url_dir = cwd.resolved(url_dir); 1018 } 1019 1020 // url is malformed 1021 if (!url_dir.isValid() || url.scheme().isEmpty()) { 1022 return false; 1023 } 1024 1025 // non local urls 1026 if (!isLocalProtocol(url.scheme())) { 1027 // url does not specify host 1028 if (url_dir.host().isEmpty()) { 1029 return false; 1030 } 1031 1032 // url does not specify a valid directory 1033 if (url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty()) { 1034 return false; 1035 } 1036 1037 // automatic completion is disabled 1038 if (isAutoCompletion() && !url_auto_completion) { 1039 return false; 1040 } 1041 } 1042 1043 // url handler doesn't support listing 1044 if (!KProtocolManager::supportsListing(url_dir)) { 1045 return false; 1046 } 1047 1048 // Remove escapes 1049 const QString directory = unescape(url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); 1050 url_dir.setPath(directory); 1051 1052 // List files if needed 1053 // 1054 if (!isListedUrl(CTUrl, directory, url.file())) { 1055 q->stop(); 1056 q->clear(); 1057 1058 setListedUrl(CTUrl, directory, QString()); 1059 1060 QList<QUrl> url_list; 1061 url_list.append(url_dir); 1062 1063 listUrls(url_list, QString(), false); 1064 1065 pMatch->clear(); 1066 } else if (!q->isRunning()) { 1067 *pMatch = finished(); 1068 } else { 1069 pMatch->clear(); 1070 } 1071 1072 return true; 1073 } 1074 1075 ////////////////////////////////////////////////// 1076 ////////////////////////////////////////////////// 1077 // Directory and URL listing 1078 // 1079 1080 /* 1081 * addMatches 1082 * 1083 * Called to add matches to KCompletion 1084 */ 1085 void KUrlCompletionPrivate::addMatches(const QStringList &matchList) 1086 { 1087 q->insertItems(matchList); 1088 } 1089 1090 /* 1091 * listDirectories 1092 * 1093 * List files starting with 'filter' in the given directories, 1094 * either using DirLister or listURLs() 1095 * 1096 * In either case, addMatches() is called with the listed 1097 * files, and eventually finished() when the listing is done 1098 * 1099 * Returns the match if available, or QString() if 1100 * DirLister timed out or using kio 1101 */ 1102 QString KUrlCompletionPrivate::listDirectories( 1103 const QStringList &dirList, 1104 const QString &filter, 1105 bool only_exe, 1106 bool only_dir, 1107 bool no_hidden, 1108 bool append_slash_to_dir) 1109 { 1110 assert(!q->isRunning()); 1111 1112 if (qEnvironmentVariableIsEmpty("KURLCOMPLETION_LOCAL_KIO")) { 1113 1114 qCDebug(KIO_WIDGETS) << "Listing directories:" << dirList << "with filter=" << filter << "using thread"; 1115 1116 // Don't use KIO 1117 1118 QStringList dirs; 1119 1120 QStringList::ConstIterator end = dirList.constEnd(); 1121 for (QStringList::ConstIterator it = dirList.constBegin(); 1122 it != end; 1123 ++it) { 1124 QUrl url = QUrl::fromLocalFile(*it); 1125 if (KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), url)) { 1126 dirs.append(*it); 1127 } 1128 } 1129 1130 Q_ASSERT(!dirListThread); // caller called stop() 1131 dirListThread = new DirectoryListThread(this, dirs, filter, mimeTypeFilters, 1132 only_exe, only_dir, no_hidden, 1133 append_slash_to_dir); 1134 QObject::connect(dirListThread, &CompletionThread::completionThreadDone, 1135 q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); 1136 dirListThread->start(); 1137 dirListThread->wait(initialWaitDuration()); 1138 qCDebug(KIO_WIDGETS) << "Adding initial matches:" << dirListThread->matches(); 1139 addMatches(dirListThread->matches()); 1140 1141 return finished(); 1142 } 1143 1144 // Use KIO 1145 //qDebug() << "Listing (listDirectories):" << dirList << "with KIO"; 1146 1147 QList<QUrl> url_list; 1148 1149 QStringList::ConstIterator it = dirList.constBegin(); 1150 QStringList::ConstIterator end = dirList.constEnd(); 1151 1152 url_list.reserve(dirList.size()); 1153 for (; it != end; ++it) { 1154 url_list.append(QUrl(*it)); 1155 } 1156 1157 listUrls(url_list, filter, only_exe, no_hidden); 1158 // Will call addMatches() and finished() 1159 1160 return QString(); 1161 } 1162 1163 /* 1164 * listURLs 1165 * 1166 * Use KIO to list the given urls 1167 * 1168 * addMatches() is called with the listed files 1169 * finished() is called when the listing is done 1170 */ 1171 void KUrlCompletionPrivate::listUrls( 1172 const QList<QUrl> &urls, 1173 const QString &filter, 1174 bool only_exe, 1175 bool no_hidden) 1176 { 1177 assert(list_urls.isEmpty()); 1178 assert(list_job == nullptr); 1179 1180 list_urls = urls; 1181 list_urls_filter = filter; 1182 list_urls_only_exe = only_exe; 1183 list_urls_no_hidden = no_hidden; 1184 1185 //qDebug() << "Listing URLs:" << *urls[0] << ",..."; 1186 1187 // Start it off by calling _k_slotIOFinished 1188 // 1189 // This will start a new list job as long as there 1190 // are urls in d->list_urls 1191 // 1192 _k_slotIOFinished(nullptr); 1193 } 1194 1195 /* 1196 * _k_slotEntries 1197 * 1198 * Receive files listed by KIO and call addMatches() 1199 */ 1200 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &entries) 1201 { 1202 QStringList matchList; 1203 1204 KIO::UDSEntryList::ConstIterator it = entries.constBegin(); 1205 const KIO::UDSEntryList::ConstIterator end = entries.constEnd(); 1206 1207 QString filter = list_urls_filter; 1208 1209 int filter_len = filter.length(); 1210 1211 // Iterate over all files 1212 // 1213 for (; it != end; ++it) { 1214 const KIO::UDSEntry &entry = *it; 1215 const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL); 1216 1217 QString entry_name; 1218 if (!url.isEmpty()) { 1219 //qDebug() << "url:" << url; 1220 entry_name = QUrl(url).fileName(); 1221 } else { 1222 entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME); 1223 } 1224 1225 //qDebug() << "name:" << name; 1226 1227 if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) && 1228 (list_urls_no_hidden || 1229 entry_name.length() == 1 || 1230 (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) { 1231 continue; 1232 } 1233 1234 const bool isDir = entry.isDir(); 1235 1236 if (mode == KUrlCompletion::DirCompletion && !isDir) { 1237 continue; 1238 } 1239 1240 if (filter_len != 0 && entry_name.leftRef(filter_len) != filter) { 1241 continue; 1242 } 1243 1244 if (!mimeTypeFilters.isEmpty() && !isDir && 1245 !mimeTypeFilters.contains(entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE))) { 1246 continue; 1247 } 1248 1249 QString toAppend = entry_name; 1250 1251 if (isDir) { 1252 toAppend.append(QLatin1Char('/')); 1253 } 1254 1255 if (!list_urls_only_exe || 1256 (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable 1257 ) { 1258 if (complete_url) { 1259 QUrl url(prepend); 1260 url = addPathToUrl(url, toAppend); 1261 matchList.append(url.toDisplayString()); 1262 } else { 1263 matchList.append(prepend + toAppend); 1264 } 1265 } 1266 1267 } 1268 1269 addMatches(matchList); 1270 } 1271 1272 /* 1273 * _k_slotIOFinished 1274 * 1275 * Called when a KIO job is finished. 1276 * 1277 * Start a new list job if there are still urls in 1278 * list_urls, otherwise call finished() 1279 */ 1280 void KUrlCompletionPrivate::_k_slotIOFinished(KJob *job) 1281 { 1282 assert(job == list_job); Q_UNUSED(job) 1283 1284 if (list_urls.isEmpty()) { 1285 1286 list_job = nullptr; 1287 1288 finished(); // will call KCompletion::makeCompletion() 1289 1290 } else { 1291 1292 QUrl kurl(list_urls.takeFirst()); 1293 1294 // list_urls.removeAll( kurl ); 1295 1296 //qDebug() << "Start KIO::listDir" << kurl; 1297 1298 list_job = KIO::listDir(kurl, KIO::HideProgressInfo); 1299 list_job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); 1300 1301 assert(list_job); 1302 1303 q->connect(list_job, 1304 SIGNAL(result(KJob*)), 1305 SLOT(_k_slotIOFinished(KJob*))); 1306 1307 q->connect(list_job, 1308 SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), 1309 SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); 1310 } 1311 } 1312 1313 /////////////////////////////////////////////////// 1314 /////////////////////////////////////////////////// 1315 1316 /* 1317 * postProcessMatch, postProcessMatches 1318 * 1319 * Called by KCompletion before emitting match() and matches() 1320 * 1321 * Append '/' to directories for file completion. This is 1322 * done here to avoid stat()'ing a lot of files 1323 */ 1324 void KUrlCompletion::postProcessMatch(QString *pMatch) const 1325 { 1326 //qDebug() << *pMatch; 1327 1328 if (!pMatch->isEmpty() && pMatch->startsWith(QLatin1String("file:"))) { 1329 1330 // Add '/' to directories in file completion mode 1331 // unless it has already been done 1332 if (d->last_compl_type == CTFile 1333 && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) { 1334 QString copy = QUrl(*pMatch).toLocalFile(); 1335 expandTilde(copy); 1336 expandEnv(copy); 1337 if (QDir::isRelativePath(copy)) { 1338 copy.prepend(d->cwd.toLocalFile() + QLatin1Char('/')); 1339 } 1340 1341 //qDebug() << "stat'ing" << copy; 1342 1343 QByteArray file = QFile::encodeName(copy); 1344 1345 QT_STATBUF sbuff; 1346 if (QT_STAT(file.constData(), &sbuff) == 0) { 1347 if ((sbuff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { 1348 pMatch->append(QLatin1Char('/')); 1349 } 1350 } else { 1351 //qDebug() << "Could not stat file" << copy; 1352 } 1353 } 1354 } 1355 } 1356 1357 void KUrlCompletion::postProcessMatches(QStringList * /*matches*/) const 1358 { 1359 // Maybe '/' should be added to directories here as in 1360 // postProcessMatch() but it would slow things down 1361 // when there are a lot of matches... 1362 } 1363 1364 void KUrlCompletion::postProcessMatches(KCompletionMatches * /*matches*/) const 1365 { 1366 // Maybe '/' should be added to directories here as in 1367 // postProcessMatch() but it would slow things down 1368 // when there are a lot of matches... 1369 } 1370 1371 // no longer used, KF6 TODO: remove this method 1372 void KUrlCompletion::customEvent(QEvent *e) 1373 { 1374 KCompletion::customEvent(e); 1375 } 1376 1377 void KUrlCompletionPrivate::slotCompletionThreadDone(QThread *thread, const QStringList &matches) 1378 { 1379 if (thread != userListThread && thread != dirListThread) { 1380 qCDebug(KIO_WIDGETS) << "got" << matches.count() << "outdated matches"; 1381 return; 1382 } 1383 1384 qCDebug(KIO_WIDGETS) << "got" << matches.count() << "matches at end of thread"; 1385 q->setItems(matches); 1386 1387 if (userListThread == thread) { 1388 thread->wait(); 1389 delete thread; 1390 userListThread = nullptr; 1391 } 1392 1393 if (dirListThread == thread) { 1394 thread->wait(); 1395 delete thread; 1396 dirListThread = nullptr; 1397 } 1398 finished(); // will call KCompletion::makeCompletion() 1399 } 1400 1401 // static 1402 QString KUrlCompletion::replacedPath(const QString &text, bool replaceHome, bool replaceEnv) 1403 { 1404 if (text.isEmpty()) { 1405 return text; 1406 } 1407 1408 KUrlCompletionPrivate::MyURL url(text, QUrl()); // no need to replace something of our current cwd 1409 if (!url.kurl().isLocalFile()) { 1410 return text; 1411 } 1412 1413 url.filter(replaceHome, replaceEnv); 1414 return url.dir() + url.file(); 1415 } 1416 1417 QString KUrlCompletion::replacedPath(const QString &text) const 1418 { 1419 return replacedPath(text, d->replace_home, d->replace_env); 1420 } 1421 1422 void KUrlCompletion::setMimeTypeFilters(const QStringList &mimeTypeFilters) 1423 { 1424 d->mimeTypeFilters = mimeTypeFilters; 1425 } 1426 1427 QStringList KUrlCompletion::mimeTypeFilters() const 1428 { 1429 return d->mimeTypeFilters; 1430 } 1431 1432 ///////////////////////////////////////////////////////// 1433 ///////////////////////////////////////////////////////// 1434 // Static functions 1435 1436 /* 1437 * expandEnv 1438 * 1439 * Expand environment variables in text. Escaped '$' are ignored. 1440 * Return true if expansion was made. 1441 */ 1442 static bool expandEnv(QString &text) 1443 { 1444 // Find all environment variables beginning with '$' 1445 // 1446 int pos = 0; 1447 1448 bool expanded = false; 1449 1450 while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) { 1451 1452 // Skip escaped '$' 1453 // 1454 if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) { 1455 pos++; 1456 } 1457 // Variable found => expand 1458 // 1459 else { 1460 // Find the end of the variable = next '/' or ' ' 1461 // 1462 int pos2 = text.indexOf(QLatin1Char(' '), pos + 1); 1463 int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1); 1464 1465 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { 1466 pos2 = pos_tmp; 1467 } 1468 1469 if (pos2 == -1) { 1470 pos2 = text.length(); 1471 } 1472 1473 // Replace if the variable is terminated by '/' or ' ' 1474 // and defined 1475 // 1476 if (pos2 >= 0) { 1477 int len = pos2 - pos; 1478 const QStringRef key = text.midRef(pos + 1, len - 1); 1479 QString value = 1480 QString::fromLocal8Bit(qgetenv(key.toLocal8Bit().constData())); 1481 1482 if (!value.isEmpty()) { 1483 expanded = true; 1484 text.replace(pos, len, value); 1485 pos = pos + value.length(); 1486 } else { 1487 pos = pos2; 1488 } 1489 } 1490 } 1491 } 1492 1493 return expanded; 1494 } 1495 1496 /* 1497 * expandTilde 1498 * 1499 * Replace "~user" with the users home directory 1500 * Return true if expansion was made. 1501 */ 1502 static bool expandTilde(QString &text) 1503 { 1504 if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) { 1505 return false; 1506 } 1507 1508 bool expanded = false; 1509 1510 // Find the end of the user name = next '/' or ' ' 1511 // 1512 int pos2 = text.indexOf(QLatin1Char(' '), 1); 1513 int pos_tmp = text.indexOf(QLatin1Char('/'), 1); 1514 1515 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { 1516 pos2 = pos_tmp; 1517 } 1518 1519 if (pos2 == -1) { 1520 pos2 = text.length(); 1521 } 1522 1523 // Replace ~user if the user name is terminated by '/' or ' ' 1524 // 1525 if (pos2 >= 0) { 1526 1527 QString userName = text.mid(1, pos2 - 1); 1528 QString dir; 1529 1530 // A single ~ is replaced with $HOME 1531 // 1532 if (userName.isEmpty()) { 1533 dir = QDir::homePath(); 1534 } 1535 // ~user is replaced with the dir from passwd 1536 // 1537 else { 1538 KUser user(userName); 1539 dir = user.homeDir(); 1540 } 1541 1542 if (!dir.isEmpty()) { 1543 expanded = true; 1544 text.replace(0, pos2, dir); 1545 } 1546 } 1547 1548 return expanded; 1549 } 1550 1551 /* 1552 * unescape 1553 * 1554 * Remove escapes and return the result in a new string 1555 * 1556 */ 1557 static QString unescape(const QString &text) 1558 { 1559 QString result; 1560 result.reserve(text.size()); 1561 1562 for (const QChar ch : text) { 1563 if (ch != QLatin1Char('\\')) { 1564 result.append(ch); 1565 } 1566 } 1567 1568 return result; 1569 } 1570 1571 #include "moc_kurlcompletion.cpp" 1572 #include "kurlcompletion.moc"