File indexing completed on 2024-09-15 03:38:33
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "mimetypefinderjob.h" 0009 0010 #include "global.h" 0011 #include "job.h" // for buildErrorString 0012 #include "kiocoredebug.h" 0013 #include "statjob.h" 0014 #include "transferjob.h" 0015 0016 #include <KLocalizedString> 0017 #include <KProtocolManager> 0018 0019 #include <QMimeDatabase> 0020 #include <QTimer> 0021 #include <QUrl> 0022 0023 class KIO::MimeTypeFinderJobPrivate 0024 { 0025 public: 0026 explicit MimeTypeFinderJobPrivate(const QUrl &url, MimeTypeFinderJob *qq) 0027 : m_url(url) 0028 , q(qq) 0029 { 0030 q->setCapabilities(KJob::Killable); 0031 } 0032 0033 void statFile(); 0034 void scanFileWithGet(); 0035 0036 QUrl m_url; 0037 KIO::MimeTypeFinderJob *const q; 0038 QString m_mimeTypeName; 0039 QString m_suggestedFileName; 0040 bool m_followRedirections = true; 0041 bool m_authPrompts = true; 0042 }; 0043 0044 KIO::MimeTypeFinderJob::MimeTypeFinderJob(const QUrl &url, QObject *parent) 0045 : KCompositeJob(parent) 0046 , d(new MimeTypeFinderJobPrivate(url, this)) 0047 { 0048 } 0049 0050 KIO::MimeTypeFinderJob::~MimeTypeFinderJob() = default; 0051 0052 void KIO::MimeTypeFinderJob::start() 0053 { 0054 if (!d->m_url.isValid() || d->m_url.scheme().isEmpty()) { 0055 const QString error = !d->m_url.isValid() ? d->m_url.errorString() : d->m_url.toDisplayString(); 0056 setError(KIO::ERR_MALFORMED_URL); 0057 setErrorText(i18n("Malformed URL\n%1", error)); 0058 emitResult(); 0059 return; 0060 } 0061 0062 if (!KProtocolManager::supportsListing(d->m_url)) { 0063 // No support for listing => it can't be a directory (example: http) 0064 d->scanFileWithGet(); 0065 return; 0066 } 0067 0068 // It may be a directory or a file, let's use stat to find out 0069 d->statFile(); 0070 } 0071 0072 void KIO::MimeTypeFinderJob::setFollowRedirections(bool b) 0073 { 0074 d->m_followRedirections = b; 0075 } 0076 0077 void KIO::MimeTypeFinderJob::setSuggestedFileName(const QString &suggestedFileName) 0078 { 0079 d->m_suggestedFileName = suggestedFileName; 0080 } 0081 0082 QString KIO::MimeTypeFinderJob::suggestedFileName() const 0083 { 0084 return d->m_suggestedFileName; 0085 } 0086 0087 QString KIO::MimeTypeFinderJob::mimeType() const 0088 { 0089 return d->m_mimeTypeName; 0090 } 0091 0092 void KIO::MimeTypeFinderJob::setAuthenticationPromptEnabled(bool enable) 0093 { 0094 d->m_authPrompts = enable; 0095 } 0096 0097 bool KIO::MimeTypeFinderJob::isAuthenticationPromptEnabled() const 0098 { 0099 return d->m_authPrompts; 0100 } 0101 0102 bool KIO::MimeTypeFinderJob::doKill() 0103 { 0104 // This should really be in KCompositeJob... 0105 const QList<KJob *> jobs = subjobs(); 0106 for (KJob *job : jobs) { 0107 job->kill(); // ret val ignored, see below 0108 } 0109 // Even if for some reason killing a subjob fails, 0110 // we can still consider this job as killed. 0111 // The stat() or get() subjob has no side effects. 0112 return true; 0113 } 0114 0115 void KIO::MimeTypeFinderJob::slotResult(KJob *job) 0116 { 0117 // We do the error handling elsewhere, just do the bookkeeping here 0118 removeSubjob(job); 0119 } 0120 0121 void KIO::MimeTypeFinderJobPrivate::statFile() 0122 { 0123 Q_ASSERT(m_mimeTypeName.isEmpty()); 0124 0125 constexpr auto statFlags = KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatMimeType; 0126 0127 KIO::StatJob *job = KIO::stat(m_url, KIO::StatJob::SourceSide, statFlags, KIO::HideProgressInfo); 0128 if (!m_authPrompts) { 0129 job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); 0130 } 0131 job->setUiDelegate(nullptr); 0132 q->addSubjob(job); 0133 QObject::connect(job, &KJob::result, q, [=]() { 0134 const int errCode = job->error(); 0135 if (errCode) { 0136 // ERR_NO_CONTENT is not an error, but an indication no further 0137 // actions need to be taken. 0138 if (errCode != KIO::ERR_NO_CONTENT) { 0139 q->setError(errCode); 0140 // We're a KJob, not a KIO::Job, so build the error string here 0141 q->setErrorText(KIO::buildErrorString(errCode, job->errorText())); 0142 } 0143 q->emitResult(); 0144 return; 0145 } 0146 if (m_followRedirections) { // Update our URL in case of a redirection 0147 m_url = job->url(); 0148 } 0149 0150 const KIO::UDSEntry entry = job->statResult(); 0151 0152 qCDebug(KIO_CORE) << "UDSEntry from StatJob in MimeTypeFinderJob" << entry; 0153 0154 const QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); 0155 if (!localPath.isEmpty()) { 0156 m_url = QUrl::fromLocalFile(localPath); 0157 } 0158 0159 // MIME type already known? (e.g. print:/manager) 0160 m_mimeTypeName = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); 0161 if (!m_mimeTypeName.isEmpty()) { 0162 q->emitResult(); 0163 return; 0164 } 0165 0166 if (entry.isDir()) { 0167 m_mimeTypeName = QStringLiteral("inode/directory"); 0168 q->emitResult(); 0169 } else { // It's a file 0170 // Start the timer. Once we get the timer event this 0171 // protocol server is back in the pool and we can reuse it. 0172 // This gives better performance than starting a new worker 0173 QTimer::singleShot(0, q, [this] { 0174 scanFileWithGet(); 0175 }); 0176 } 0177 }); 0178 } 0179 0180 static QMimeType fixupMimeType(const QString &mimeType, const QString &fileName) 0181 { 0182 QMimeDatabase db; 0183 QMimeType mime = db.mimeTypeForName(mimeType); 0184 if ((!mime.isValid() || mime.isDefault()) && !fileName.isEmpty()) { 0185 mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension); 0186 } 0187 return mime; 0188 } 0189 0190 void KIO::MimeTypeFinderJobPrivate::scanFileWithGet() 0191 { 0192 Q_ASSERT(m_mimeTypeName.isEmpty()); 0193 0194 if (!KProtocolManager::supportsReading(m_url)) { 0195 qCDebug(KIO_CORE) << "No support for reading from" << m_url.scheme(); 0196 q->setError(KIO::ERR_CANNOT_READ); 0197 // We're a KJob, not a KIO::Job, so build the error string here 0198 q->setErrorText(KIO::buildErrorString(q->error(), m_url.toDisplayString())); 0199 q->emitResult(); 0200 return; 0201 } 0202 // qDebug() << this << "Scanning file" << url; 0203 0204 KIO::TransferJob *job = KIO::get(m_url, KIO::NoReload /*reload*/, KIO::HideProgressInfo); 0205 if (!m_authPrompts) { 0206 job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); 0207 } 0208 job->setUiDelegate(nullptr); 0209 q->addSubjob(job); 0210 QObject::connect(job, &KJob::result, q, [=]() { 0211 const int errCode = job->error(); 0212 if (errCode) { 0213 // ERR_NO_CONTENT is not an error, but an indication no further 0214 // actions need to be taken. 0215 if (errCode != KIO::ERR_NO_CONTENT) { 0216 q->setError(errCode); 0217 q->setErrorText(job->errorString()); 0218 } 0219 q->emitResult(); 0220 } 0221 // if the job succeeded, we certainly hope it emitted mimeTypeFound()... 0222 if (m_mimeTypeName.isEmpty()) { 0223 qCWarning(KIO_CORE) << "KIO::get didn't emit a mimetype! Please fix the KIO worker for URL" << m_url; 0224 q->setError(KIO::ERR_INTERNAL); 0225 q->setErrorText(i18n("Unable to determine the type of file for %1", m_url.toDisplayString())); 0226 q->emitResult(); 0227 } 0228 }); 0229 QObject::connect(job, &KIO::TransferJob::mimeTypeFound, q, [=](KIO::Job *, const QString &mimetype) { 0230 if (m_followRedirections) { // Update our URL in case of a redirection 0231 m_url = job->url(); 0232 } 0233 if (mimetype.isEmpty()) { 0234 qCWarning(KIO_CORE) << "get() didn't emit a MIME type! Probably a KIO worker bug, please check the implementation of" << m_url.scheme(); 0235 } 0236 m_mimeTypeName = mimetype; 0237 0238 // If the current MIME type is the default MIME type, then attempt to 0239 // determine the "real" MIME type from the file name (bug #279675) 0240 const QMimeType mime = fixupMimeType(m_mimeTypeName, m_suggestedFileName.isEmpty() ? m_url.fileName() : m_suggestedFileName); 0241 if (mime.isValid()) { 0242 m_mimeTypeName = mime.name(); 0243 } 0244 0245 if (m_suggestedFileName.isEmpty()) { 0246 m_suggestedFileName = job->queryMetaData(QStringLiteral("content-disposition-filename")); 0247 } 0248 0249 q->emitResult(); 0250 }); 0251 } 0252 0253 #include "moc_mimetypefinderjob.cpp"