2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "utils/RegExp.h"
23 #include "utils/log.h"
24 #include "utils/URIUtils.h"
25 #include "utils/StringUtils.h"
27 #include "filesystem/File.h"
29 #include "filesystem/StackDirectory.h"
30 #include "addons/Addon.h"
31 #include "utils/StringUtils.h"
33 #include <sys\types.h>
38 using namespace ADDON
;
40 CURL::CURL(const CStdString
& strURL1
)
56 m_strHostName
.clear();
58 m_strUserName
.clear();
59 m_strPassword
.clear();
60 m_strShareName
.clear();
61 m_strFileName
.clear();
62 m_strProtocol
.clear();
63 m_strFileType
.clear();
65 m_strProtocolOptions
.clear();
67 m_protocolOptions
.Clear();
71 void CURL::Parse(const CStdString
& strURL1
)
74 // start by validating the path
75 CStdString strURL
= CUtil::ValidatePath(strURL1
);
77 // strURL can be one of the following:
78 // format 1: protocol://[username:password]@hostname[:port]/directoryandfile
79 // format 2: protocol://file
80 // format 3: drive:directoryandfile
82 // first need 2 check if this is a protocol or just a normal drive & path
83 if (!strURL
.size()) return ;
84 if (strURL
.Equals("?", true)) return;
86 // form is format 1 or 2
87 // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
88 // format 2: protocol://file
91 size_t iPos
= strURL
.find("://");
92 if (iPos
== std::string::npos
)
94 // This is an ugly hack that needs some work.
95 // example: filename /foo/bar.zip/alice.rar/bob.avi
96 // This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
98 bool is_apk
= (strURL
.find(".apk/", iPos
) != std::string::npos
);
102 iPos
= strURL
.find(".apk/", iPos
);
104 iPos
= strURL
.find(".zip/", iPos
);
107 if (iPos
== std::string::npos
)
109 /* set filename and update extension*/
114 std::string archiveName
= strURL
.substr(0, iPos
);
116 if (XFILE::CFile::Stat(archiveName
, &s
) == 0)
119 if (!S_ISDIR(s
.st_mode
))
121 if (!(s
.st_mode
& S_IFDIR
))
124 archiveName
= Encode(archiveName
);
127 CURL
c("apk://" + archiveName
+ "/" + strURL
.substr(iPos
+ 1));
132 CURL
c("zip://" + archiveName
+ "/" + strURL
.substr(iPos
+ 1));
142 SetProtocol(strURL
.substr(0, iPos
));
147 // why not handle all format 2 (protocol://file) style urls here?
148 // ones that come to mind are iso9660, cdda, musicdb, etc.
149 // they are all local protocols and have no server part, port number, special options, etc.
150 // this removes the need for special handling below.
152 m_strProtocol
.Equals("stack") ||
153 m_strProtocol
.Equals("virtualpath") ||
154 m_strProtocol
.Equals("multipath") ||
155 m_strProtocol
.Equals("filereader") ||
156 m_strProtocol
.Equals("special")
159 SetFileName(strURL
.substr(iPos
));
163 // check for username/password - should occur before first /
164 if (iPos
== std::string::npos
) iPos
= 0;
166 // for protocols supporting options, chop that part off here
167 // maybe we should invert this list instead?
168 size_t iEnd
= strURL
.length();
169 const char* sep
= NULL
;
171 //TODO fix all Addon paths
172 CStdString strProtocol2
= GetTranslatedProtocol();
173 if(m_strProtocol
.Equals("rss") ||
174 m_strProtocol
.Equals("rar") ||
175 m_strProtocol
.Equals("addons") ||
176 m_strProtocol
.Equals("image") ||
177 m_strProtocol
.Equals("videodb") ||
178 m_strProtocol
.Equals("musicdb") ||
179 m_strProtocol
.Equals("androidapp"))
182 if(strProtocol2
.Equals("http")
183 || strProtocol2
.Equals("https")
184 || strProtocol2
.Equals("plugin")
185 || strProtocol2
.Equals("addons")
186 || strProtocol2
.Equals("hdhomerun")
187 || strProtocol2
.Equals("rtsp")
188 || strProtocol2
.Equals("apk")
189 || strProtocol2
.Equals("zip"))
191 else if(strProtocol2
.Equals("ftp")
192 || strProtocol2
.Equals("ftps"))
197 size_t iOptions
= strURL
.find_first_of(sep
, iPos
);
198 if (iOptions
!= std::string::npos
)
200 // we keep the initial char as it can be any of the above
201 size_t iProto
= strURL
.find_first_of("|",iOptions
);
202 if (iProto
!= std::string::npos
)
204 SetProtocolOptions(strURL
.substr(iProto
+1));
205 SetOptions(strURL
.substr(iOptions
,iProto
-iOptions
));
208 SetOptions(strURL
.substr(iOptions
));
213 size_t iSlash
= strURL
.find("/", iPos
);
215 iSlash
= std::string::npos
; // was an invalid slash as it was contained in options
217 if( !m_strProtocol
.Equals("iso9660") )
219 size_t iAlphaSign
= strURL
.find("@", iPos
);
220 if (iAlphaSign
!= std::string::npos
&& iAlphaSign
< iEnd
&& (iAlphaSign
< iSlash
|| iSlash
== std::string::npos
))
222 // username/password found
223 CStdString strUserNamePassword
= strURL
.substr(iPos
, iAlphaSign
- iPos
);
225 // first extract domain, if protocol is smb
226 if (m_strProtocol
.Equals("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);
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);
247 m_strUserName
= strUserNamePassword
;
250 iPos
= iAlphaSign
+ 1;
251 iSlash
= strURL
.find("/", iAlphaSign
);
254 iSlash
= std::string::npos
;
258 // detect hostname:port/
259 if (iSlash
== std::string::npos
)
261 CStdString strHostNameAndPort
= strURL
.substr(iPos
, iEnd
- iPos
);
262 size_t iColon
= strHostNameAndPort
.find(":");
263 if (iColon
!= std::string::npos
)
265 m_strHostName
= strHostNameAndPort
.substr(0, iColon
);
266 m_iPort
= atoi(strHostNameAndPort
.substr(iColon
+ 1).c_str());
270 m_strHostName
= strHostNameAndPort
;
276 CStdString strHostNameAndPort
= strURL
.substr(iPos
, iSlash
- iPos
);
277 size_t iColon
= strHostNameAndPort
.find(":");
278 if (iColon
!= std::string::npos
)
280 m_strHostName
= strHostNameAndPort
.substr(0, iColon
);
281 m_iPort
= atoi(strHostNameAndPort
.substr(iColon
+ 1).c_str());
285 m_strHostName
= strHostNameAndPort
;
290 m_strFileName
= strURL
.substr(iPos
, iEnd
- iPos
);
292 iSlash
= m_strFileName
.find("/");
293 if(iSlash
== std::string::npos
)
294 m_strShareName
= m_strFileName
;
296 m_strShareName
= m_strFileName
.substr(0, iSlash
);
300 // iso9960 doesnt have an hostname;-)
301 if (m_strProtocol
== "iso9660"
302 || m_strProtocol
== "musicdb"
303 || m_strProtocol
== "videodb"
304 || m_strProtocol
== "sources"
305 || m_strProtocol
== "pvr"
306 || StringUtils::StartsWith(m_strProtocol
, "mem"))
308 if (m_strHostName
!= "" && m_strFileName
!= "")
310 m_strFileName
= StringUtils::Format("%s/%s", m_strHostName
.c_str(), m_strFileName
.c_str());
315 if (!m_strHostName
.empty() && strURL
[iEnd
-1]=='/')
316 m_strFileName
= m_strHostName
+ "/";
318 m_strFileName
= m_strHostName
;
323 StringUtils::Replace(m_strFileName
, '\\', '/');
325 /* update extension */
326 SetFileName(m_strFileName
);
328 /* decode urlencoding on this stuff */
329 if(URIUtils::ProtocolHasEncodedHostname(m_strProtocol
))
331 m_strHostName
= Decode(m_strHostName
);
332 SetHostName(m_strHostName
);
335 m_strUserName
= Decode(m_strUserName
);
336 m_strPassword
= Decode(m_strPassword
);
339 void CURL::SetFileName(const CStdString
& strFileName
)
341 m_strFileName
= strFileName
;
343 int slash
= m_strFileName
.find_last_of(GetDirectorySeparator());
344 int period
= m_strFileName
.find_last_of('.');
345 if(period
!= -1 && (slash
== -1 || period
> slash
))
346 m_strFileType
= m_strFileName
.substr(period
+1);
350 StringUtils::Trim(m_strFileType
);
351 StringUtils::ToLower(m_strFileType
);
354 void CURL::SetHostName(const CStdString
& strHostName
)
356 m_strHostName
= strHostName
;
359 void CURL::SetUserName(const CStdString
& strUserName
)
361 m_strUserName
= strUserName
;
364 void CURL::SetPassword(const CStdString
& strPassword
)
366 m_strPassword
= strPassword
;
369 void CURL::SetProtocol(const CStdString
& strProtocol
)
371 m_strProtocol
= strProtocol
;
372 StringUtils::ToLower(m_strProtocol
);
375 void CURL::SetOptions(const CStdString
& strOptions
)
377 m_strOptions
.clear();
379 if( strOptions
.length() > 0)
381 if(strOptions
[0] == '?' ||
382 strOptions
[0] == '#' ||
383 strOptions
[0] == ';' ||
384 strOptions
.find("xml") != std::string::npos
)
386 m_strOptions
= strOptions
;
387 m_options
.AddOptions(m_strOptions
);
390 CLog::Log(LOGWARNING
, "%s - Invalid options specified for url %s", __FUNCTION__
, strOptions
.c_str());
394 void CURL::SetProtocolOptions(const CStdString
& strOptions
)
396 m_strProtocolOptions
.clear();
397 m_protocolOptions
.Clear();
398 if (strOptions
.length() > 0)
400 if (strOptions
[0] == '|')
401 m_strProtocolOptions
= strOptions
.substr(1);
403 m_strProtocolOptions
= strOptions
;
404 m_protocolOptions
.AddOptions(m_strProtocolOptions
);
408 void CURL::SetPort(int port
)
413 bool CURL::HasPort() const
415 return (m_iPort
!= 0);
418 int CURL::GetPort() const
424 const CStdString
& CURL::GetHostName() const
426 return m_strHostName
;
429 const CStdString
& CURL::GetShareName() const
431 return m_strShareName
;
434 const CStdString
& CURL::GetDomain() const
439 const CStdString
& CURL::GetUserName() const
441 return m_strUserName
;
444 const CStdString
& CURL::GetPassWord() const
446 return m_strPassword
;
449 const CStdString
& CURL::GetFileName() const
451 return m_strFileName
;
454 const CStdString
& CURL::GetProtocol() const
456 return m_strProtocol
;
459 const CStdString
CURL::GetTranslatedProtocol() const
461 return TranslateProtocol(m_strProtocol
);
464 const CStdString
& CURL::GetFileType() const
466 return m_strFileType
;
469 const CStdString
& CURL::GetOptions() const
474 const CStdString
& CURL::GetProtocolOptions() const
476 return m_strProtocolOptions
;
479 const CStdString
CURL::GetFileNameWithoutPath() const
481 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
482 if ((m_strProtocol
== "rar" ||
483 m_strProtocol
== "zip" ||
484 m_strProtocol
== "apk") &&
485 m_strFileName
.empty())
486 return URIUtils::GetFileName(m_strHostName
);
488 // otherwise, we've already got the filepath, so just grab the filename portion
489 CStdString
file(m_strFileName
);
490 URIUtils::RemoveSlashAtEnd(file
);
491 return URIUtils::GetFileName(file
);
494 char CURL::GetDirectorySeparator() const
504 CStdString
CURL::Get() const
506 unsigned int sizeneed
= m_strProtocol
.length()
507 + m_strDomain
.length()
508 + m_strUserName
.length()
509 + m_strPassword
.length()
510 + m_strHostName
.length()
511 + m_strFileName
.length()
512 + m_strOptions
.length()
513 + m_strProtocolOptions
.length()
516 if (m_strProtocol
== "")
517 return m_strFileName
;
520 strURL
.reserve(sizeneed
);
522 strURL
= GetWithoutFilename();
523 strURL
+= m_strFileName
;
525 if( !m_strOptions
.empty() )
526 strURL
+= m_strOptions
;
527 if (!m_strProtocolOptions
.empty())
528 strURL
+= "|"+m_strProtocolOptions
;
533 std::string
CURL::GetWithoutUserDetails(bool redact
) const
537 if (m_strProtocol
.Equals("stack"))
542 XFILE::CStackDirectory dir
;
543 dir
.GetDirectory(strURL2
,items
);
544 vector
<std::string
> newItems
;
545 for (int i
=0;i
<items
.Size();++i
)
547 CURL
url(items
[i
]->GetPath());
548 items
[i
]->SetPath(url
.GetWithoutUserDetails(redact
));
549 newItems
.push_back(items
[i
]->GetPath());
551 dir
.ConstructStackPath(newItems
, strURL
);
555 unsigned int sizeneed
= m_strProtocol
.length()
556 + m_strDomain
.length()
557 + m_strHostName
.length()
558 + m_strFileName
.length()
559 + m_strOptions
.length()
560 + m_strProtocolOptions
.length()
564 sizeneed
+= sizeof("USERNAME:PASSWORD@");
566 strURL
.reserve(sizeneed
);
568 if (m_strProtocol
== "")
569 return m_strFileName
;
571 strURL
= m_strProtocol
;
574 if (redact
&& !m_strUserName
.empty())
576 strURL
+= "USERNAME";
577 if (!m_strPassword
.empty())
579 strURL
+= ":PASSWORD";
584 if (!m_strHostName
.empty())
586 std::string strHostName
;
588 if (URIUtils::ProtocolHasParentInHostname(m_strProtocol
))
589 strHostName
= CURL(m_strHostName
).GetWithoutUserDetails();
591 strHostName
= m_strHostName
;
593 if (URIUtils::ProtocolHasEncodedHostname(m_strProtocol
))
594 strURL
+= Encode(strHostName
);
596 strURL
+= strHostName
;
600 strURL
+= StringUtils::Format(":%i", m_iPort
);
604 strURL
+= m_strFileName
;
606 if( m_strOptions
.length() > 0 )
607 strURL
+= m_strOptions
;
608 if( m_strProtocolOptions
.length() > 0 )
609 strURL
+= "|"+m_strProtocolOptions
;
614 CStdString
CURL::GetWithoutFilename() const
616 if (m_strProtocol
== "")
619 unsigned int sizeneed
= m_strProtocol
.length()
620 + m_strDomain
.length()
621 + m_strUserName
.length()
622 + m_strPassword
.length()
623 + m_strHostName
.length()
627 strURL
.reserve(sizeneed
);
629 strURL
= m_strProtocol
;
632 if (m_strDomain
!= "")
634 strURL
+= m_strDomain
;
638 if (m_strUserName
!= "")
640 strURL
+= Encode(m_strUserName
);
641 if (m_strPassword
!= "")
644 strURL
+= Encode(m_strPassword
);
648 else if (m_strDomain
!= "")
651 if (m_strHostName
!= "")
653 if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol
) )
654 strURL
+= Encode(m_strHostName
);
656 strURL
+= m_strHostName
;
659 CStdString strPort
= StringUtils::Format("%i", m_iPort
);
669 std::string
CURL::GetRedacted() const
671 return GetWithoutUserDetails(true);
674 std::string
CURL::GetRedacted(const std::string
& path
)
676 return CURL(path
).GetRedacted();
679 bool CURL::IsLocal() const
681 return (IsLocalHost() || m_strProtocol
.empty());
684 bool CURL::IsLocalHost() const
686 return (m_strHostName
.Equals("localhost") || m_strHostName
.Equals("127.0.0.1"));
689 bool CURL::IsFileOnly(const CStdString
&url
)
691 return url
.find_first_of("/\\") == CStdString::npos
;
694 bool CURL::IsFullPath(const CStdString
&url
)
696 if (url
.size() && url
[0] == '/') return true; // /foo/bar.ext
697 if (url
.find("://") != std::string::npos
) return true; // foo://bar.ext
698 if (url
.size() > 1 && url
[1] == ':') return true; // c:\\foo\\bar\\bar.ext
699 if (StringUtils::StartsWith(url
, "\\\\")) return true; // \\UNC\path\to\file
703 std::string
CURL::Decode(const std::string
& strURLData
)
704 //modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
705 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
707 std::string strResult
;
709 /* result will always be less than source */
710 strResult
.reserve( strURLData
.length() );
712 for (unsigned int i
= 0; i
< strURLData
.size(); ++i
)
714 int kar
= (unsigned char)strURLData
[i
];
715 if (kar
== '+') strResult
+= ' ';
718 if (i
< strURLData
.size() - 2)
721 strTmp
.assign(strURLData
.substr(i
+ 1, 2));
723 sscanf(strTmp
.c_str(), "%x", (unsigned int *)&dec_num
);
724 if (dec_num
<0 || dec_num
>255)
728 strResult
+= (char)dec_num
;
735 else strResult
+= kar
;
741 std::string
CURL::Encode(const std::string
& strURLData
)
743 std::string strResult
;
745 /* wonder what a good value is here is, depends on how often it occurs */
746 strResult
.reserve( strURLData
.length() * 2 );
748 for (size_t i
= 0; i
< strURLData
.size(); ++i
)
750 const char kar
= strURLData
[i
];
752 // Don't URL encode "-_.!()" according to RFC1738
753 // TODO: Update it to "-_.~" after Gotham according to RFC3986
754 if (StringUtils::isasciialphanum(kar
) || kar
== '-' || kar
== '.' || kar
== '_' || kar
== '!' || kar
== '(' || kar
== ')')
755 strResult
.push_back(kar
);
757 strResult
+= StringUtils::Format("%%%02.2x", (unsigned int)((unsigned char)kar
)); // TODO: Change to "%%%02.2X" after Gotham
763 CStdString
CURL::TranslateProtocol(const CStdString
& prot
)
778 void CURL::GetOptions(std::map
<CStdString
, CStdString
> &options
) const
780 CUrlOptions::UrlOptions optionsMap
= m_options
.GetOptions();
781 for (CUrlOptions::UrlOptions::const_iterator option
= optionsMap
.begin(); option
!= optionsMap
.end(); option
++)
782 options
[option
->first
] = option
->second
.asString();
785 bool CURL::HasOption(const CStdString
&key
) const
787 return m_options
.HasOption(key
);
790 bool CURL::GetOption(const CStdString
&key
, CStdString
&value
) const
793 if (!m_options
.GetOption(key
, valueObj
))
796 value
= valueObj
.asString();
800 CStdString
CURL::GetOption(const CStdString
&key
) const
803 if (!GetOption(key
, value
))
809 void CURL::SetOption(const CStdString
&key
, const CStdString
&value
)
811 m_options
.AddOption(key
, value
);
812 SetOptions(m_options
.GetOptionsString(true));
815 void CURL::RemoveOption(const CStdString
&key
)
817 m_options
.RemoveOption(key
);
818 SetOptions(m_options
.GetOptionsString(true));
821 void CURL::GetProtocolOptions(std::map
<CStdString
, CStdString
> &options
) const
823 CUrlOptions::UrlOptions optionsMap
= m_protocolOptions
.GetOptions();
824 for (CUrlOptions::UrlOptions::const_iterator option
= optionsMap
.begin(); option
!= optionsMap
.end(); option
++)
825 options
[option
->first
] = option
->second
.asString();
828 bool CURL::HasProtocolOption(const CStdString
&key
) const
830 return m_protocolOptions
.HasOption(key
);
833 bool CURL::GetProtocolOption(const CStdString
&key
, CStdString
&value
) const
836 if (!m_protocolOptions
.GetOption(key
, valueObj
))
839 value
= valueObj
.asString();
843 CStdString
CURL::GetProtocolOption(const CStdString
&key
) const
846 if (!GetProtocolOption(key
, value
))
852 void CURL::SetProtocolOption(const CStdString
&key
, const CStdString
&value
)
854 m_protocolOptions
.AddOption(key
, value
);
855 m_strProtocolOptions
= m_protocolOptions
.GetOptionsString(false);
858 void CURL::RemoveProtocolOption(const CStdString
&key
)
860 m_protocolOptions
.RemoveOption(key
);
861 m_strProtocolOptions
= m_protocolOptions
.GetOptionsString(false);