File indexing completed on 2024-04-21 04:57:28
0001 /* 0002 * SPDX-FileCopyrightText: 2010 Peter Penz <peter.penz19@gmail.com> 0003 * SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kio_filenamesearch.h" 0009 #include "kio_filenamesearch_p.h" 0010 0011 #include "kio_filenamesearch_debug.h" 0012 0013 #include <KIO/FileCopyJob> 0014 #include <KIO/ListJob> 0015 #include <KLocalizedString> 0016 0017 #include <QCoreApplication> 0018 #include <QDBusInterface> 0019 #include <QMimeDatabase> 0020 #include <QRegularExpression> 0021 #include <QTemporaryFile> 0022 #include <QUrl> 0023 #include <QUrlQuery> 0024 0025 // Pseudo plugin class to embed meta data 0026 class KIOPluginForMetaData : public QObject 0027 { 0028 Q_OBJECT 0029 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.filenamesearch" FILE "filenamesearch.json") 0030 }; 0031 0032 static bool contentContainsPattern(const QUrl &url, const QRegularExpression ®ex) 0033 { 0034 auto fileContainsPattern = [&](const QString &path) { 0035 QFile file(path); 0036 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 0037 return false; 0038 } 0039 0040 QTextStream in(&file); 0041 while (!in.atEnd()) { 0042 const QString line = in.readLine(); 0043 if (regex.match(line).hasMatch()) { 0044 return true; 0045 } 0046 } 0047 0048 return false; 0049 }; 0050 0051 if (url.isLocalFile()) { 0052 return fileContainsPattern(url.toLocalFile()); 0053 } else { 0054 QTemporaryFile tempFile; 0055 if (tempFile.open()) { 0056 const QString tempName = tempFile.fileName(); 0057 KIO::Job *getJob = KIO::file_copy(url, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite | KIO::HideProgressInfo); 0058 if (getJob->exec()) { 0059 // The non-local file was downloaded successfully. 0060 return fileContainsPattern(tempName); 0061 } 0062 } 0063 } 0064 0065 return false; 0066 } 0067 0068 static bool match(const KIO::UDSEntry &entry, const QRegularExpression ®ex, bool searchContents) 0069 { 0070 if (!searchContents) { 0071 return regex.match(entry.stringValue(KIO::UDSEntry::UDS_NAME)).hasMatch(); 0072 } else { 0073 const QUrl entryUrl(entry.stringValue(KIO::UDSEntry::UDS_URL)); 0074 QMimeDatabase mdb; 0075 QMimeType mimetype = mdb.mimeTypeForUrl(entryUrl); 0076 if (mimetype.inherits(QStringLiteral("text/plain"))) { 0077 return contentContainsPattern(entryUrl, regex); 0078 } 0079 } 0080 0081 return false; 0082 } 0083 0084 FileNameSearchProtocol::FileNameSearchProtocol(const QByteArray &pool, const QByteArray &app) 0085 : QObject() 0086 , WorkerBase("search", pool, app) 0087 { 0088 QDBusInterface kded(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6")); 0089 kded.call(QStringLiteral("loadModule"), QStringLiteral("filenamesearchmodule")); 0090 } 0091 0092 FileNameSearchProtocol::~FileNameSearchProtocol() = default; 0093 0094 KIO::WorkerResult FileNameSearchProtocol::stat(const QUrl &url) 0095 { 0096 KIO::UDSEntry uds; 0097 uds.reserve(9); 0098 uds.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700); 0099 uds.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0100 uds.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); 0101 uds.fastInsert(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES, QStringLiteral("baloo")); 0102 uds.fastInsert(KIO::UDSEntry::UDS_DISPLAY_TYPE, i18n("Search Folder")); 0103 uds.fastInsert(KIO::UDSEntry::UDS_URL, url.url()); 0104 0105 QUrlQuery query(url); 0106 QString title = query.queryItemValue(QStringLiteral("title"), QUrl::FullyDecoded); 0107 if (!title.isEmpty()) { 0108 uds.fastInsert(KIO::UDSEntry::UDS_NAME, title); 0109 uds.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, title); 0110 } 0111 0112 statEntry(uds); 0113 return KIO::WorkerResult::pass(); 0114 } 0115 0116 // Create a UDSEntry for "." 0117 void FileNameSearchProtocol::listRootEntry() 0118 { 0119 KIO::UDSEntry entry; 0120 entry.reserve(4); 0121 entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); 0122 entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); 0123 entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0); 0124 entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH); 0125 listEntry(entry); 0126 } 0127 0128 KIO::WorkerResult FileNameSearchProtocol::listDir(const QUrl &url) 0129 { 0130 listRootEntry(); 0131 0132 const QUrlQuery urlQuery(url); 0133 const QString search = urlQuery.queryItemValue(QStringLiteral("search")); 0134 if (search.isEmpty()) { 0135 return KIO::WorkerResult::pass(); 0136 } 0137 0138 const QRegularExpression regex(search, QRegularExpression::CaseInsensitiveOption); 0139 if (!regex.isValid()) { 0140 qCWarning(KIO_FILENAMESEARCH) << "Invalid QRegularExpression/PCRE search pattern:" << search; 0141 return KIO::WorkerResult::pass(); 0142 } 0143 0144 const QUrl dirUrl = QUrl(urlQuery.queryItemValue(QStringLiteral("url"))); 0145 0146 // Don't try to iterate the /proc directory of Linux 0147 if (dirUrl.isLocalFile() && dirUrl.toLocalFile() == QLatin1String("/proc")) { 0148 return KIO::WorkerResult::pass(); 0149 } 0150 0151 const bool isContent = urlQuery.queryItemValue(QStringLiteral("checkContent")) == QLatin1String("yes"); 0152 0153 std::set<QString> iteratedDirs; 0154 std::queue<QUrl> pendingDirs; 0155 0156 searchDir(dirUrl, regex, isContent, iteratedDirs, pendingDirs); 0157 0158 while (!pendingDirs.empty()) { 0159 const QUrl pendingUrl = pendingDirs.front(); 0160 pendingDirs.pop(); 0161 searchDir(pendingUrl, regex, isContent, iteratedDirs, pendingDirs); 0162 } 0163 0164 return KIO::WorkerResult::pass(); 0165 } 0166 0167 void FileNameSearchProtocol::searchDir(const QUrl &dirUrl, 0168 const QRegularExpression ®ex, 0169 bool searchContents, 0170 std::set<QString> &iteratedDirs, 0171 std::queue<QUrl> &pendingDirs) 0172 { 0173 KIO::ListJob *listJob = KIO::listRecursive(dirUrl, KIO::HideProgressInfo, KIO::ListJob::ListFlags{}); 0174 0175 connect(this, &QObject::destroyed, listJob, [listJob]() { 0176 listJob->kill(); 0177 }); 0178 0179 connect(listJob, &KIO::ListJob::entries, this, [&](KJob *, const KIO::UDSEntryList &list) { 0180 if (wasKilled()) { // don't finish the search if we are meant to shut down 0181 listJob->kill(); 0182 return; 0183 } 0184 0185 if (listJob->error()) { 0186 qCWarning(KIO_FILENAMESEARCH) << "Searching failed:" << listJob->errorText(); 0187 return; 0188 } 0189 0190 for (auto entry : list) { 0191 QUrl entryUrl(dirUrl); 0192 QString path = entryUrl.path(); 0193 if (!path.endsWith(QLatin1Char('/'))) { 0194 path += QLatin1Char('/'); 0195 } 0196 // UDS_NAME is e.g. "foo/bar/somefile.txt" 0197 entryUrl.setPath(path + entry.stringValue(KIO::UDSEntry::UDS_NAME)); 0198 0199 const QString urlStr = entryUrl.toDisplayString(); 0200 entry.replace(KIO::UDSEntry::UDS_URL, urlStr); 0201 0202 const QString fileName = entryUrl.fileName(); 0203 entry.replace(KIO::UDSEntry::UDS_NAME, fileName); 0204 0205 if (entry.isDir()) { 0206 // Also search the target of a dir symlink 0207 if (const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); !linkDest.isEmpty()) { 0208 // Remember the dir to prevent endless loops 0209 const auto [it, isInserted] = iteratedDirs.insert(linkDest); 0210 if (isInserted) { 0211 pendingDirs.push(entryUrl.resolved(QUrl(linkDest))); 0212 } 0213 } 0214 0215 iteratedDirs.insert(urlStr); 0216 } 0217 0218 if (match(entry, regex, searchContents)) { 0219 // UDS_DISPLAY_NAME is e.g. "foo/bar/somefile.txt" 0220 entry.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, fileName); 0221 listEntry(entry); 0222 } 0223 } 0224 }); 0225 0226 listJob->exec(); 0227 } 0228 0229 extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) 0230 { 0231 QCoreApplication app(argc, argv); 0232 0233 if (argc != 4) { 0234 qCDebug(KIO_FILENAMESEARCH) << "Usage: kio_filenamesearch protocol domain-socket1 domain-socket2"; 0235 return -1; 0236 } 0237 0238 FileNameSearchProtocol worker(argv[2], argv[3]); 0239 worker.dispatchLoop(); 0240 0241 return 0; 0242 } 0243 0244 #include "kio_filenamesearch.moc" 0245 #include "moc_kio_filenamesearch.cpp"