Merge pull request #4594 from FernetMenta/paplayer
[xbmc.git] / xbmc / URL.cpp
blobb2bd0578f67af7234406af64b0fccfab3c156b3d
1 /*
2 * Copyright (C) 2005-2013 Team XBMC
3 * http://xbmc.org
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)
8 * any later version.
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/>.
21 #include "URL.h"
22 #include "utils/RegExp.h"
23 #include "utils/log.h"
24 #include "utils/URIUtils.h"
25 #include "utils/StringUtils.h"
26 #include "Util.h"
27 #include "filesystem/File.h"
28 #include "FileItem.h"
29 #include "filesystem/StackDirectory.h"
30 #include "addons/Addon.h"
31 #include "utils/StringUtils.h"
32 #ifndef TARGET_POSIX
33 #include <sys\types.h>
34 #include <sys\stat.h>
35 #endif
37 using namespace std;
38 using namespace ADDON;
40 CURL::CURL(const CStdString& strURL1)
42 Parse(strURL1);
45 CURL::CURL()
47 m_iPort = 0;
50 CURL::~CURL()
54 void CURL::Reset()
56 m_strHostName.clear();
57 m_strDomain.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();
64 m_strOptions.clear();
65 m_strProtocolOptions.clear();
66 m_options.Clear();
67 m_protocolOptions.Clear();
68 m_iPort = 0;
71 void CURL::Parse(const CStdString& strURL1)
73 Reset();
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
90 // decode protocol
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
97 iPos = 0;
98 bool is_apk = (strURL.find(".apk/", iPos) != std::string::npos);
99 while (1)
101 if (is_apk)
102 iPos = strURL.find(".apk/", iPos);
103 else
104 iPos = strURL.find(".zip/", iPos);
106 int extLen = 3;
107 if (iPos == std::string::npos)
109 /* set filename and update extension*/
110 SetFileName(strURL);
111 return ;
113 iPos += extLen + 1;
114 std::string archiveName = strURL.substr(0, iPos);
115 struct __stat64 s;
116 if (XFILE::CFile::Stat(archiveName, &s) == 0)
118 #ifdef TARGET_POSIX
119 if (!S_ISDIR(s.st_mode))
120 #else
121 if (!(s.st_mode & S_IFDIR))
122 #endif
124 archiveName = Encode(archiveName);
125 if (is_apk)
127 CURL c("apk://" + archiveName + "/" + strURL.substr(iPos + 1));
128 *this = c;
130 else
132 CURL c("zip://" + archiveName + "/" + strURL.substr(iPos + 1));
133 *this = c;
135 return;
140 else
142 SetProtocol(strURL.substr(0, iPos));
143 iPos += 3;
146 // virtual protocols
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.
151 if (
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));
160 return;
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"))
180 sep = "?";
181 else
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"))
190 sep = "?;#|";
191 else if(strProtocol2.Equals("ftp")
192 || strProtocol2.Equals("ftps"))
193 sep = "?;|";
195 if(sep)
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));
207 else
208 SetOptions(strURL.substr(iOptions));
209 iEnd = iOptions;
213 size_t iSlash = strURL.find("/", iPos);
214 if(iSlash >= iEnd)
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);
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 = strUserNamePassword;
250 iPos = iAlphaSign + 1;
251 iSlash = strURL.find("/", iAlphaSign);
253 if(iSlash >= iEnd)
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());
268 else
270 m_strHostName = strHostNameAndPort;
274 else
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());
283 else
285 m_strHostName = strHostNameAndPort;
287 iPos = iSlash + 1;
288 if (iEnd > iPos)
290 m_strFileName = strURL.substr(iPos, iEnd - iPos);
292 iSlash = m_strFileName.find("/");
293 if(iSlash == std::string::npos)
294 m_strShareName = m_strFileName;
295 else
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());
311 m_strHostName = "";
313 else
315 if (!m_strHostName.empty() && strURL[iEnd-1]=='/')
316 m_strFileName = m_strHostName + "/";
317 else
318 m_strFileName = m_strHostName;
319 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);
347 else
348 m_strFileType = "";
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();
378 m_options.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);
389 else
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);
402 else
403 m_strProtocolOptions = strOptions;
404 m_protocolOptions.AddOptions(m_strProtocolOptions);
408 void CURL::SetPort(int port)
410 m_iPort = port;
413 bool CURL::HasPort() const
415 return (m_iPort != 0);
418 int CURL::GetPort() const
420 return m_iPort;
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
436 return m_strDomain;
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
471 return m_strOptions;
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
496 #ifndef TARGET_POSIX
497 if ( IsLocal() )
498 return '\\';
499 else
500 #endif
501 return '/';
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()
514 + 10;
516 if (m_strProtocol == "")
517 return m_strFileName;
519 CStdString strURL;
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;
530 return strURL;
533 std::string CURL::GetWithoutUserDetails(bool redact) const
535 std::string strURL;
537 if (m_strProtocol.Equals("stack"))
539 CFileItemList items;
540 std::string strURL2;
541 strURL2 = Get();
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);
552 return 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()
561 + 10;
563 if (redact)
564 sizeneed += sizeof("USERNAME:PASSWORD@");
566 strURL.reserve(sizeneed);
568 if (m_strProtocol == "")
569 return m_strFileName;
571 strURL = m_strProtocol;
572 strURL += "://";
574 if (redact && !m_strUserName.empty())
576 strURL += "USERNAME";
577 if (!m_strPassword.empty())
579 strURL += ":PASSWORD";
581 strURL += "@";
584 if (!m_strHostName.empty())
586 std::string strHostName;
588 if (URIUtils::ProtocolHasParentInHostname(m_strProtocol))
589 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
590 else
591 strHostName = m_strHostName;
593 if (URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
594 strURL += Encode(strHostName);
595 else
596 strURL += strHostName;
598 if ( HasPort() )
600 strURL += StringUtils::Format(":%i", m_iPort);
602 strURL += "/";
604 strURL += m_strFileName;
606 if( m_strOptions.length() > 0 )
607 strURL += m_strOptions;
608 if( m_strProtocolOptions.length() > 0 )
609 strURL += "|"+m_strProtocolOptions;
611 return strURL;
614 CStdString CURL::GetWithoutFilename() const
616 if (m_strProtocol == "")
617 return "";
619 unsigned int sizeneed = m_strProtocol.length()
620 + m_strDomain.length()
621 + m_strUserName.length()
622 + m_strPassword.length()
623 + m_strHostName.length()
624 + 10;
626 CStdString strURL;
627 strURL.reserve(sizeneed);
629 strURL = m_strProtocol;
630 strURL += "://";
632 if (m_strDomain != "")
634 strURL += m_strDomain;
635 strURL += ";";
638 if (m_strUserName != "")
640 strURL += Encode(m_strUserName);
641 if (m_strPassword != "")
643 strURL += ":";
644 strURL += Encode(m_strPassword);
646 strURL += "@";
648 else if (m_strDomain != "")
649 strURL += "@";
651 if (m_strHostName != "")
653 if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol) )
654 strURL += Encode(m_strHostName);
655 else
656 strURL += m_strHostName;
657 if (HasPort())
659 CStdString strPort = StringUtils::Format("%i", m_iPort);
660 strURL += ":";
661 strURL += strPort;
663 strURL += "/";
666 return strURL;
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
700 return false;
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 += ' ';
716 else if (kar == '%')
718 if (i < strURLData.size() - 2)
720 std::string strTmp;
721 strTmp.assign(strURLData.substr(i + 1, 2));
722 int dec_num=-1;
723 sscanf(strTmp.c_str(), "%x", (unsigned int *)&dec_num);
724 if (dec_num<0 || dec_num>255)
725 strResult += kar;
726 else
728 strResult += (char)dec_num;
729 i += 2;
732 else
733 strResult += kar;
735 else strResult += kar;
738 return strResult;
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);
756 else
757 strResult += StringUtils::Format("%%%02.2x", (unsigned int)((unsigned char)kar)); // TODO: Change to "%%%02.2X" after Gotham
760 return strResult;
763 CStdString CURL::TranslateProtocol(const CStdString& prot)
765 if (prot == "shout"
766 || prot == "daap"
767 || prot == "dav"
768 || prot == "tuxbox"
769 || prot == "rss")
770 return "http";
772 if (prot == "davs")
773 return "https";
775 return 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
792 CVariant valueObj;
793 if (!m_options.GetOption(key, valueObj))
794 return false;
796 value = valueObj.asString();
797 return true;
800 CStdString CURL::GetOption(const CStdString &key) const
802 CStdString value;
803 if (!GetOption(key, value))
804 return "";
806 return 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
835 CVariant valueObj;
836 if (!m_protocolOptions.GetOption(key, valueObj))
837 return false;
839 value = valueObj.asString();
840 return true;
843 CStdString CURL::GetProtocolOption(const CStdString &key) const
845 CStdString value;
846 if (!GetProtocolOption(key, value))
847 return "";
849 return 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);