File indexing completed on 2024-05-19 05:00:42

0001 /*
0002     SPDX-FileCopyrightText: 2004, 2005 Jakub Stachowski <qbast@go2.pl>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "dnssd.h"
0008 
0009 // kioworker
0010 #include "zeroconfurl.h"
0011 // KF
0012 #include <KLocalizedString>
0013 #include <KProtocolInfo>
0014 // Qt
0015 #include <QCoreApplication>
0016 #include <qplatformdefs.h> // S_IFDIR
0017 
0018 
0019 class KIOPluginForMetaData : public QObject
0020 {
0021     Q_OBJECT
0022     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.zeroconf" FILE "zeroconf.json")
0023 };
0024 
0025 
0026 void ProtocolData::feedUrl( QUrl* url, const RemoteService* remoteService ) const
0027 {
0028     const QMap<QString,QByteArray> serviceTextData = remoteService->textData();
0029 
0030     url->setScheme( protocol );
0031     if (!userEntry.isNull())
0032         url->setUserName( QString::fromUtf8(serviceTextData[userEntry]) );
0033     if (!passwordEntry.isNull())
0034         url->setPassword( QString::fromUtf8(serviceTextData[passwordEntry]) );
0035     if (!pathEntry.isNull())
0036         url->setPath( QString::fromUtf8(serviceTextData[pathEntry]) );
0037 
0038     url->setHost( remoteService->hostName() );
0039     url->setPort( remoteService->port() );
0040 }
0041 
0042 
0043 ZeroConfProtocol::ZeroConfProtocol(const QByteArray& protocol, const QByteArray &pool_socket, const QByteArray &app_socket)
0044     : WorkerBase(protocol, pool_socket, app_socket),
0045     serviceBrowser(nullptr),
0046     serviceTypeBrowser(nullptr),
0047     serviceToResolve(nullptr)
0048 {
0049     knownProtocols[QStringLiteral("_ftp._tcp")]=     ProtocolData(i18n("FTP servers"),            QStringLiteral("ftp"),    QStringLiteral("path"), QStringLiteral("u"), QStringLiteral("p"));
0050     knownProtocols[QStringLiteral("_webdav._tcp")]=  ProtocolData(i18n("WebDav remote directory"),QStringLiteral("webdav"), QStringLiteral("path"));
0051     knownProtocols[QStringLiteral("_sftp-ssh._tcp")]=ProtocolData(i18n("Remote disk (sftp)"),     QStringLiteral("sftp"),   QString(),              QStringLiteral("u"), QStringLiteral("p"));
0052     knownProtocols[QStringLiteral("_ssh._tcp")]=     ProtocolData(i18n("Remote disk (fish)"),     QStringLiteral("fish"),   QString(),              QStringLiteral("u"), QStringLiteral("p"));
0053     knownProtocols[QStringLiteral("_nfs._tcp")]=     ProtocolData(i18n("NFS remote directory"),   QStringLiteral("nfs"),    QStringLiteral("path"));
0054 }
0055 
0056 ZeroConfProtocol::~ZeroConfProtocol()
0057 {
0058     delete serviceToResolve;
0059 }
0060 
0061 KIO::WorkerResult ZeroConfProtocol::get( const QUrl& url )
0062 {
0063     const KIO::WorkerResult dnssdOkResult = dnssdOK();
0064     if (!dnssdOkResult.success()) {
0065         return dnssdOkResult;
0066     }
0067 
0068     const ZeroConfUrl zeroConfUrl( url );
0069 
0070     ZeroConfUrl::Type type = zeroConfUrl.type();
0071     if (type != ZeroConfUrl::Service) {
0072         return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.toDisplayString());
0073     }
0074 
0075     return resolveAndRedirect( zeroConfUrl );
0076 }
0077 
0078 KIO::WorkerResult ZeroConfProtocol::mimetype( const QUrl& url )
0079 {
0080     return resolveAndRedirect( ZeroConfUrl(url) );
0081 }
0082 
0083 KIO::WorkerResult ZeroConfProtocol::stat( const QUrl& url )
0084 {
0085     const KIO::WorkerResult dnssdOkResult = dnssdOK();
0086     if (!dnssdOkResult.success()) {
0087         return dnssdOkResult;
0088     }
0089 
0090     const ZeroConfUrl zeroConfUrl( url );
0091 
0092     ZeroConfUrl::Type type = zeroConfUrl.type();
0093 
0094     switch (type)
0095     {
0096     case ZeroConfUrl::RootDir:
0097     case ZeroConfUrl::ServiceDir:
0098     {
0099         UDSEntry entry;
0100         feedEntryAsDir( &entry, QString() );
0101         statEntry( entry );
0102         return KIO::WorkerResult::pass();
0103     }
0104     case ZeroConfUrl::Service:
0105         return resolveAndRedirect( zeroConfUrl );
0106         break;
0107     default:
0108         return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.toDisplayString());
0109     }
0110 }
0111 
0112 KIO::WorkerResult ZeroConfProtocol::listDir( const QUrl& url )
0113 {
0114     const KIO::WorkerResult dnssdOkResult = dnssdOK();
0115     if (!dnssdOkResult.success()) {
0116         return dnssdOkResult;
0117     }
0118 
0119     const ZeroConfUrl zeroConfUrl( url );
0120 
0121     ZeroConfUrl::Type type = zeroConfUrl.type();
0122     switch (type)
0123     {
0124     case ZeroConfUrl::RootDir:
0125         listCurrentDirEntry();
0126         serviceTypeBrowser = new ServiceTypeBrowser(zeroConfUrl.domain());
0127         connect(serviceTypeBrowser, &ServiceTypeBrowser::serviceTypeAdded,
0128                 this, &ZeroConfProtocol::addServiceType);
0129         connect(serviceTypeBrowser, &ServiceTypeBrowser::finished,
0130                 this, &ZeroConfProtocol::onBrowserFinished);
0131         serviceTypeBrowser->startBrowse();
0132         enterLoop();
0133         return KIO::WorkerResult::pass();
0134     case ZeroConfUrl::ServiceDir:
0135         if( !knownProtocols.contains(zeroConfUrl.serviceType()) )
0136         {
0137             return KIO::WorkerResult::fail(KIO::ERR_SERVICE_NOT_AVAILABLE, zeroConfUrl.serviceType());
0138         }
0139         listCurrentDirEntry();
0140         serviceBrowser = new ServiceBrowser( zeroConfUrl.serviceType(), false, zeroConfUrl.domain() );
0141         connect(serviceBrowser, &ServiceBrowser::serviceAdded,
0142                 this, &ZeroConfProtocol::addService);
0143         connect(serviceBrowser, &ServiceBrowser::finished,
0144                 this, &ZeroConfProtocol::onBrowserFinished);
0145         serviceBrowser->startBrowse();
0146         enterLoop();
0147         return KIO::WorkerResult::pass();
0148     case ZeroConfUrl::Service:
0149         return resolveAndRedirect( zeroConfUrl );
0150     default:
0151         return KIO::WorkerResult::fail(KIO::ERR_MALFORMED_URL, url.toDisplayString());
0152     }
0153 }
0154 
0155 KIO::WorkerResult ZeroConfProtocol::dnssdOK()
0156 {
0157     switch (ServiceBrowser::isAvailable())
0158     {
0159     case ServiceBrowser::Stopped:
0160         return KIO::WorkerResult::fail( KIO::ERR_UNSUPPORTED_ACTION,
0161                i18n("The Zeroconf daemon (mdnsd) is not running."));
0162      case ServiceBrowser::Unsupported:
0163         return KIO::WorkerResult::fail( KIO::ERR_UNSUPPORTED_ACTION,
0164                i18n("The KDNSSD library has been built without Zeroconf support."));
0165     default:
0166         return KIO::WorkerResult::pass();
0167     }
0168 }
0169 
0170 KIO::WorkerResult ZeroConfProtocol::resolveAndRedirect( const ZeroConfUrl& zeroConfUrl )
0171 {
0172     if (serviceToResolve && !zeroConfUrl.matches(serviceToResolve))
0173     {
0174         delete serviceToResolve;
0175         serviceToResolve = nullptr;
0176     }
0177     if (serviceToResolve == nullptr)
0178     {
0179         if( !knownProtocols.contains(zeroConfUrl.serviceType()) )
0180         {
0181             return KIO::WorkerResult::fail(KIO::ERR_SERVICE_NOT_AVAILABLE, zeroConfUrl.serviceType());
0182         }
0183 
0184         serviceToResolve = new RemoteService( zeroConfUrl.serviceName(), zeroConfUrl.serviceType(), zeroConfUrl.domain() );
0185         if (!serviceToResolve->resolve())
0186         {
0187             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, zeroConfUrl.serviceName());
0188         }
0189     }
0190 
0191     if( !knownProtocols.contains(zeroConfUrl.serviceType()) )
0192         return KIO::WorkerResult::fail();
0193 
0194     // action
0195     const ProtocolData& protocolData = knownProtocols[zeroConfUrl.serviceType()];
0196     QUrl destUrl;
0197     protocolData.feedUrl( &destUrl, serviceToResolve );
0198 
0199     redirection( destUrl );
0200 
0201     return KIO::WorkerResult::pass();
0202 }
0203 
0204 void ZeroConfProtocol::listCurrentDirEntry()
0205 {
0206     UDSEntry entry;
0207     feedEntryAsDir( &entry, QStringLiteral(".") );
0208     listEntry(entry);
0209 }
0210 
0211 void ZeroConfProtocol::addServiceType( const QString& serviceType )
0212 {
0213     if (ServiceTypesAdded.contains(serviceType))
0214         return;
0215     ServiceTypesAdded << serviceType;
0216 
0217     if (!knownProtocols.contains(serviceType))
0218         return;
0219 
0220     // action
0221     UDSEntry entry;
0222     feedEntryAsDir( &entry, serviceType, knownProtocols[serviceType].name );
0223     listEntry( entry );
0224 }
0225 
0226 void ZeroConfProtocol::addService( KDNSSD::RemoteService::Ptr service )
0227 {
0228     UDSEntry entry;
0229     entry.fastInsert(UDSEntry::UDS_NAME,      service->serviceName() );
0230     entry.fastInsert( UDSEntry::UDS_ACCESS,    0666);
0231     entry.fastInsert( UDSEntry::UDS_FILE_TYPE, S_IFDIR );
0232     const QString iconName = KProtocolInfo::icon( knownProtocols[service->type()].protocol );
0233     if (!iconName.isNull())
0234         entry.fastInsert( UDSEntry::UDS_ICON_NAME, iconName );
0235 
0236     listEntry( entry );
0237 }
0238 
0239 void ZeroConfProtocol::onBrowserFinished()
0240 {
0241     // cleanup
0242     if (serviceBrowser)
0243     {
0244         serviceBrowser->deleteLater();
0245         serviceBrowser = nullptr;
0246     }
0247     if (serviceTypeBrowser)
0248     {
0249         serviceTypeBrowser->deleteLater();
0250         serviceTypeBrowser = nullptr;
0251     }
0252     ServiceTypesAdded.clear();
0253 
0254     Q_EMIT leaveModality();
0255 }
0256 
0257 void ZeroConfProtocol::feedEntryAsDir( UDSEntry* entry, const QString& name, const QString& displayName )
0258 {
0259     entry->fastInsert( UDSEntry::UDS_NAME,      name );
0260     entry->fastInsert( UDSEntry::UDS_ACCESS,    0555 );
0261     entry->fastInsert( UDSEntry::UDS_FILE_TYPE, S_IFDIR );
0262     entry->fastInsert( UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory") );
0263     if (!displayName.isEmpty())
0264         entry->fastInsert( UDSEntry::UDS_DISPLAY_NAME, displayName );
0265 }
0266 
0267 void ZeroConfProtocol::enterLoop()
0268 {
0269     QEventLoop eventLoop;
0270     connect(this, &ZeroConfProtocol::leaveModality, &eventLoop, &QEventLoop::quit);
0271     eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
0272 }
0273 
0274 
0275 extern "C" Q_DECL_EXPORT int kdemain( int argc, char **argv )
0276 {
0277     // necessary to use other KIO workers
0278     QCoreApplication app(argc,argv);
0279     app.setApplicationName(QStringLiteral("kio_zeroconf"));
0280 
0281     if (argc != 4) {
0282         fprintf(stderr, "Usage: %s protocol domain-socket1 domain-socket2\n", argv[0]);
0283         exit(-1);
0284     }
0285 
0286     // start the worker
0287     ZeroConfProtocol worker(argv[1],argv[2],argv[3]);
0288     worker.dispatchLoop();
0289     return 0;
0290 }
0291 
0292 #include "dnssd.moc"
0293 #include "moc_dnssd.cpp"