File indexing completed on 2024-04-21 03:55:10

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"