[windows] Fix MAC Address Discovery
[xbmc.git] / xbmc / URL.cpp
blob073d1104e00a575f902575a896e3b26412c3f3d1
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 if (
135 IsProtocol("stack") ||
136 IsProtocol("virtualpath") ||
137 IsProtocol("multipath") ||
138 IsProtocol("special") ||
139 IsProtocol("resource")
142 SetFileName(std::move(strURL).substr(iPos));
143 return;
146 if (IsProtocol("udf") || IsProtocol("iso9660"))
148 std::string lower(strURL);
149 StringUtils::ToLower(lower);
150 size_t isoPos = lower.find(".iso\\", iPos);
151 if (isoPos == std::string::npos)
152 isoPos = lower.find(".udf\\", iPos);
153 if (isoPos != std::string::npos)
155 strURL.replace(isoPos + 4, 1, "/");
159 // check for username/password - should occur before first /
160 if (iPos == std::string::npos) iPos = 0;
162 // for protocols supporting options, chop that part off here
163 // maybe we should invert this list instead?
164 size_t iEnd = strURL.length();
165 const char* sep = NULL;
167 //! @todo fix all Addon paths
168 std::string strProtocol2 = GetTranslatedProtocol();
169 if(IsProtocol("rss") ||
170 IsProtocol("rsss") ||
171 IsProtocol("rar") ||
172 IsProtocol("apk") ||
173 IsProtocol("xbt") ||
174 IsProtocol("zip") ||
175 IsProtocol("addons") ||
176 IsProtocol("image") ||
177 IsProtocol("videodb") ||
178 IsProtocol("musicdb") ||
179 IsProtocol("androidapp") ||
180 IsProtocol("pvr"))
181 sep = "?";
182 else
183 if( IsProtocolEqual(strProtocol2, "http")
184 || IsProtocolEqual(strProtocol2, "https")
185 || IsProtocolEqual(strProtocol2, "plugin")
186 || IsProtocolEqual(strProtocol2, "addons")
187 || IsProtocolEqual(strProtocol2, "rtsp"))
188 sep = "?;#|";
189 else if(IsProtocolEqual(strProtocol2, "ftp")
190 || IsProtocolEqual(strProtocol2, "ftps"))
191 sep = "?;|";
193 if(sep)
195 size_t iOptions = strURL.find_first_of(sep, iPos);
196 if (iOptions != std::string::npos)
198 // we keep the initial char as it can be any of the above
199 size_t iProto = strURL.find_first_of('|', iOptions);
200 if (iProto != std::string::npos)
202 SetProtocolOptions(strURL.substr(iProto+1));
203 SetOptions(strURL.substr(iOptions,iProto-iOptions));
205 else
206 SetOptions(strURL.substr(iOptions));
207 iEnd = iOptions;
211 size_t iSlash = strURL.find('/', iPos);
212 if(iSlash >= iEnd)
213 iSlash = std::string::npos; // was an invalid slash as it was contained in options
215 // also skip parsing username:password@ for udp/rtp as it not valid
216 // and conflicts with the following example: rtp://sourceip@multicastip
217 size_t iAlphaSign = strURL.find('@', iPos);
218 if (iAlphaSign != std::string::npos && iAlphaSign < iEnd &&
219 (iAlphaSign < iSlash || iSlash == std::string::npos) &&
220 !IsProtocol("udp") && !IsProtocol("rtp"))
222 // username/password found
223 std::string strUserNamePassword = strURL.substr(iPos, iAlphaSign - iPos);
225 // first extract domain, if protocol is smb
226 if (IsProtocol("smb"))
228 size_t iSemiColon = strUserNamePassword.find(';');
230 if (iSemiColon != std::string::npos)
232 m_strDomain = strUserNamePassword.substr(0, iSemiColon);
233 strUserNamePassword.erase(0, iSemiColon + 1);
237 // username:password
238 size_t iColon = strUserNamePassword.find(':');
239 if (iColon != std::string::npos)
241 m_strUserName = strUserNamePassword.substr(0, iColon);
242 m_strPassword = strUserNamePassword.substr(iColon + 1);
244 // username
245 else
247 m_strUserName = std::move(strUserNamePassword);
250 iPos = iAlphaSign + 1;
251 iSlash = strURL.find('/', iAlphaSign);
253 if (iSlash >= iEnd)
254 iSlash = std::string::npos;
257 std::string strHostNameAndPort = strURL.substr(iPos, (iSlash == std::string::npos) ? iEnd - iPos : iSlash - iPos);
258 // check for IPv6 numerical representation inside [].
259 // if [] found, let's store string inside as hostname
260 // and remove that parsed part from strHostNameAndPort
261 size_t iBrk = strHostNameAndPort.rfind(']');
262 if (iBrk != std::string::npos && strHostNameAndPort.find('[') == 0)
264 m_strHostName = strHostNameAndPort.substr(1, iBrk-1);
265 strHostNameAndPort.erase(0, iBrk+1);
268 // detect hostname:port/ or just :port/ if previous step found [IPv6] format
269 size_t iColon = strHostNameAndPort.rfind(':');
270 if (iColon != std::string::npos && iColon == strHostNameAndPort.find(':'))
272 if (m_strHostName.empty())
273 m_strHostName = strHostNameAndPort.substr(0, iColon);
274 m_iPort = atoi(strHostNameAndPort.substr(iColon + 1).c_str());
277 // if we still don't have hostname, the strHostNameAndPort substring
278 // is 'just' hostname without :port specification - so use it as is.
279 if (m_strHostName.empty())
280 m_strHostName = std::move(strHostNameAndPort);
282 if (iSlash != std::string::npos)
284 iPos = iSlash + 1;
285 if (iEnd > iPos)
286 m_strFileName = strURL.substr(iPos, iEnd - iPos);
289 if (IsProtocol("musicdb") || IsProtocol("videodb") || IsProtocol("sources") || IsProtocol("pvr"))
291 if (m_strHostName != "" && m_strFileName != "")
293 m_strFileName = StringUtils::Format("{}/{}", m_strHostName, m_strFileName);
294 m_strHostName = "";
296 else
298 if (!m_strHostName.empty() && strURL[iEnd-1]=='/')
299 m_strFileName = m_strHostName + "/";
300 else
301 m_strFileName = m_strHostName;
302 m_strHostName = "";
306 StringUtils::Replace(m_strFileName, '\\', '/');
308 /* update extension + sharename */
309 SetFileName(m_strFileName);
311 /* decode urlencoding on this stuff */
312 if(URIUtils::HasEncodedHostname(*this))
314 m_strHostName = Decode(m_strHostName);
315 SetHostName(m_strHostName);
318 m_strUserName = Decode(m_strUserName);
319 m_strPassword = Decode(m_strPassword);
322 void CURL::SetFileName(std::string strFileName)
324 m_strFileName = std::move(strFileName);
326 size_t slash = m_strFileName.find_last_of(GetDirectorySeparator());
327 size_t period = m_strFileName.find_last_of('.');
328 if(period != std::string::npos && (slash == std::string::npos || period > slash))
329 m_strFileType = m_strFileName.substr(period+1);
330 else
331 m_strFileType = "";
333 slash = m_strFileName.find_first_of(GetDirectorySeparator());
334 if(slash == std::string::npos)
335 m_strShareName = m_strFileName;
336 else
337 m_strShareName = m_strFileName.substr(0, slash);
339 StringUtils::Trim(m_strFileType);
340 StringUtils::ToLower(m_strFileType);
343 void CURL::SetProtocol(std::string strProtocol)
345 m_strProtocol = std::move(strProtocol);
346 StringUtils::ToLower(m_strProtocol);
349 void CURL::SetOptions(std::string strOptions)
351 m_strOptions.clear();
352 m_options.Clear();
353 if( strOptions.length() > 0)
355 if(strOptions[0] == '?' ||
356 strOptions[0] == '#' ||
357 strOptions[0] == ';' ||
358 strOptions.find("xml") != std::string::npos)
360 m_strOptions = std::move(strOptions);
361 m_options.AddOptions(m_strOptions);
363 else
364 CLog::Log(LOGWARNING, "{} - Invalid options specified for url {}", __FUNCTION__, strOptions);
368 void CURL::SetProtocolOptions(std::string strOptions)
370 m_strProtocolOptions.clear();
371 m_protocolOptions.Clear();
372 if (strOptions.length() > 0)
374 if (strOptions[0] == '|')
375 m_strProtocolOptions = std::move(strOptions).substr(1);
376 else
377 m_strProtocolOptions = std::move(strOptions);
378 m_protocolOptions.AddOptions(m_strProtocolOptions);
382 std::string CURL::GetTranslatedProtocol() const
384 if (IsProtocol("shout")
385 || IsProtocol("dav")
386 || IsProtocol("rss"))
387 return "http";
389 if (IsProtocol("davs")
390 || IsProtocol("rsss"))
391 return "https";
393 return GetProtocol();
396 std::string CURL::GetFileNameWithoutPath() const
398 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
399 if ((IsProtocol("rar") ||
400 IsProtocol("zip") ||
401 IsProtocol("xbt") ||
402 IsProtocol("apk")) &&
403 m_strFileName.empty())
404 return URIUtils::GetFileName(m_strHostName);
406 // otherwise, we've already got the filepath, so just grab the filename portion
407 std::string file(m_strFileName);
408 URIUtils::RemoveSlashAtEnd(file);
409 return URIUtils::GetFileName(file);
412 inline
413 void protectIPv6(std::string &hn)
415 if (!hn.empty() && hn.find(':') != hn.rfind(':') && hn.find(':') != std::string::npos)
417 hn = '[' + hn + ']';
421 char CURL::GetDirectorySeparator() const
423 #ifndef TARGET_POSIX
424 //We don't want to use IsLocal here, it can return true
425 //for network protocols that matches localhost or hostname
426 //we only ever want to use \ for win32 local filesystem
427 if ( m_strProtocol.empty() )
428 return '\\';
429 else
430 #endif
431 return '/';
434 std::string CURL::Get() const
436 if (m_strProtocol.empty())
437 return m_strFileName;
439 unsigned int sizeneed = m_strProtocol.length()
440 + m_strDomain.length()
441 + m_strUserName.length()
442 + m_strPassword.length()
443 + m_strHostName.length()
444 + m_strFileName.length()
445 + m_strOptions.length()
446 + m_strProtocolOptions.length()
447 + 10;
449 std::string strURL;
450 strURL.reserve(sizeneed);
452 strURL = GetWithoutOptions();
454 if( !m_strOptions.empty() )
455 strURL += m_strOptions;
457 if (!m_strProtocolOptions.empty())
458 strURL += "|"+m_strProtocolOptions;
460 return strURL;
463 std::string CURL::GetWithoutOptions() const
465 if (m_strProtocol.empty())
466 return m_strFileName;
468 std::string strGet = GetWithoutFilename();
470 // Prevent double slash when concatenating host part and filename part
471 if (m_strFileName.size() && (m_strFileName[0] == '/' || m_strFileName[0] == '\\') && URIUtils::HasSlashAtEnd(strGet))
472 URIUtils::RemoveSlashAtEnd(strGet);
474 return strGet + m_strFileName;
477 std::string CURL::GetWithoutUserDetails(bool redact) const
479 std::string strURL;
481 if (IsProtocol("stack"))
483 CFileItemList items;
484 XFILE::CStackDirectory dir;
485 dir.GetDirectory(*this,items);
486 std::vector<std::string> newItems;
487 for (int i=0;i<items.Size();++i)
489 CURL url(items[i]->GetPath());
490 items[i]->SetPath(url.GetWithoutUserDetails(redact));
491 newItems.push_back(items[i]->GetPath());
493 dir.ConstructStackPath(newItems, strURL);
494 return strURL;
497 unsigned int sizeneed = m_strProtocol.length()
498 + m_strHostName.length()
499 + m_strFileName.length()
500 + m_strOptions.length()
501 + m_strProtocolOptions.length()
502 + 10;
504 if (redact && !m_strUserName.empty())
506 sizeneed += sizeof("USERNAME");
507 if (!m_strPassword.empty())
508 sizeneed += sizeof(":PASSWORD@");
509 if (!m_strDomain.empty())
510 sizeneed += sizeof("DOMAIN;");
513 strURL.reserve(sizeneed);
515 if (m_strProtocol.empty())
516 return m_strFileName;
518 strURL = m_strProtocol;
519 strURL += "://";
521 if (redact && !m_strUserName.empty())
523 if (!m_strDomain.empty())
524 strURL += "DOMAIN;";
525 strURL += "USERNAME";
526 if (!m_strPassword.empty())
527 strURL += ":PASSWORD";
528 strURL += "@";
531 if (!m_strHostName.empty())
533 std::string strHostName;
535 if (URIUtils::HasParentInHostname(*this))
536 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
537 else
538 strHostName = m_strHostName;
540 if (URIUtils::HasEncodedHostname(*this))
541 strHostName = Encode(strHostName);
543 if ( HasPort() )
545 protectIPv6(strHostName);
546 strURL += strHostName + StringUtils::Format(":{}", m_iPort);
548 else
549 strURL += strHostName;
551 strURL += "/";
553 strURL += m_strFileName;
555 if( m_strOptions.length() > 0 )
556 strURL += m_strOptions;
557 if( m_strProtocolOptions.length() > 0 )
558 strURL += "|"+m_strProtocolOptions;
560 return strURL;
563 std::string CURL::GetWithoutFilename() const
565 if (m_strProtocol.empty())
566 return "";
568 unsigned int sizeneed = m_strProtocol.length()
569 + m_strDomain.length()
570 + m_strUserName.length()
571 + m_strPassword.length()
572 + m_strHostName.length()
573 + 10;
575 std::string strURL;
576 strURL.reserve(sizeneed);
578 strURL = m_strProtocol;
579 strURL += "://";
581 if (!m_strUserName.empty())
583 if (!m_strDomain.empty())
585 strURL += Encode(m_strDomain);
586 strURL += ";";
588 strURL += Encode(m_strUserName);
589 if (!m_strPassword.empty())
591 strURL += ":";
592 strURL += Encode(m_strPassword);
594 strURL += "@";
597 if (!m_strHostName.empty())
599 std::string hostname;
601 if( URIUtils::HasEncodedHostname(*this) )
602 hostname = Encode(m_strHostName);
603 else
604 hostname = m_strHostName;
606 if (HasPort())
608 protectIPv6(hostname);
609 strURL += hostname + StringUtils::Format(":{}", m_iPort);
611 else
612 strURL += hostname;
614 strURL += "/";
617 return strURL;
620 std::string CURL::GetRedacted() const
622 return GetWithoutUserDetails(true);
625 std::string CURL::GetRedacted(std::string path)
627 return CURL(std::move(path)).GetRedacted();
630 bool CURL::IsLocal() const
632 return (m_strProtocol.empty() || IsLocalHost() || IsProtocol("win-lib"));
635 bool CURL::IsLocalHost() const
637 return CServiceBroker::GetNetwork().IsLocalHost(m_strHostName);
640 bool CURL::IsFileOnly(const std::string &url)
642 return url.find_first_of("/\\") == std::string::npos;
645 bool CURL::IsFullPath(const std::string &url)
647 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
648 if (url.find("://") != std::string::npos) return true; // foo://bar.ext
649 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
650 if (StringUtils::StartsWith(url, "\\\\")) return true; // \\UNC\path\to\file
651 return false;
654 std::string CURL::Decode(std::string_view strURLData)
655 //modified to be more accommodating - if a non hex value follows a % take the characters directly and don't raise an error.
656 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
658 std::string strResult;
660 /* result will always be less than source */
661 strResult.reserve( strURLData.length() );
663 const char* const iterEnd = strURLData.data() + strURLData.size();
664 for (const char* iter = strURLData.data(); iter < iterEnd; ++iter)
666 if (*iter == '+')
667 strResult += ' ';
668 else if (*iter == '%')
670 if (std::distance(iter, iterEnd) >= 3)
672 uint8_t dec_num{};
673 const std::from_chars_result res = std::from_chars(iter + 1, iter + 3, dec_num, 16);
674 if (res.ec != std::errc() || res.ptr != iter + 3)
675 strResult += *iter;
676 else
678 strResult += (char)dec_num;
679 iter += 2;
682 else
683 strResult += *iter;
685 else
686 strResult += *iter;
689 return strResult;
692 std::string CURL::Encode(std::string_view strURLData)
694 std::string strResult;
696 /* wonder what a good value is here is, depends on how often it occurs */
697 strResult.reserve( strURLData.length() * 2 );
699 for (auto kar : strURLData)
701 // Don't URL encode "-_.!()" according to RFC1738
702 //! @todo Update it to "-_.~" after Gotham according to RFC3986
703 if (StringUtils::isasciialphanum(kar) || kar == '-' || kar == '.' || kar == '_' || kar == '!' || kar == '(' || kar == ')')
704 strResult.push_back(kar);
705 else
706 fmt::format_to(std::back_insert_iterator(strResult), "%{:02x}",
707 (unsigned int)((unsigned char)kar));
710 return strResult;
713 bool CURL::IsProtocolEqual(const std::string &protocol, const char *type)
716 NOTE: We're currently using == here as m_strProtocol is assigned as lower-case in SetProtocol(),
717 and we've assumed all other callers are calling with protocol lower-case otherwise.
718 We possibly shouldn't do this (as CURL(foo).Get() != foo, though there are other reasons for this as well)
719 but it handles the requirements of RFC-1738 which allows the scheme to be case-insensitive.
721 if (type)
722 return protocol == type;
723 return false;
726 void CURL::GetOptions(std::map<std::string, std::string> &options) const
728 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
729 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin();
730 option != optionsMap.end(); ++option)
731 options[option->first] = option->second.asString();
734 bool CURL::HasOption(const std::string &key) const
736 return m_options.HasOption(key);
739 bool CURL::GetOption(const std::string &key, std::string &value) const
741 CVariant valueObj;
742 if (!m_options.GetOption(key, valueObj))
743 return false;
745 value = valueObj.asString();
746 return true;
749 std::string CURL::GetOption(const std::string &key) const
751 std::string value;
752 if (!GetOption(key, value))
753 return "";
755 return value;
758 void CURL::SetOption(const std::string &key, const std::string &value)
760 m_options.AddOption(key, value);
761 SetOptions(m_options.GetOptionsString(true));
764 void CURL::RemoveOption(const std::string &key)
766 m_options.RemoveOption(key);
767 SetOptions(m_options.GetOptionsString(true));
770 void CURL::GetProtocolOptions(std::map<std::string, std::string> &options) const
772 CUrlOptions::UrlOptions optionsMap = m_protocolOptions.GetOptions();
773 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin();
774 option != optionsMap.end(); ++option)
775 options[option->first] = option->second.asString();
778 bool CURL::HasProtocolOption(const std::string &key) const
780 return m_protocolOptions.HasOption(key);
783 bool CURL::GetProtocolOption(const std::string &key, std::string &value) const
785 CVariant valueObj;
786 if (!m_protocolOptions.GetOption(key, valueObj))
787 return false;
789 value = valueObj.asString();
790 return true;
793 std::string CURL::GetProtocolOption(const std::string &key) const
795 std::string value;
796 if (!GetProtocolOption(key, value))
797 return "";
799 return value;
802 void CURL::SetProtocolOption(const std::string &key, const std::string &value)
804 m_protocolOptions.AddOption(key, value);
805 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);
808 void CURL::RemoveProtocolOption(const std::string &key)
810 m_protocolOptions.RemoveOption(key);
811 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);