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.
10 #include "utils/log.h"
11 #include "utils/URIUtils.h"
12 #include "utils/StringUtils.h"
14 #include "filesystem/File.h"
16 #include "filesystem/StackDirectory.h"
17 #include "network/Network.h"
18 #include "ServiceBroker.h"
26 using namespace ADDON
;
28 CURL::~CURL() = default;
32 m_strHostName
.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();
41 m_strProtocolOptions
.clear();
43 m_protocolOptions
.Clear();
47 void CURL::Parse(const std::string
& strURL1
)
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
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
74 bool is_apk
= (strURL
.find(".apk/", iPos
) != std::string::npos
);
78 iPos
= strURL
.find(".apk/", iPos
);
80 iPos
= strURL
.find(".zip/", iPos
);
83 if (iPos
== std::string::npos
)
85 /* set filename and update extension*/
90 std::string archiveName
= strURL
.substr(0, iPos
);
92 if (XFILE::CFile::Stat(archiveName
, &s
) == 0)
95 if (!S_ISDIR(s
.st_mode
))
97 if (!(s
.st_mode
& S_IFDIR
))
100 archiveName
= Encode(archiveName
);
103 CURL
c("apk://" + archiveName
+ "/" + strURL
.substr(iPos
+ 1));
108 CURL
c("zip://" + archiveName
+ "/" + strURL
.substr(iPos
+ 1));
118 SetProtocol(strURL
.substr(0, iPos
));
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.
128 IsProtocol("stack") ||
129 IsProtocol("virtualpath") ||
130 IsProtocol("multipath") ||
131 IsProtocol("special") ||
132 IsProtocol("resource")
135 SetFileName(strURL
.substr(iPos
));
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") ||
168 IsProtocol("addons") ||
169 IsProtocol("image") ||
170 IsProtocol("videodb") ||
171 IsProtocol("musicdb") ||
172 IsProtocol("androidapp") ||
176 if( IsProtocolEqual(strProtocol2
, "http")
177 || IsProtocolEqual(strProtocol2
, "https")
178 || IsProtocolEqual(strProtocol2
, "plugin")
179 || IsProtocolEqual(strProtocol2
, "addons")
180 || IsProtocolEqual(strProtocol2
, "rtsp"))
182 else if(IsProtocolEqual(strProtocol2
, "ftp")
183 || IsProtocolEqual(strProtocol2
, "ftps"))
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
));
199 SetOptions(strURL
.substr(iOptions
));
204 size_t iSlash
= strURL
.find('/', iPos
);
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);
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);
240 m_strUserName
= strUserNamePassword
;
243 iPos
= iAlphaSign
+ 1;
244 iSlash
= strURL
.find('/', iAlphaSign
);
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
)
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
);
291 if (!m_strHostName
.empty() && strURL
[iEnd
-1]=='/')
292 m_strFileName
= m_strHostName
+ "/";
294 m_strFileName
= 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);
326 slash
= m_strFileName
.find_first_of(GetDirectorySeparator());
327 if(slash
== std::string::npos
)
328 m_strShareName
= m_strFileName
;
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();
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
);
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);
370 m_strProtocolOptions
= strOptions
;
371 m_protocolOptions
.AddOptions(m_strProtocolOptions
);
375 const std::string
CURL::GetTranslatedProtocol() const
377 if (IsProtocol("shout")
379 || IsProtocol("rss"))
382 if (IsProtocol("davs")
383 || IsProtocol("rsss"))
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") ||
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
);
406 void protectIPv6(std::string
&hn
)
408 if (!hn
.empty() && hn
.find(':') != hn
.rfind(':') && hn
.find(':') != std::string::npos
)
414 char CURL::GetDirectorySeparator() const
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() )
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()
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
;
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
474 if (IsProtocol("stack"))
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
);
490 unsigned int sizeneed
= m_strProtocol
.length()
491 + m_strHostName
.length()
492 + m_strFileName
.length()
493 + m_strOptions
.length()
494 + m_strProtocolOptions
.length()
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
;
514 if (redact
&& !m_strUserName
.empty())
516 if (!m_strDomain
.empty())
518 strURL
+= "USERNAME";
519 if (!m_strPassword
.empty())
520 strURL
+= ":PASSWORD";
524 if (!m_strHostName
.empty())
526 std::string strHostName
;
528 if (URIUtils::HasParentInHostname(*this))
529 strHostName
= CURL(m_strHostName
).GetWithoutUserDetails();
531 strHostName
= m_strHostName
;
533 if (URIUtils::HasEncodedHostname(*this))
534 strHostName
= Encode(strHostName
);
538 protectIPv6(strHostName
);
539 strURL
+= strHostName
+ StringUtils::Format(":{}", m_iPort
);
542 strURL
+= strHostName
;
546 strURL
+= m_strFileName
;
548 if( m_strOptions
.length() > 0 )
549 strURL
+= m_strOptions
;
550 if( m_strProtocolOptions
.length() > 0 )
551 strURL
+= "|"+m_strProtocolOptions
;
556 std::string
CURL::GetWithoutFilename() const
558 if (m_strProtocol
.empty())
561 unsigned int sizeneed
= m_strProtocol
.length()
562 + m_strDomain
.length()
563 + m_strUserName
.length()
564 + m_strPassword
.length()
565 + m_strHostName
.length()
569 strURL
.reserve(sizeneed
);
571 strURL
= m_strProtocol
;
574 if (!m_strUserName
.empty())
576 if (!m_strDomain
.empty())
578 strURL
+= Encode(m_strDomain
);
581 strURL
+= Encode(m_strUserName
);
582 if (!m_strPassword
.empty())
585 strURL
+= Encode(m_strPassword
);
590 if (!m_strHostName
.empty())
592 std::string hostname
;
594 if( URIUtils::HasEncodedHostname(*this) )
595 hostname
= Encode(m_strHostName
);
597 hostname
= m_strHostName
;
601 protectIPv6(hostname
);
602 strURL
+= hostname
+ StringUtils::Format(":{}", m_iPort
);
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
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
+= ' ';
662 if (i
< strURLData
.size() - 2)
665 strTmp
.assign(strURLData
.substr(i
+ 1, 2));
667 sscanf(strTmp
.c_str(), "%x", (unsigned int *)&dec_num
);
668 if (dec_num
<0 || dec_num
>255)
672 strResult
+= (char)dec_num
;
679 else strResult
+= kar
;
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
);
701 strResult
+= StringUtils::Format("%{:02x}", (unsigned int)((unsigned char)kar
));
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.
716 return protocol
== type
;
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
735 if (!m_options
.GetOption(key
, valueObj
))
738 value
= valueObj
.asString();
742 std::string
CURL::GetOption(const std::string
&key
) const
745 if (!GetOption(key
, 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
778 if (!m_protocolOptions
.GetOption(key
, valueObj
))
781 value
= valueObj
.asString();
785 std::string
CURL::GetProtocolOption(const std::string
&key
) const
788 if (!GetProtocolOption(key
, 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);