File indexing completed on 2024-04-28 04:58:02

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2000 Caldera Systems Inc.
0004     SPDX-FileCopyrightText: 2020-2021 Harald Sitter <sitter@kde.org>
0005     SPDX-FileContributor: Matthew Peterson <mpeterson@caldera.com>
0006 */
0007 
0008 #include "smburl.h"
0009 #include "smb-logsettings.h"
0010 
0011 #include <KConfig>
0012 #include <KIO/Global>
0013 #include <QDir>
0014 #include <QHostAddress>
0015 #include <QUrlQuery>
0016 
0017 SMBUrl::SMBUrl(const QUrl &kurl)
0018     : QUrl(kurl)
0019 {
0020     // We treat cifs as an alias but need to translate it to smb.
0021     // https://bugs.kde.org/show_bug.cgi?id=327295
0022     // It's not IANA registered and also libsmbc internally expects
0023     // smb URIs so we do very broadly coerce cifs to smb.
0024     // Also see SMBWorker::checkURL.
0025     if (scheme() == "cifs") {
0026         setScheme("smb");
0027     }
0028     updateCache();
0029 }
0030 
0031 SMBUrl::SMBUrl() = default;
0032 SMBUrl::SMBUrl(const SMBUrl &other) = default;
0033 SMBUrl::~SMBUrl() = default;
0034 SMBUrl &SMBUrl::operator=(const SMBUrl &) = default;
0035 
0036 void SMBUrl::addPath(const QString &filedir)
0037 {
0038     if (path().length() > 0 && path().at(path().length() - 1) != QLatin1Char('/')) {
0039         QUrl::setPath(path() + QLatin1Char('/') + filedir);
0040     } else {
0041         QUrl::setPath(path() + filedir);
0042     }
0043     updateCache();
0044 }
0045 
0046 void SMBUrl::cdUp()
0047 {
0048     setUrl(KIO::upUrl(*this).url());
0049     updateCache();
0050 }
0051 
0052 void SMBUrl::updateCache()
0053 {
0054     QUrl::setPath(QDir::cleanPath(path()));
0055 
0056     // SMB URLs are UTF-8 encoded
0057     qCDebug(KIO_SMB_LOG) << "updateCache " << QUrl::path();
0058 
0059     QUrl sambaUrl(*this);
0060 
0061     const QHostAddress address(sambaUrl.host());
0062     switch (address.protocol()) {
0063     case QAbstractSocket::IPv6Protocol: {
0064         // Convert to Windows IPv6 literal to bypass limitations in samba.
0065         // https://bugzilla.samba.org/show_bug.cgi?id=14297
0066         // https://docs.microsoft.com/en-us/windows/win32/api/winnetwk/nf-winnetwk-wnetaddconnection2a
0067         // https://devblogs.microsoft.com/oldnewthing/20100915-00/?p=12863
0068         // https://www.samba.org/~idra/code/nss-ipv6literal/README.html
0069         // https://ipv6-literal.com
0070         QString literal = address.toString();
0071         literal.replace(':', '-'); // address
0072         literal.replace('%', 's'); // scope
0073         if (literal.front() == '-') {
0074             // Special prefix for [::f] so it doesn't start with a dash.
0075             literal.prepend('0');
0076         }
0077         if (literal.back() == '-') {
0078             // Special suffix, also cannot end with a dash.
0079             literal.append('0');
0080         }
0081         literal += ".ipv6-literal.net"; // reserved host host
0082         qCDebug(KIO_SMB_LOG) << "converting IPv6 to literal " << host() << literal;
0083         sambaUrl.setHost(literal);
0084         break;
0085     }
0086     case QAbstractSocket::IPv4Protocol:
0087     case QAbstractSocket::AnyIPProtocol:
0088     case QAbstractSocket::UnknownNetworkLayerProtocol:
0089         break;
0090     }
0091 
0092     // NetBios workgroup names may contain characters that QUrl will not
0093     // allow in a host. Yet the SMB URI requires us to have the workgroup
0094     // in the host field when browsing a workgroup.
0095     // As a hacky workaround we'll not set a host but use a query param
0096     // when encountering a workgroup that causes QUrl to error out.
0097     // For libsmbc we then need to translate the query back to SMB URI.
0098     // Since this is super daft string construction it will doubltlessly
0099     // be imperfect and so we do still prefer deferring the string
0100     // construction to QUrl whenever possible.
0101     // https://support.microsoft.com/en-gb/help/909264/naming-conventions-in-active-directory-for-computers-domains-sites-and
0102     // https://bugs.kde.org/show_bug.cgi?id=204423
0103     //
0104     // Should we ever stop supporting workgroup browsing this entire
0105     // hack can be removed.
0106     QUrlQuery query(sambaUrl);
0107     const QString workgroup = query.queryItemValue("kio-workgroup");
0108     if (workgroup.isEmpty()) {
0109         // If we don't have a hack to apply we can simply defer to QUrl
0110         if (sambaUrl.url() == "smb:/") {
0111             m_surl = "smb://";
0112         } else {
0113             m_surl = sambaUrl.toString(QUrl::PrettyDecoded).toUtf8();
0114         }
0115     } else {
0116         // If we have a workgroup hack to apply we need to manually construct
0117         // the stringy URI.
0118         query.removeQueryItem("kio-workgroup");
0119         sambaUrl.setQuery(query);
0120 
0121         QString url;
0122         url = "smb://";
0123         if (!sambaUrl.userInfo().isEmpty()) {
0124             url += sambaUrl.userInfo() + "@";
0125         }
0126         url += workgroup;
0127         // Workgroups can have ports per the IANA definition of smb.
0128         if (sambaUrl.port() != -1) {
0129             url += ':' + QString::number(sambaUrl.port());
0130         }
0131 
0132         // Make sure to only use clear paths. libsmbc is allergic to excess slashes.
0133         QString path('/');
0134         if (!sambaUrl.host().isEmpty()) {
0135             path += sambaUrl.host();
0136         }
0137         if (!sambaUrl.path().isEmpty()) {
0138             path += sambaUrl.path();
0139         }
0140         url += QDir::cleanPath(path);
0141 
0142         if (!sambaUrl.query().isEmpty()) {
0143             url += '?' + sambaUrl.query();
0144         }
0145         if (!sambaUrl.fragment().isEmpty()) {
0146             url += '#' + sambaUrl.fragment();
0147         }
0148         m_surl = url.toUtf8();
0149     }
0150 
0151     m_type = SMBURLTYPE_UNKNOWN;
0152     // update m_type
0153     (void)getType();
0154 }
0155 
0156 SMBUrlType SMBUrl::getType() const
0157 {
0158     if (m_type != SMBURLTYPE_UNKNOWN)
0159         return m_type;
0160 
0161     if (scheme() != "smb") {
0162         m_type = SMBURLTYPE_UNKNOWN;
0163         return m_type;
0164     }
0165 
0166     if (QUrlQuery query(*this); query.queryItemValue("kio-printer") == "true") {
0167         return m_type = SMBURLTYPE_PRINTER;
0168     }
0169 
0170     if (path().isEmpty() || path(QUrl::FullyDecoded) == "/") {
0171         if (host().isEmpty() && !query().contains("kio-workgroup"))
0172             m_type = SMBURLTYPE_ENTIRE_NETWORK;
0173         else
0174             m_type = SMBURLTYPE_WORKGROUP_OR_SERVER;
0175         return m_type;
0176     }
0177 
0178     // Check for the path if we get this far
0179     m_type = SMBURLTYPE_SHARE_OR_PATH;
0180 
0181     return m_type;
0182 }
0183 
0184 SMBUrl SMBUrl::partUrl() const
0185 {
0186     const bool isRemoteFile = m_type == SMBURLTYPE_SHARE_OR_PATH && !fileName().isEmpty();
0187     const bool isLocalFile = scheme() == QLatin1String("file") && !fileName().isEmpty();
0188     //  Mind that filename doesn't necessarily mean it is a file.
0189     if (isRemoteFile || isLocalFile) {
0190         SMBUrl url(*this);
0191         url.setPath(path() + QLatin1String(".part"));
0192         return url;
0193     }
0194 
0195     return SMBUrl();
0196 }