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;
}
}
}
|