summaryrefslogtreecommitdiffstats
path: root/xbmc/filesystem/ZeroconfDirectory.cpp
blob: f34b6b2f1f27b212ba32fc997b78efe6595478ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "ZeroconfDirectory.h"

#include "Directory.h"
#include "FileItem.h"
#include "URL.h"
#include "network/ZeroconfBrowser.h"
#include "utils/URIUtils.h"
#include "utils/log.h"

#include <cassert>
#include <stdexcept>

using namespace XFILE;

CZeroconfDirectory::CZeroconfDirectory()
{
  CZeroconfBrowser::GetInstance()->Start();
}

CZeroconfDirectory::~CZeroconfDirectory() = default;

namespace
{
  std::string GetHumanReadableProtocol(std::string const& fcr_service_type)
  {
    if(fcr_service_type == "_smb._tcp.")
      return "SAMBA";
    else if(fcr_service_type == "_ftp._tcp.")
      return "FTP";
    else if(fcr_service_type == "_webdav._tcp.")
      return "WebDAV";
    else if(fcr_service_type == "_nfs._tcp.")
      return "NFS";
    else if(fcr_service_type == "_sftp-ssh._tcp.")
      return "SFTP";
    //fallback, just return the received type
    return fcr_service_type;
  }
  bool GetXBMCProtocol(std::string const& fcr_service_type, std::string& fr_protocol)
  {
    if(fcr_service_type == "_smb._tcp.")
      fr_protocol = "smb";
    else if(fcr_service_type == "_ftp._tcp.")
      fr_protocol = "ftp";
    else if(fcr_service_type == "_webdav._tcp.")
      fr_protocol = "dav";
    else if(fcr_service_type == "_nfs._tcp.")
      fr_protocol = "nfs";
    else if(fcr_service_type == "_sftp-ssh._tcp.")
      fr_protocol = "sftp";
    else
      return false;
    return true;
  }
}

bool GetDirectoryFromTxtRecords(const CZeroconfBrowser::ZeroconfService& zeroconf_service, CURL& url, CFileItemList &items)
{
  bool ret = false;

  //get the txt-records from this service
  CZeroconfBrowser::ZeroconfService::tTxtRecordMap txtRecords=zeroconf_service.GetTxtRecords();

  //if we have some records
  if(!txtRecords.empty())
  {
    std::string path;
    std::string username;
    std::string password;

    //search for a path key entry
    CZeroconfBrowser::ZeroconfService::tTxtRecordMap::iterator it = txtRecords.find(TXT_RECORD_PATH_KEY);

    //if we found the key - be sure there is a value there
    if( it != txtRecords.end() && !it->second.empty() )
    {
      //from now on we treat the value as a path - everything else would mean
      //a misconfigured zeroconf server.
      path=it->second;
    }

    //search for a username key entry
    it = txtRecords.find(TXT_RECORD_USERNAME_KEY);

    //if we found the key - be sure there is a value there
    if( it != txtRecords.end() && !it->second.empty() )
    {
      username=it->second;
      url.SetUserName(username);
    }

    //search for a password key entry
    it = txtRecords.find(TXT_RECORD_PASSWORD_KEY);

    //if we found the key - be sure there is a value there
    if( it != txtRecords.end() && !it->second.empty() )
    {
      password=it->second;
      url.SetPassword(password);
    }

    //if we got a path - add a item - else at least we maybe have set username and password to theurl
    if( !path.empty())
    {
      CFileItemPtr item(new CFileItem("", true));
      std::string urlStr(url.Get());
      //if path has a leading slash (sure it should have one)
      if( path.at(0) == '/' )
      {
        URIUtils::RemoveSlashAtEnd(urlStr);//we don't need the slash at and of url then
      }
      else//path doesn't start with slash -
      {//this is some kind of misconfiguration - we fix it by adding a slash to the url
        URIUtils::AddSlashAtEnd(urlStr);
      }

      //add slash at end of path since it has to be a folder
      URIUtils::AddSlashAtEnd(path);
      //this is the full path including remote stuff (e.x. nfs://ip/path
      item->SetPath(urlStr + path);
      //remove the slash at the end of the path or GetFileName will not give the last dir
      URIUtils::RemoveSlashAtEnd(path);
      //set the label to the last directory in path
      if( !URIUtils::GetFileName(path).empty() )
        item->SetLabel(URIUtils::GetFileName(path));
      else
        item->SetLabel("/");

      item->SetLabelPreformatted(true);
      //just set the default folder icon
      item->FillInDefaultIcon();
      item->m_bIsShareOrDrive=true;
      items.Add(item);
      ret = true;
    }
  }
  return ret;
}

bool CZeroconfDirectory::GetDirectory(const CURL& url, CFileItemList &items)
{
  assert(url.IsProtocol("zeroconf"));
  std::string strPath = url.Get();
  std::string path = strPath.substr(11, strPath.length());
  URIUtils::RemoveSlashAtEnd(path);
  if(path.empty())
  {
    std::vector<CZeroconfBrowser::ZeroconfService> found_services = CZeroconfBrowser::GetInstance()->GetFoundServices();
    for (auto& it : found_services)
    {
      //only use discovered services we can connect to through directory
      std::string tmp;
      if (GetXBMCProtocol(it.GetType(), tmp))
      {
        CFileItemPtr item(new CFileItem("", true));
        CURL url;
        url.SetProtocol("zeroconf");
        std::string service_path(CURL::Encode(CZeroconfBrowser::ZeroconfService::toPath(it)));
        url.SetFileName(service_path);
        item->SetPath(url.Get());

        //now do the formatting
        std::string protocol = GetHumanReadableProtocol(it.GetType());
        item->SetLabel(it.GetName() + " (" + protocol + ")");
        item->SetLabelPreformatted(true);
        //just set the default folder icon
        item->FillInDefaultIcon();
        items.Add(item);
      }
    }
    return true;
  }
  else
  {
    //decode the path first
    std::string decoded(CURL::Decode(path));
    try
    {
      CZeroconfBrowser::ZeroconfService zeroconf_service = CZeroconfBrowser::ZeroconfService::fromPath(decoded);

      if(!CZeroconfBrowser::GetInstance()->ResolveService(zeroconf_service))
      {
        CLog::Log(LOGINFO,
                  "CZeroconfDirectory::GetDirectory service ( {} ) could not be resolved in time",
                  zeroconf_service.GetName());
        return false;
      }
      else
      {
        assert(!zeroconf_service.GetIP().empty());
        CURL service;
        service.SetPort(zeroconf_service.GetPort());
        service.SetHostName(zeroconf_service.GetIP());
        //do protocol conversion (_smb._tcp -> smb)
        //! @todo try automatic conversion -> remove leading '_' and '._tcp'?
        std::string protocol;
        if(!GetXBMCProtocol(zeroconf_service.GetType(), protocol))
        {
          CLog::Log(LOGERROR,
                    "CZeroconfDirectory::GetDirectory Unknown service type ({}), skipping; ",
                    zeroconf_service.GetType());
          return false;
        }

        service.SetProtocol(protocol);

        //first try to show the txt-record defined path if any
        if(GetDirectoryFromTxtRecords(zeroconf_service, service, items))
        {
          return true;
        }
        else//no txt record path - so let the CDirectory handler show the folders
        {
          return CDirectory::GetDirectory(service.Get(), items, "", DIR_FLAG_ALLOW_PROMPT);
        }
      }
    } catch (std::runtime_error& e) {
      CLog::Log(LOGERROR,
                "CZeroconfDirectory::GetDirectory failed getting directory: '{}'. Error: '{}'",
                decoded, e.what());
      return false;
    }
  }
}