[WASAPI] fix stream types and frequencies enumeration
[xbmc.git] / xbmc / URL.cpp
blob8bc0f98c7f595870fa14eeee84283b6e376a7b9c
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "URL.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "ServiceBroker.h"
14 #include "Util.h"
15 #include "filesystem/File.h"
16 #include "filesystem/StackDirectory.h"
17 #include "network/Network.h"
18 #include "utils/StringUtils.h"
19 #include "utils/URIUtils.h"
20 #include "utils/log.h"
21 #ifndef TARGET_POSIX
22 #include <sys\stat.h>
23 #endif
25 #include <charconv>
26 #include <iterator>
27 #include <string>
28 #include <system_error>
29 #include <vector>
31 #include <fmt/xchar.h>
33 using namespace ADDON;
35 CURL::~CURL() = default;
37 void CURL::Reset()
39 m_strHostName.clear();
40 m_strDomain.clear();
41 m_strUserName.clear();
42 m_strPassword.clear();
43 m_strShareName.clear();
44 m_strFileName.clear();
45 m_strProtocol.clear();
46 m_strFileType.clear();
47 m_strOptions.clear();
48 m_strProtocolOptions.clear();
49 m_options.Clear();
50 m_protocolOptions.Clear();
51 m_iPort = 0;
54 void CURL::Parse(std::string strURL1)
56 Reset();
57 // start by validating the path
58 std::string strURL = CUtil::ValidatePath(std::move(strURL1));
60 // strURL can be one of the following:
61 // format 1: protocol://[username:password]@hostname[:port]/directoryandfile
62 // format 2: protocol://file
63 // format 3: drive:directoryandfile
65 // first need 2 check if this is a protocol or just a normal drive & path
66 if (!strURL.size()) return ;
67 if (strURL == "?") return;
69 // form is format 1 or 2
70 // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
71 // format 2: protocol://file
73 // decode protocol
74 size_t iPos = strURL.find("://");
75 if (iPos == std::string::npos)
77 // This is an ugly hack that needs some work.
78 // example: filename /foo/bar.zip/alice.rar/bob.avi
79 // This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
80 iPos = 0;
81 bool is_apk = (strURL.find(".apk/", iPos) != std::string::npos);
82 while (true)
84 if (is_apk)
85 iPos = strURL.find(".apk/", iPos);
86 else
87 iPos = strURL.find(".zip/", iPos);
89 int extLen = 3;
90 if (iPos == std::string::npos)
92 /* set filename and update extension*/
93 SetFileName(std::move(strURL));
94 return ;
96 iPos += extLen + 1;
97 std::string archiveName = strURL.substr(0, iPos);
98 struct __stat64 s;
99 if (XFILE::CFile::Stat(archiveName, &s) == 0)
101 #ifdef TARGET_POSIX
102 if (!S_ISDIR(s.st_mode))
103 #else
104 if (!(s.st_mode & S_IFDIR))
105 #endif
107 archiveName = Encode(archiveName);
108 if (is_apk)
110 CURL c("apk://" + archiveName + "/" + std::move(strURL).substr(iPos + 1));
111 *this = c;
113 else
115 CURL c("zip://" + archiveName + "/" + std::move(strURL).substr(iPos + 1));
116 *this = c;
118 return;
123 else
125 SetProtocol(strURL.substr(0, iPos));
126 iPos += 3;
129 // virtual protocols
130 // why not handle all format 2 (protocol://file) style urls here?
131 // ones that come to mind are iso9660, cdda, musicdb, etc.
132 // they are all local protocols and have no server part, port number, special options, etc.
133 // this removes the need for special handling below.
134 // clang-format off
135 if (
136 IsProtocol("stack") ||
137 IsProtocol("virtualpath") ||
138 IsProtocol("multipath") ||
139 IsProtocol("special") ||
140 IsProtocol("resource") ||
141 IsProtocol("file")
143 // clang-format on
145 SetFileName(std::move(strURL).substr(iPos));
146 return;
149 if (IsProtocol("udf") || IsProtocol("iso9660"))
151 std::string lower(strURL);
152 StringUtils::ToLower(lower);
153 size_t isoPos = lower.find(".iso\\", iPos);
154 if (isoPos == std::string::npos)
155 isoPos = lower.find(".udf\\", iPos);
156 if (isoPos != std::string::npos)
158 strURL.replace(isoPos + 4, 1, "/");
162 // check for username/password - should occur before first /
163 if (iPos == std::string::npos) iPos = 0;
165 // for protocols supporting options, chop that part off here
166 // maybe we should invert this list instead?
167 size_t iEnd = strURL.length();
168 const char* sep = NULL;
170 //! @todo fix all Addon paths
171 std::string strProtocol2 = GetTranslatedProtocol();
172 if(IsProtocol("rss") ||
173 IsProtocol("rsss") ||
174 IsProtocol("rar") ||
175 IsProtocol("apk") ||
176 IsProtocol("xbt") ||
177 IsProtocol("zip") ||
178 IsProtocol("addons") ||
179 IsProtocol("image") ||
180 IsProtocol("videodb") ||
181 IsProtocol("musicdb") ||
182 IsProtocol("androidapp") ||
183 IsProtocol("pvr"))
184 sep = "?";
185 else
186 if( IsProtocolEqual(strProtocol2, "http")
187 || IsProtocolEqual(strProtocol2, "https")
188 || IsProtocolEqual(strProtocol2, "plugin")
189 || IsProtocolEqual(strProtocol2, "addons")
190 || IsProtocolEqual(strProtocol2, "rtsp"))
191 sep = "?;#|";
192 else if(IsProtocolEqual(strProtocol2, "ftp")
193 || IsProtocolEqual(strProtocol2, "ftps"))
194 sep = "?;|";
196 if(sep)
198 size_t iOptions = strURL.find_first_of(sep, iPos);
199 if (iOptions != std::string::npos)
201 // we keep the initial char as it can be any of the above
202 size_t iProto = strURL.find_first_of('|', iOptions);
203 if (iProto != std::string::npos)
205 SetProtocolOptions(strURL.substr(iProto+1));
206 SetOptions(strURL.substr(iOptions,iProto-iOptions));
208 else
209 SetOptions(strURL.substr(iOptions));
210 iEnd = iOptions;
214 size_t iSlash = strURL.find('/', iPos);
215 if(iSlash >= iEnd)
216 iSlash = std::string::npos; // was an invalid slash as it was contained in options
218 // also skip parsing username:password@ for udp/rtp as it not valid
219 // and conflicts with the following example: rtp://sourceip@multicastip
220 size_t iAlphaSign = strURL.find('@', iPos);
221 if (iAlphaSign != std::string::npos && iAlphaSign < iEnd &&
222 (iAlphaSign < iSlash || iSlash == std::string::npos) &&
223 !IsProtocol("udp") && !IsProtocol("rtp"))
225 // username/password found
226 std::string strUserNamePassword = strURL.substr(iPos, iAlphaSign - iPos);
228 // first extract domain, if protocol is smb
229 if (IsProtocol("smb"))
231 size_t iSemiColon = strUserNamePassword.find(';');
233 if (iSemiColon != std::string::npos)
235 m_strDomain = strUserNamePassword.substr(0, iSemiColon);
236 strUserNamePassword.erase(0, iSemiColon + 1);
240 // username:password
241 size_t iColon = strUserNamePassword.find(':');
242 if (iColon != std::string::npos)
244 m_strUserName = strUserNamePassword.substr(0, iColon);
245 m_strPassword = strUserNamePassword.substr(iColon + 1);
247 // username
248 else
250 m_strUserName = std::move(strUserNamePassword);
253 iPos = iAlphaSign + 1;
254 iSlash = strURL.find('/', iAlphaSign);
256 if (iSlash >= iEnd)
257 iSlash = std::string::npos;
260 std::string strHostNameAndPort = strURL.substr(iPos, (iSlash == std::string::npos) ? iEnd - iPos : iSlash - iPos);
261 // check for IPv6 numerical representation inside [].
262 // if [] found, let's store string inside as hostname
263 // and remove that parsed part from strHostNameAndPort
264 size_t iBrk = strHostNameAndPort.rfind(']');
265 if (iBrk != std::string::npos && strHostNameAndPort.starts_with('['))
267 m_strHostName = strHostNameAndPort.substr(1, iBrk-1);
268 strHostNameAndPort.erase(0, iBrk+1);
271 // detect hostname:port/ or just :port/ if previous step found [IPv6] format
272 size_t iColon = strHostNameAndPort.rfind(':');
273 if (iColon != std::string::npos && iColon == strHostNameAndPort.find(':'))
275 if (m_strHostName.empty())
276 m_strHostName = strHostNameAndPort.substr(0, iColon);
277 m_iPort = atoi(strHostNameAndPort.substr(iColon + 1).c_str());
280 // if we still don't have hostname, the strHostNameAndPort substring
281 // is 'just' hostname without :port specification - so use it as is.
282 if (m_strHostName.empty())
283 m_strHostName = std::move(strHostNameAndPort);
285 if (iSlash != std::string::npos)
287 iPos = iSlash + 1;
288 if (iEnd > iPos)
289 m_strFileName = strURL.substr(iPos, iEnd - iPos);
292 if (IsProtocol("musicdb") || IsProtocol("videodb") || IsProtocol("sources") || IsProtocol("pvr"))
294 if (m_strHostName != "" && m_strFileName != "")
296 m_strFileName = StringUtils::Format("{}/{}", m_strHostName, m_strFileName);
297 m_strHostName = "";
299 else
301 if (!m_strHostName.empty() && strURL[iEnd-1]=='/')
302 m_strFileName = m_strHostName + "/";
303 else
304 m_strFileName = m_strHostName;
305 m_strHostName = "";
309 StringUtils::Replace(m_strFileName, '\\', '/');
311 /* update extension + sharename */
312 SetFileName(m_strFileName);
314 /* decode urlencoding on this stuff */
315 if(URIUtils::HasEncodedHostname(*this))
317 m_strHostName = Decode(m_strHostName);
318 SetHostName(m_strHostName);
321 m_strUserName = Decode(m_strUserName);
322 m_strPassword = Decode(m_strPassword);
325 void CURL::SetFileName(std::string strFileName)
327 m_strFileName = std::move(strFileName);
329 size_t slash = m_strFileName.find_last_of(GetDirectorySeparator());
330 size_t period = m_strFileName.find_last_of('.');
331 if(period != std::string::npos && (slash == std::string::npos || period > slash))
332 m_strFileType = m_strFileName.substr(period+1);
333 else
334 m_strFileType = "";
336 slash = m_strFileName.find_first_of(GetDirectorySeparator());
337 if(slash == std::string::npos)
338 m_strShareName = m_strFileName;
339 else
340 m_strShareName = m_strFileName.substr(0, slash);
342 StringUtils::Trim(m_strFileType);
343 StringUtils::ToLower(m_strFileType);
346 void CURL::SetProtocol(std::string strProtocol)
348 m_strProtocol = std::move(strProtocol);
349 StringUtils::ToLower(m_strProtocol);
352 void CURL::SetOptions(std::string strOptions)
354 m_strOptions.clear();
355 m_options.Clear();
356 if( strOptions.length() > 0)
358 if(strOptions[0] == '?' ||
359 strOptions[0] == '#' ||
360 strOptions[0] == ';' ||
361 strOptions.find("xml") != std::string::npos)
363 m_strOptions = std::move(strOptions);
364 m_options.AddOptions(m_strOptions);
366 else
367 CLog::Log(LOGWARNING, "{} - Invalid options specified for url {}", __FUNCTION__, strOptions);
371 void CURL::SetProtocolOptions(std::string strOptions)
373 m_strProtocolOptions.clear();
374 m_protocolOptions.Clear();
375 if (strOptions.length() > 0)
377 if (strOptions[0] == '|')
378 m_strProtocolOptions = std::move(strOptions).substr(1);
379 else
380 m_strProtocolOptions = std::move(strOptions);
381 m_protocolOptions.AddOptions(m_strProtocolOptions);
385 std::string CURL::GetTranslatedProtocol() const
387 if (IsProtocol("shout")
388 || IsProtocol("dav")
389 || IsProtocol("rss"))
390 return "http";
392 if (IsProtocol("davs")
393 || IsProtocol("rsss"))
394 return "https";
396 return GetProtocol();
399 std::string CURL::GetFileNameWithoutPath() const
401 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
402 if ((IsProtocol("rar") ||
403 IsProtocol("zip") ||
404 IsProtocol("xbt") ||
405 IsProtocol("apk")) &&
406 m_strFileName.empty())
407 return URIUtils::GetFileName(m_strHostName);
409 // otherwise, we've already got the filepath, so just grab the filename portion
410 std::string file(m_strFileName);
411 URIUtils::RemoveSlashAtEnd(file);
412 return URIUtils::GetFileName(file);
415 inline
416 void protectIPv6(std::string &hn)
418 if (!hn.empty() && hn.find(':') != hn.rfind(':') && hn.find(':') != std::string::npos)
420 hn = '[' + hn + ']';
424 char CURL::GetDirectorySeparator() const
426 #ifndef TARGET_POSIX
427 //We don't want to use IsLocal here, it can return true
428 //for network protocols that matches localhost or hostname
429 //we only ever want to use \ for win32 local filesystem
430 if ( m_strProtocol.empty() )
431 return '\\';
432 else
433 #endif
434 return '/';
437 std::string CURL::Get() const
439 if (m_strProtocol.empty())
440 return m_strFileName;
442 unsigned int sizeneed = m_strProtocol.length()
443 + m_strDomain.length()
444 + m_strUserName.length()
445 + m_strPassword.length()
446 + m_strHostName.length()
447 + m_strFileName.length()
448 + m_strOptions.length()
449 + m_strProtocolOptions.length()
450 + 10;
452 std::string strURL;
453 strURL.reserve(sizeneed);
455 strURL = GetWithoutOptions();
457 if( !m_strOptions.empty() )
458 strURL += m_strOptions;
460 if (!m_strProtocolOptions.empty())
461 strURL += "|"+m_strProtocolOptions;
463 return strURL;
466 std::string CURL::GetWithoutOptions() const
468 if (m_strProtocol.empty())
469 return m_strFileName;
471 std::string strGet = GetWithoutFilename();
473 // Prevent double slash when concatenating host part and filename part
474 if (m_strFileName.size() && (m_strFileName[0] == '/' || m_strFileName[0] == '\\') && URIUtils::HasSlashAtEnd(strGet))
475 URIUtils::RemoveSlashAtEnd(strGet);
477 return strGet + m_strFileName;
480 std::string CURL::GetWithoutUserDetails(bool redact) const
482 std::string strURL;
484 if (IsProtocol("stack"))
486 CFileItemList items;
487 XFILE::CStackDirectory dir;
488 dir.GetDirectory(*this,items);
489 std::vector<std::string> newItems;
490 for (int i=0;i<items.Size();++i)
492 CURL url(items[i]->GetPath());
493 items[i]->SetPath(url.GetWithoutUserDetails(redact));
494 newItems.push_back(items[i]->GetPath());
496 dir.ConstructStackPath(newItems, strURL);
497 return strURL;
500 unsigned int sizeneed = m_strProtocol.length()
501 + m_strHostName.length()
502 + m_strFileName.length()
503 + m_strOptions.length()
504 + m_strProtocolOptions.length()
505 + 10;
507 if (redact && !m_strUserName.empty())
509 sizeneed += sizeof("USERNAME");
510 if (!m_strPassword.empty())
511 sizeneed += sizeof(":PASSWORD@");
512 if (!m_strDomain.empty())
513 sizeneed += sizeof("DOMAIN;");
516 strURL.reserve(sizeneed);
518 if (m_strProtocol.empty())
519 return m_strFileName;
521 strURL = m_strProtocol;
522 strURL += "://";
524 if (redact && !m_strUserName.empty())
526 if (!m_strDomain.empty())
527 strURL += "DOMAIN;";
528 strURL += "USERNAME";
529 if (!m_strPassword.empty())
530 strURL += ":PASSWORD";
531 strURL += "@";
534 if (!m_strHostName.empty())
536 std::string strHostName;
538 if (URIUtils::HasParentInHostname(*this))
539 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
540 else
541 strHostName = m_strHostName;
543 if (URIUtils::HasEncodedHostname(*this))
544 strHostName = Encode(strHostName);
546 if ( HasPort() )
548 protectIPv6(strHostName);
549 strURL += strHostName + StringUtils::Format(":{}", m_iPort);
551 else
552 strURL += strHostName;
554 strURL += "/";
556 strURL += m_strFileName;
558 if( m_strOptions.length() > 0 )
559 strURL += m_strOptions;
560 if( m_strProtocolOptions.length() > 0 )
561 strURL += "|"+m_strProtocolOptions;
563 return strURL;
566 std::string CURL::GetWithoutFilename() const
568 if (m_strProtocol.empty())
569 return "";
571 unsigned int sizeneed = m_strProtocol.length()
572 + m_strDomain.length()
573 + m_strUserName.length()
574 + m_strPassword.length()
575 + m_strHostName.length()
576 + 10;
578 std::string strURL;
579 strURL.reserve(sizeneed);
581 strURL = m_strProtocol;
582 strURL += "://";
584 if (!m_strUserName.empty())
586 if (!m_strDomain.empty())
588 strURL += Encode(m_strDomain);
589 strURL += ";";
591 strURL += Encode(m_strUserName);
592 if (!m_strPassword.empty())
594 strURL += ":";
595 strURL += Encode(m_strPassword);
597 strURL += "@";
600 if (!m_strHostName.empty())
602 std::string hostname;
604 if( URIUtils::HasEncodedHostname(*this) )
605 hostname = Encode(m_strHostName);
606 else
607 hostname = m_strHostName;
609 if (HasPort())
611 protectIPv6(hostname);
612 strURL += hostname + StringUtils::Format(":{}", m_iPort);
614 else
615 strURL += hostname;
617 strURL += "/";
620 return strURL;
623 std::string CURL::GetRedacted() const
625 return GetWithoutUserDetails(true);
628 std::string CURL::GetRedacted(std::string path)
630 return CURL(std::move(path)).GetRedacted();
633 bool CURL::IsLocal() const
635 return (m_strProtocol.empty() || IsLocalHost() || IsProtocol("win-lib"));
638 bool CURL::IsLocalHost() const
640 return CServiceBroker::GetNetwork().IsLocalHost(m_strHostName);
643 bool CURL::IsFileOnly(const std::string &url)
645 return url.find_first_of("/\\") == std::string::npos;
648 bool CURL::IsFullPath(const std::string &url)
650 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
651 if (url.find("://") != std::string::npos) return true; // foo://bar.ext
652 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
653 if (StringUtils::StartsWith(url, "\\\\")) return true; // \\UNC\path\to\file
654 return false;
657 std::string CURL::Decode(std::string_view strURLData)
658 //modified to be more accommodating - if a non hex value follows a % take the characters directly and don't raise an error.
659 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
661 std::string strResult;
663 /* result will always be less than source */
664 strResult.reserve( strURLData.length() );
666 const char* const iterEnd = strURLData.data() + strURLData.size();
667 for (const char* iter = strURLData.data(); iter < iterEnd; ++iter)
669 if (*iter == '+')
670 strResult += ' ';
671 else if (*iter == '%')
673 if (std::distance(iter, iterEnd) >= 3)
675 uint8_t dec_num{};
676 const std::from_chars_result res = std::from_chars(iter + 1, iter + 3, dec_num, 16);
677 if (res.ec != std::errc() || res.ptr != iter + 3)
678 strResult += *iter;
679 else
681 strResult += (char)dec_num;
682 iter += 2;
685 else
686 strResult += *iter;
688 else
689 strResult += *iter;
692 return strResult;
695 std::string CURL::Encode(std::string_view strURLData)
697 std::string strResult;
699 /* wonder what a good value is here is, depends on how often it occurs */
700 strResult.reserve( strURLData.length() * 2 );
702 for (auto kar : strURLData)
704 // Don't URL encode "-_.!()" according to RFC1738
705 //! @todo Update it to "-_.~" after Gotham according to RFC3986
706 if (StringUtils::isasciialphanum(kar) || kar == '-' || kar == '.' || kar == '_' || kar == '!' || kar == '(' || kar == ')')
707 strResult.push_back(kar);
708 else
709 fmt::format_to(std::back_insert_iterator(strResult), "%{:02x}",
710 (unsigned int)((unsigned char)kar));
713 return strResult;
716 bool CURL::IsProtocolEqual(const std::string &protocol, const char *type)
719 NOTE: We're currently using == here as m_strProtocol is assigned as lower-case in SetProtocol(),
720 and we've assumed all other callers are calling with protocol lower-case otherwise.
721 We possibly shouldn't do this (as CURL(foo).Get() != foo, though there are other reasons for this as well)
722 but it handles the requirements of RFC-1738 which allows the scheme to be case-insensitive.
724 if (type)
725 return protocol == type;
726 return false;
729 void CURL::GetOptions(std::map<std::string, std::string> &options) const
731 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
732 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin();
733 option != optionsMap.end(); ++option)
734 options[option->first] = option->second.asString();
737 bool CURL::HasOption(const std::string &key) const
739 return m_options.HasOption(key);
742 bool CURL::GetOption(const std::string &key, std::string &value) const
744 CVariant valueObj;
745 if (!m_options.GetOption(key, valueObj))
746 return false;
748 value = valueObj.asString();
749 return true;
752 std::string CURL::GetOption(const std::string &key) const
754 std::string value;
755 if (!GetOption(key, value))
756 return "";
758 return value;
761 void CURL::SetOption(const std::string &key, const std::string &value)
763 m_options.AddOption(key, value);
764 SetOptions(m_options.GetOptionsString(true));
767 void CURL::RemoveOption(const std::string &key)
769 m_options.RemoveOption(key);
770 SetOptions(m_options.GetOptionsString(true));
773 void CURL::GetProtocolOptions(std::map<std::string, std::string> &options) const
775 CUrlOptions::UrlOptions optionsMap = m_protocolOptions.GetOptions();
776 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin();
777 option != optionsMap.end(); ++option)
778 options[option->first] = option->second.asString();
781 bool CURL::HasProtocolOption(const std::string &key) const
783 return m_protocolOptions.HasOption(key);
786 bool CURL::GetProtocolOption(const std::string &key, std::string &value) const
788 CVariant valueObj;
789 if (!m_protocolOptions.GetOption(key, valueObj))
790 return false;
792 value = valueObj.asString();
793 return true;
796 std::string CURL::GetProtocolOption(const std::string &key) const
798 std::string value;
799 if (!GetProtocolOption(key, value))
800 return "";
802 return value;
805 void CURL::SetProtocolOption(const std::string &key, const std::string &value)
807 m_protocolOptions.AddOption(key, value);
808 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);
811 void CURL::RemoveProtocolOption(const std::string &key)
813 m_protocolOptions.RemoveOption(key);
814 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);