[PVR][Estuary] Timer settings dialog: Show client name in timer type selection dialog...
[xbmc.git] / xbmc / URL.cpp
blobf4a66d0e802ed9dc809e3e0d06445a489e3f5415
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"
10 #include "utils/log.h"
11 #include "utils/URIUtils.h"
12 #include "utils/StringUtils.h"
13 #include "Util.h"
14 #include "filesystem/File.h"
15 #include "FileItem.h"
16 #include "filesystem/StackDirectory.h"
17 #include "network/Network.h"
18 #include "ServiceBroker.h"
19 #ifndef TARGET_POSIX
20 #include <sys\stat.h>
21 #endif
23 #include <string>
24 #include <vector>
26 using namespace ADDON;
28 CURL::~CURL() = default;
30 void CURL::Reset()
32 m_strHostName.clear();
33 m_strDomain.clear();
34 m_strUserName.clear();
35 m_strPassword.clear();
36 m_strShareName.clear();
37 m_strFileName.clear();
38 m_strProtocol.clear();
39 m_strFileType.clear();
40 m_strOptions.clear();
41 m_strProtocolOptions.clear();
42 m_options.Clear();
43 m_protocolOptions.Clear();
44 m_iPort = 0;
47 void CURL::Parse(const std::string& strURL1)
49 Reset();
50 // start by validating the path
51 std::string strURL = CUtil::ValidatePath(strURL1);
53 // strURL can be one of the following:
54 // format 1: protocol://[username:password]@hostname[:port]/directoryandfile
55 // format 2: protocol://file
56 // format 3: drive:directoryandfile
58 // first need 2 check if this is a protocol or just a normal drive & path
59 if (!strURL.size()) return ;
60 if (strURL == "?") return;
62 // form is format 1 or 2
63 // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
64 // format 2: protocol://file
66 // decode protocol
67 size_t iPos = strURL.find("://");
68 if (iPos == std::string::npos)
70 // This is an ugly hack that needs some work.
71 // example: filename /foo/bar.zip/alice.rar/bob.avi
72 // This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
73 iPos = 0;
74 bool is_apk = (strURL.find(".apk/", iPos) != std::string::npos);
75 while (true)
77 if (is_apk)
78 iPos = strURL.find(".apk/", iPos);
79 else
80 iPos = strURL.find(".zip/", iPos);
82 int extLen = 3;
83 if (iPos == std::string::npos)
85 /* set filename and update extension*/
86 SetFileName(strURL);
87 return ;
89 iPos += extLen + 1;
90 std::string archiveName = strURL.substr(0, iPos);
91 struct __stat64 s;
92 if (XFILE::CFile::Stat(archiveName, &s) == 0)
94 #ifdef TARGET_POSIX
95 if (!S_ISDIR(s.st_mode))
96 #else
97 if (!(s.st_mode & S_IFDIR))
98 #endif
100 archiveName = Encode(archiveName);
101 if (is_apk)
103 CURL c("apk://" + archiveName + "/" + strURL.substr(iPos + 1));
104 *this = c;
106 else
108 CURL c("zip://" + archiveName + "/" + strURL.substr(iPos + 1));
109 *this = c;
111 return;
116 else
118 SetProtocol(strURL.substr(0, iPos));
119 iPos += 3;
122 // virtual protocols
123 // why not handle all format 2 (protocol://file) style urls here?
124 // ones that come to mind are iso9660, cdda, musicdb, etc.
125 // they are all local protocols and have no server part, port number, special options, etc.
126 // this removes the need for special handling below.
127 if (
128 IsProtocol("stack") ||
129 IsProtocol("virtualpath") ||
130 IsProtocol("multipath") ||
131 IsProtocol("special") ||
132 IsProtocol("resource")
135 SetFileName(strURL.substr(iPos));
136 return;
139 if (IsProtocol("udf") || IsProtocol("iso9660"))
141 std::string lower(strURL);
142 StringUtils::ToLower(lower);
143 size_t isoPos = lower.find(".iso\\", iPos);
144 if (isoPos == std::string::npos)
145 isoPos = lower.find(".udf\\", iPos);
146 if (isoPos != std::string::npos)
148 strURL = strURL.replace(isoPos + 4, 1, "/");
152 // check for username/password - should occur before first /
153 if (iPos == std::string::npos) iPos = 0;
155 // for protocols supporting options, chop that part off here
156 // maybe we should invert this list instead?
157 size_t iEnd = strURL.length();
158 const char* sep = NULL;
160 //! @todo fix all Addon paths
161 std::string strProtocol2 = GetTranslatedProtocol();
162 if(IsProtocol("rss") ||
163 IsProtocol("rsss") ||
164 IsProtocol("rar") ||
165 IsProtocol("apk") ||
166 IsProtocol("xbt") ||
167 IsProtocol("zip") ||
168 IsProtocol("addons") ||
169 IsProtocol("image") ||
170 IsProtocol("videodb") ||
171 IsProtocol("musicdb") ||
172 IsProtocol("androidapp") ||
173 IsProtocol("pvr"))
174 sep = "?";
175 else
176 if( IsProtocolEqual(strProtocol2, "http")
177 || IsProtocolEqual(strProtocol2, "https")
178 || IsProtocolEqual(strProtocol2, "plugin")
179 || IsProtocolEqual(strProtocol2, "addons")
180 || IsProtocolEqual(strProtocol2, "rtsp"))
181 sep = "?;#|";
182 else if(IsProtocolEqual(strProtocol2, "ftp")
183 || IsProtocolEqual(strProtocol2, "ftps"))
184 sep = "?;|";
186 if(sep)
188 size_t iOptions = strURL.find_first_of(sep, iPos);
189 if (iOptions != std::string::npos)
191 // we keep the initial char as it can be any of the above
192 size_t iProto = strURL.find_first_of('|', iOptions);
193 if (iProto != std::string::npos)
195 SetProtocolOptions(strURL.substr(iProto+1));
196 SetOptions(strURL.substr(iOptions,iProto-iOptions));
198 else
199 SetOptions(strURL.substr(iOptions));
200 iEnd = iOptions;
204 size_t iSlash = strURL.find('/', iPos);
205 if(iSlash >= iEnd)
206 iSlash = std::string::npos; // was an invalid slash as it was contained in options
208 // also skip parsing username:password@ for udp/rtp as it not valid
209 // and conflicts with the following example: rtp://sourceip@multicastip
210 size_t iAlphaSign = strURL.find('@', iPos);
211 if (iAlphaSign != std::string::npos && iAlphaSign < iEnd &&
212 (iAlphaSign < iSlash || iSlash == std::string::npos) &&
213 !IsProtocol("udp") && !IsProtocol("rtp"))
215 // username/password found
216 std::string strUserNamePassword = strURL.substr(iPos, iAlphaSign - iPos);
218 // first extract domain, if protocol is smb
219 if (IsProtocol("smb"))
221 size_t iSemiColon = strUserNamePassword.find(';');
223 if (iSemiColon != std::string::npos)
225 m_strDomain = strUserNamePassword.substr(0, iSemiColon);
226 strUserNamePassword.erase(0, iSemiColon + 1);
230 // username:password
231 size_t iColon = strUserNamePassword.find(':');
232 if (iColon != std::string::npos)
234 m_strUserName = strUserNamePassword.substr(0, iColon);
235 m_strPassword = strUserNamePassword.substr(iColon + 1);
237 // username
238 else
240 m_strUserName = strUserNamePassword;
243 iPos = iAlphaSign + 1;
244 iSlash = strURL.find('/', iAlphaSign);
246 if (iSlash >= iEnd)
247 iSlash = std::string::npos;
250 std::string strHostNameAndPort = strURL.substr(iPos, (iSlash == std::string::npos) ? iEnd - iPos : iSlash - iPos);
251 // check for IPv6 numerical representation inside [].
252 // if [] found, let's store string inside as hostname
253 // and remove that parsed part from strHostNameAndPort
254 size_t iBrk = strHostNameAndPort.rfind(']');
255 if (iBrk != std::string::npos && strHostNameAndPort.find('[') == 0)
257 m_strHostName = strHostNameAndPort.substr(1, iBrk-1);
258 strHostNameAndPort.erase(0, iBrk+1);
261 // detect hostname:port/ or just :port/ if previous step found [IPv6] format
262 size_t iColon = strHostNameAndPort.rfind(':');
263 if (iColon != std::string::npos && iColon == strHostNameAndPort.find(':'))
265 if (m_strHostName.empty())
266 m_strHostName = strHostNameAndPort.substr(0, iColon);
267 m_iPort = atoi(strHostNameAndPort.substr(iColon + 1).c_str());
270 // if we still don't have hostname, the strHostNameAndPort substring
271 // is 'just' hostname without :port specification - so use it as is.
272 if (m_strHostName.empty())
273 m_strHostName = strHostNameAndPort;
275 if (iSlash != std::string::npos)
277 iPos = iSlash + 1;
278 if (iEnd > iPos)
279 m_strFileName = strURL.substr(iPos, iEnd - iPos);
282 if (IsProtocol("musicdb") || IsProtocol("videodb") || IsProtocol("sources") || IsProtocol("pvr"))
284 if (m_strHostName != "" && m_strFileName != "")
286 m_strFileName = StringUtils::Format("{}/{}", m_strHostName, m_strFileName);
287 m_strHostName = "";
289 else
291 if (!m_strHostName.empty() && strURL[iEnd-1]=='/')
292 m_strFileName = m_strHostName + "/";
293 else
294 m_strFileName = m_strHostName;
295 m_strHostName = "";
299 StringUtils::Replace(m_strFileName, '\\', '/');
301 /* update extension + sharename */
302 SetFileName(m_strFileName);
304 /* decode urlencoding on this stuff */
305 if(URIUtils::HasEncodedHostname(*this))
307 m_strHostName = Decode(m_strHostName);
308 SetHostName(m_strHostName);
311 m_strUserName = Decode(m_strUserName);
312 m_strPassword = Decode(m_strPassword);
315 void CURL::SetFileName(const std::string& strFileName)
317 m_strFileName = strFileName;
319 size_t slash = m_strFileName.find_last_of(GetDirectorySeparator());
320 size_t period = m_strFileName.find_last_of('.');
321 if(period != std::string::npos && (slash == std::string::npos || period > slash))
322 m_strFileType = m_strFileName.substr(period+1);
323 else
324 m_strFileType = "";
326 slash = m_strFileName.find_first_of(GetDirectorySeparator());
327 if(slash == std::string::npos)
328 m_strShareName = m_strFileName;
329 else
330 m_strShareName = m_strFileName.substr(0, slash);
332 StringUtils::Trim(m_strFileType);
333 StringUtils::ToLower(m_strFileType);
336 void CURL::SetProtocol(const std::string& strProtocol)
338 m_strProtocol = strProtocol;
339 StringUtils::ToLower(m_strProtocol);
342 void CURL::SetOptions(const std::string& strOptions)
344 m_strOptions.clear();
345 m_options.Clear();
346 if( strOptions.length() > 0)
348 if(strOptions[0] == '?' ||
349 strOptions[0] == '#' ||
350 strOptions[0] == ';' ||
351 strOptions.find("xml") != std::string::npos)
353 m_strOptions = strOptions;
354 m_options.AddOptions(m_strOptions);
356 else
357 CLog::Log(LOGWARNING, "{} - Invalid options specified for url {}", __FUNCTION__, strOptions);
361 void CURL::SetProtocolOptions(const std::string& strOptions)
363 m_strProtocolOptions.clear();
364 m_protocolOptions.Clear();
365 if (strOptions.length() > 0)
367 if (strOptions[0] == '|')
368 m_strProtocolOptions = strOptions.substr(1);
369 else
370 m_strProtocolOptions = strOptions;
371 m_protocolOptions.AddOptions(m_strProtocolOptions);
375 const std::string CURL::GetTranslatedProtocol() const
377 if (IsProtocol("shout")
378 || IsProtocol("dav")
379 || IsProtocol("rss"))
380 return "http";
382 if (IsProtocol("davs")
383 || IsProtocol("rsss"))
384 return "https";
386 return GetProtocol();
389 const std::string CURL::GetFileNameWithoutPath() const
391 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
392 if ((IsProtocol("rar") ||
393 IsProtocol("zip") ||
394 IsProtocol("xbt") ||
395 IsProtocol("apk")) &&
396 m_strFileName.empty())
397 return URIUtils::GetFileName(m_strHostName);
399 // otherwise, we've already got the filepath, so just grab the filename portion
400 std::string file(m_strFileName);
401 URIUtils::RemoveSlashAtEnd(file);
402 return URIUtils::GetFileName(file);
405 inline
406 void protectIPv6(std::string &hn)
408 if (!hn.empty() && hn.find(':') != hn.rfind(':') && hn.find(':') != std::string::npos)
410 hn = '[' + hn + ']';
414 char CURL::GetDirectorySeparator() const
416 #ifndef TARGET_POSIX
417 //We don't want to use IsLocal here, it can return true
418 //for network protocols that matches localhost or hostname
419 //we only ever want to use \ for win32 local filesystem
420 if ( m_strProtocol.empty() )
421 return '\\';
422 else
423 #endif
424 return '/';
427 std::string CURL::Get() const
429 if (m_strProtocol.empty())
430 return m_strFileName;
432 unsigned int sizeneed = m_strProtocol.length()
433 + m_strDomain.length()
434 + m_strUserName.length()
435 + m_strPassword.length()
436 + m_strHostName.length()
437 + m_strFileName.length()
438 + m_strOptions.length()
439 + m_strProtocolOptions.length()
440 + 10;
442 std::string strURL;
443 strURL.reserve(sizeneed);
445 strURL = GetWithoutOptions();
447 if( !m_strOptions.empty() )
448 strURL += m_strOptions;
450 if (!m_strProtocolOptions.empty())
451 strURL += "|"+m_strProtocolOptions;
453 return strURL;
456 std::string CURL::GetWithoutOptions() const
458 if (m_strProtocol.empty())
459 return m_strFileName;
461 std::string strGet = GetWithoutFilename();
463 // Prevent double slash when concatenating host part and filename part
464 if (m_strFileName.size() && (m_strFileName[0] == '/' || m_strFileName[0] == '\\') && URIUtils::HasSlashAtEnd(strGet))
465 URIUtils::RemoveSlashAtEnd(strGet);
467 return strGet + m_strFileName;
470 std::string CURL::GetWithoutUserDetails(bool redact) const
472 std::string strURL;
474 if (IsProtocol("stack"))
476 CFileItemList items;
477 XFILE::CStackDirectory dir;
478 dir.GetDirectory(*this,items);
479 std::vector<std::string> newItems;
480 for (int i=0;i<items.Size();++i)
482 CURL url(items[i]->GetPath());
483 items[i]->SetPath(url.GetWithoutUserDetails(redact));
484 newItems.push_back(items[i]->GetPath());
486 dir.ConstructStackPath(newItems, strURL);
487 return strURL;
490 unsigned int sizeneed = m_strProtocol.length()
491 + m_strHostName.length()
492 + m_strFileName.length()
493 + m_strOptions.length()
494 + m_strProtocolOptions.length()
495 + 10;
497 if (redact && !m_strUserName.empty())
499 sizeneed += sizeof("USERNAME");
500 if (!m_strPassword.empty())
501 sizeneed += sizeof(":PASSWORD@");
502 if (!m_strDomain.empty())
503 sizeneed += sizeof("DOMAIN;");
506 strURL.reserve(sizeneed);
508 if (m_strProtocol.empty())
509 return m_strFileName;
511 strURL = m_strProtocol;
512 strURL += "://";
514 if (redact && !m_strUserName.empty())
516 if (!m_strDomain.empty())
517 strURL += "DOMAIN;";
518 strURL += "USERNAME";
519 if (!m_strPassword.empty())
520 strURL += ":PASSWORD";
521 strURL += "@";
524 if (!m_strHostName.empty())
526 std::string strHostName;
528 if (URIUtils::HasParentInHostname(*this))
529 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
530 else
531 strHostName = m_strHostName;
533 if (URIUtils::HasEncodedHostname(*this))
534 strHostName = Encode(strHostName);
536 if ( HasPort() )
538 protectIPv6(strHostName);
539 strURL += strHostName + StringUtils::Format(":{}", m_iPort);
541 else
542 strURL += strHostName;
544 strURL += "/";
546 strURL += m_strFileName;
548 if( m_strOptions.length() > 0 )
549 strURL += m_strOptions;
550 if( m_strProtocolOptions.length() > 0 )
551 strURL += "|"+m_strProtocolOptions;
553 return strURL;
556 std::string CURL::GetWithoutFilename() const
558 if (m_strProtocol.empty())
559 return "";
561 unsigned int sizeneed = m_strProtocol.length()
562 + m_strDomain.length()
563 + m_strUserName.length()
564 + m_strPassword.length()
565 + m_strHostName.length()
566 + 10;
568 std::string strURL;
569 strURL.reserve(sizeneed);
571 strURL = m_strProtocol;
572 strURL += "://";
574 if (!m_strUserName.empty())
576 if (!m_strDomain.empty())
578 strURL += Encode(m_strDomain);
579 strURL += ";";
581 strURL += Encode(m_strUserName);
582 if (!m_strPassword.empty())
584 strURL += ":";
585 strURL += Encode(m_strPassword);
587 strURL += "@";
590 if (!m_strHostName.empty())
592 std::string hostname;
594 if( URIUtils::HasEncodedHostname(*this) )
595 hostname = Encode(m_strHostName);
596 else
597 hostname = m_strHostName;
599 if (HasPort())
601 protectIPv6(hostname);
602 strURL += hostname + StringUtils::Format(":{}", m_iPort);
604 else
605 strURL += hostname;
607 strURL += "/";
610 return strURL;
613 std::string CURL::GetRedacted() const
615 return GetWithoutUserDetails(true);
618 std::string CURL::GetRedacted(const std::string& path)
620 return CURL(path).GetRedacted();
623 bool CURL::IsLocal() const
625 return (m_strProtocol.empty() || IsLocalHost() || IsProtocol("win-lib"));
628 bool CURL::IsLocalHost() const
630 return CServiceBroker::GetNetwork().IsLocalHost(m_strHostName);
633 bool CURL::IsFileOnly(const std::string &url)
635 return url.find_first_of("/\\") == std::string::npos;
638 bool CURL::IsFullPath(const std::string &url)
640 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
641 if (url.find("://") != std::string::npos) return true; // foo://bar.ext
642 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
643 if (StringUtils::StartsWith(url, "\\\\")) return true; // \\UNC\path\to\file
644 return false;
647 std::string CURL::Decode(const std::string& strURLData)
648 //modified to be more accommodating - if a non hex value follows a % take the characters directly and don't raise an error.
649 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
651 std::string strResult;
653 /* result will always be less than source */
654 strResult.reserve( strURLData.length() );
656 for (unsigned int i = 0; i < strURLData.size(); ++i)
658 int kar = (unsigned char)strURLData[i];
659 if (kar == '+') strResult += ' ';
660 else if (kar == '%')
662 if (i < strURLData.size() - 2)
664 std::string strTmp;
665 strTmp.assign(strURLData.substr(i + 1, 2));
666 int dec_num=-1;
667 sscanf(strTmp.c_str(), "%x", (unsigned int *)&dec_num);
668 if (dec_num<0 || dec_num>255)
669 strResult += kar;
670 else
672 strResult += (char)dec_num;
673 i += 2;
676 else
677 strResult += kar;
679 else strResult += kar;
682 return strResult;
685 std::string CURL::Encode(const std::string& strURLData)
687 std::string strResult;
689 /* wonder what a good value is here is, depends on how often it occurs */
690 strResult.reserve( strURLData.length() * 2 );
692 for (size_t i = 0; i < strURLData.size(); ++i)
694 const char kar = strURLData[i];
696 // Don't URL encode "-_.!()" according to RFC1738
697 //! @todo Update it to "-_.~" after Gotham according to RFC3986
698 if (StringUtils::isasciialphanum(kar) || kar == '-' || kar == '.' || kar == '_' || kar == '!' || kar == '(' || kar == ')')
699 strResult.push_back(kar);
700 else
701 strResult += StringUtils::Format("%{:02x}", (unsigned int)((unsigned char)kar));
704 return strResult;
707 bool CURL::IsProtocolEqual(const std::string &protocol, const char *type)
710 NOTE: We're currently using == here as m_strProtocol is assigned as lower-case in SetProtocol(),
711 and we've assumed all other callers are calling with protocol lower-case otherwise.
712 We possibly shouldn't do this (as CURL(foo).Get() != foo, though there are other reasons for this as well)
713 but it handles the requirements of RFC-1738 which allows the scheme to be case-insensitive.
715 if (type)
716 return protocol == type;
717 return false;
720 void CURL::GetOptions(std::map<std::string, std::string> &options) const
722 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
723 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
724 options[option->first] = option->second.asString();
727 bool CURL::HasOption(const std::string &key) const
729 return m_options.HasOption(key);
732 bool CURL::GetOption(const std::string &key, std::string &value) const
734 CVariant valueObj;
735 if (!m_options.GetOption(key, valueObj))
736 return false;
738 value = valueObj.asString();
739 return true;
742 std::string CURL::GetOption(const std::string &key) const
744 std::string value;
745 if (!GetOption(key, value))
746 return "";
748 return value;
751 void CURL::SetOption(const std::string &key, const std::string &value)
753 m_options.AddOption(key, value);
754 SetOptions(m_options.GetOptionsString(true));
757 void CURL::RemoveOption(const std::string &key)
759 m_options.RemoveOption(key);
760 SetOptions(m_options.GetOptionsString(true));
763 void CURL::GetProtocolOptions(std::map<std::string, std::string> &options) const
765 CUrlOptions::UrlOptions optionsMap = m_protocolOptions.GetOptions();
766 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
767 options[option->first] = option->second.asString();
770 bool CURL::HasProtocolOption(const std::string &key) const
772 return m_protocolOptions.HasOption(key);
775 bool CURL::GetProtocolOption(const std::string &key, std::string &value) const
777 CVariant valueObj;
778 if (!m_protocolOptions.GetOption(key, valueObj))
779 return false;
781 value = valueObj.asString();
782 return true;
785 std::string CURL::GetProtocolOption(const std::string &key) const
787 std::string value;
788 if (!GetProtocolOption(key, value))
789 return "";
791 return value;
794 void CURL::SetProtocolOption(const std::string &key, const std::string &value)
796 m_protocolOptions.AddOption(key, value);
797 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);
800 void CURL::RemoveProtocolOption(const std::string &key)
802 m_protocolOptions.RemoveOption(key);
803 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);