File indexing completed on 2024-04-28 11:41:06

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