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 CStdString URLEncodeInline(const CStdString& strData)
42 CStdString buffer = strData;
43 buffer = CURL::Encode(buffer);
47 CURL::CURL(const CStdString& strURL1)
63 m_strHostName.clear();
65 m_strUserName.clear();
66 m_strPassword.clear();
67 m_strShareName.clear();
68 m_strFileName.clear();
69 m_strProtocol.clear();
70 m_strFileType.clear();
72 m_strProtocolOptions.clear();
74 m_protocolOptions.Clear();
78 void CURL::Parse(const CStdString& strURL1)
81 // start by validating the path
82 CStdString strURL = CUtil::ValidatePath(strURL1);
84 // strURL can be one of the following:
85 // format 1: protocol://[username:password]@hostname[:port]/directoryandfile
86 // format 2: protocol://file
87 // format 3: drive:directoryandfile
89 // first need 2 check if this is a protocol or just a normal drive & path
90 if (!strURL.size()) return ;
91 if (strURL.Equals("?", true)) return;
93 // form is format 1 or 2
94 // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
95 // format 2: protocol://file
98 size_t iPos = strURL.find("://");
99 if (iPos == std::string::npos)
101 // This is an ugly hack that needs some work.
102 // example: filename /foo/bar.zip/alice.rar/bob.avi
103 // This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
105 bool is_apk = (strURL.find(".apk/", iPos) != std::string::npos);
109 iPos = strURL.find(".apk/", iPos);
111 iPos = strURL.find(".zip/", iPos);
114 if (iPos == std::string::npos)
116 /* set filename and update extension*/
121 std::string archiveName = strURL.substr(0, iPos);
123 if (XFILE::CFile::Stat(archiveName, &s) == 0)
126 if (!S_ISDIR(s.st_mode))
128 if (!(s.st_mode & S_IFDIR))
131 archiveName = Encode(archiveName);
134 CURL c("apk://" + archiveName + "/" + strURL.substr(iPos + 1));
139 CURL c("zip://" + archiveName + "/" + strURL.substr(iPos + 1));
149 SetProtocol(strURL.substr(0, iPos));
154 // why not handle all format 2 (protocol://file) style urls here?
155 // ones that come to mind are iso9660, cdda, musicdb, etc.
156 // they are all local protocols and have no server part, port number, special options, etc.
157 // this removes the need for special handling below.
159 m_strProtocol.Equals("stack") ||
160 m_strProtocol.Equals("virtualpath") ||
161 m_strProtocol.Equals("multipath") ||
162 m_strProtocol.Equals("filereader") ||
163 m_strProtocol.Equals("special")
166 SetFileName(strURL.substr(iPos));
170 // check for username/password - should occur before first /
171 if (iPos == std::string::npos) iPos = 0;
173 // for protocols supporting options, chop that part off here
174 // maybe we should invert this list instead?
175 size_t iEnd = strURL.length();
176 const char* sep = NULL;
178 //TODO fix all Addon paths
179 CStdString strProtocol2 = GetTranslatedProtocol();
180 if(m_strProtocol.Equals("rss") ||
181 m_strProtocol.Equals("rar") ||
182 m_strProtocol.Equals("addons") ||
183 m_strProtocol.Equals("image") ||
184 m_strProtocol.Equals("videodb") ||
185 m_strProtocol.Equals("musicdb") ||
186 m_strProtocol.Equals("androidapp"))
189 if(strProtocol2.Equals("http")
190 || strProtocol2.Equals("https")
191 || strProtocol2.Equals("plugin")
192 || strProtocol2.Equals("addons")
193 || strProtocol2.Equals("hdhomerun")
194 || strProtocol2.Equals("rtsp")
195 || strProtocol2.Equals("apk")
196 || strProtocol2.Equals("zip"))
198 else if(strProtocol2.Equals("ftp")
199 || strProtocol2.Equals("ftps"))
204 size_t iOptions = strURL.find_first_of(sep, iPos);
205 if (iOptions != std::string::npos)
207 // we keep the initial char as it can be any of the above
208 size_t iProto = strURL.find_first_of("|",iOptions);
209 if (iProto != std::string::npos)
211 SetProtocolOptions(strURL.substr(iProto+1));
212 SetOptions(strURL.substr(iOptions,iProto-iOptions));
215 SetOptions(strURL.substr(iOptions));
220 size_t iSlash = strURL.find("/", iPos);
222 iSlash = std::string::npos; // was an invalid slash as it was contained in options
224 if( !m_strProtocol.Equals("iso9660") )
226 size_t iAlphaSign = strURL.find("@", iPos);
227 if (iAlphaSign != std::string::npos && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash == std::string::npos))
229 // username/password found
230 CStdString strUserNamePassword = strURL.substr(iPos, iAlphaSign - iPos);
232 // first extract domain, if protocol is smb
233 if (m_strProtocol.Equals("smb"))
235 size_t iSemiColon = strUserNamePassword.find(";");
237 if (iSemiColon != std::string::npos)
239 m_strDomain = strUserNamePassword.substr(0, iSemiColon);
240 strUserNamePassword.erase(0, iSemiColon + 1);
245 size_t iColon = strUserNamePassword.find(":");
246 if (iColon != std::string::npos)
248 m_strUserName = strUserNamePassword.substr(0, iColon);
249 m_strPassword = strUserNamePassword.substr(iColon + 1);
254 m_strUserName = strUserNamePassword;
257 iPos = iAlphaSign + 1;
258 iSlash = strURL.find("/", iAlphaSign);
261 iSlash = std::string::npos;
265 // detect hostname:port/
266 if (iSlash == std::string::npos)
268 CStdString strHostNameAndPort = strURL.substr(iPos, iEnd - iPos);
269 size_t iColon = strHostNameAndPort.find(":");
270 if (iColon != std::string::npos)
272 m_strHostName = strHostNameAndPort.substr(0, iColon);
273 m_iPort = atoi(strHostNameAndPort.substr(iColon + 1).c_str());
277 m_strHostName = strHostNameAndPort;
283 CStdString strHostNameAndPort = strURL.substr(iPos, iSlash - iPos);
284 size_t iColon = strHostNameAndPort.find(":");
285 if (iColon != std::string::npos)
287 m_strHostName = strHostNameAndPort.substr(0, iColon);
288 m_iPort = atoi(strHostNameAndPort.substr(iColon + 1).c_str());
292 m_strHostName = strHostNameAndPort;
297 m_strFileName = strURL.substr(iPos, iEnd - iPos);
299 iSlash = m_strFileName.find("/");
300 if(iSlash == std::string::npos)
301 m_strShareName = m_strFileName;
303 m_strShareName = m_strFileName.substr(0, iSlash);
307 // iso9960 doesnt have an hostname;-)
308 if (m_strProtocol == "iso9660"
309 || m_strProtocol == "musicdb"
310 || m_strProtocol == "videodb"
311 || m_strProtocol == "sources"
312 || m_strProtocol == "pvr"
313 || StringUtils::StartsWith(m_strProtocol, "mem"))
315 if (m_strHostName != "" && m_strFileName != "")
317 m_strFileName = StringUtils::Format("%s/%s", m_strHostName.c_str(), m_strFileName.c_str());
322 if (!m_strHostName.empty() && strURL[iEnd-1]=='/')
323 m_strFileName = m_strHostName + "/";
325 m_strFileName = m_strHostName;
330 StringUtils::Replace(m_strFileName, '\\', '/');
332 /* update extension */
333 SetFileName(m_strFileName);
335 /* decode urlencoding on this stuff */
336 if(URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
338 Decode(m_strHostName);
339 SetHostName(m_strHostName);
342 Decode(m_strUserName);
343 Decode(m_strPassword);
346 void CURL::SetFileName(const CStdString& strFileName)
348 m_strFileName = strFileName;
350 int slash = m_strFileName.find_last_of(GetDirectorySeparator());
351 int period = m_strFileName.find_last_of('.');
352 if(period != -1 && (slash == -1 || period > slash))
353 m_strFileType = m_strFileName.substr(period+1);
357 StringUtils::Trim(m_strFileType);
358 StringUtils::ToLower(m_strFileType);
361 void CURL::SetHostName(const CStdString& strHostName)
363 m_strHostName = strHostName;
366 void CURL::SetUserName(const CStdString& strUserName)
368 m_strUserName = strUserName;
371 void CURL::SetPassword(const CStdString& strPassword)
373 m_strPassword = strPassword;
376 void CURL::SetProtocol(const CStdString& strProtocol)
378 m_strProtocol = strProtocol;
379 StringUtils::ToLower(m_strProtocol);
382 void CURL::SetOptions(const CStdString& strOptions)
384 m_strOptions.clear();
386 if( strOptions.length() > 0)
388 if(strOptions[0] == '?' ||
389 strOptions[0] == '#' ||
390 strOptions[0] == ';' ||
391 strOptions.find("xml") != std::string::npos)
393 m_strOptions = strOptions;
394 m_options.AddOptions(m_strOptions);
397 CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
401 void CURL::SetProtocolOptions(const CStdString& strOptions)
403 m_strProtocolOptions.clear();
404 m_protocolOptions.Clear();
405 if (strOptions.length() > 0)
407 if (strOptions[0] == '|')
408 m_strProtocolOptions = strOptions.substr(1);
410 m_strProtocolOptions = strOptions;
411 m_protocolOptions.AddOptions(m_strProtocolOptions);
415 void CURL::SetPort(int port)
420 bool CURL::HasPort() const
422 return (m_iPort != 0);
425 int CURL::GetPort() const
431 const CStdString& CURL::GetHostName() const
433 return m_strHostName;
436 const CStdString& CURL::GetShareName() const
438 return m_strShareName;
441 const CStdString& CURL::GetDomain() const
446 const CStdString& CURL::GetUserName() const
448 return m_strUserName;
451 const CStdString& CURL::GetPassWord() const
453 return m_strPassword;
456 const CStdString& CURL::GetFileName() const
458 return m_strFileName;
461 const CStdString& CURL::GetProtocol() const
463 return m_strProtocol;
466 const CStdString CURL::GetTranslatedProtocol() const
468 return TranslateProtocol(m_strProtocol);
471 const CStdString& CURL::GetFileType() const
473 return m_strFileType;
476 const CStdString& CURL::GetOptions() const
481 const CStdString& CURL::GetProtocolOptions() const
483 return m_strProtocolOptions;
486 const CStdString CURL::GetFileNameWithoutPath() const
488 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
489 if ((m_strProtocol == "rar" ||
490 m_strProtocol == "zip" ||
491 m_strProtocol == "apk") &&
492 m_strFileName.empty())
493 return URIUtils::GetFileName(m_strHostName);
495 // otherwise, we've already got the filepath, so just grab the filename portion
496 CStdString file(m_strFileName);
497 URIUtils::RemoveSlashAtEnd(file);
498 return URIUtils::GetFileName(file);
501 char CURL::GetDirectorySeparator() const
511 CStdString CURL::Get() const
513 unsigned int sizeneed = m_strProtocol.length()
514 + m_strDomain.length()
515 + m_strUserName.length()
516 + m_strPassword.length()
517 + m_strHostName.length()
518 + m_strFileName.length()
519 + m_strOptions.length()
520 + m_strProtocolOptions.length()
523 if (m_strProtocol == "")
524 return m_strFileName;
527 strURL.reserve(sizeneed);
529 strURL = GetWithoutFilename();
530 strURL += m_strFileName;
532 if( !m_strOptions.empty() )
533 strURL += m_strOptions;
534 if (!m_strProtocolOptions.empty())
535 strURL += "|"+m_strProtocolOptions;
540 std::string CURL::GetWithoutUserDetails(bool redact) const
544 if (m_strProtocol.Equals("stack"))
549 XFILE::CStackDirectory dir;
550 dir.GetDirectory(strURL2,items);
551 vector<std::string> newItems;
552 for (int i=0;i<items.Size();++i)
554 CURL url(items[i]->GetPath());
555 items[i]->SetPath(url.GetWithoutUserDetails(redact));
556 newItems.push_back(items[i]->GetPath());
558 dir.ConstructStackPath(newItems, strURL);
562 unsigned int sizeneed = m_strProtocol.length()
563 + m_strDomain.length()
564 + m_strHostName.length()
565 + m_strFileName.length()
566 + m_strOptions.length()
567 + m_strProtocolOptions.length()
571 sizeneed += sizeof("USERNAME:PASSWORD@");
573 strURL.reserve(sizeneed);
575 if (m_strProtocol == "")
576 return m_strFileName;
578 strURL = m_strProtocol;
581 if (redact && !m_strUserName.empty())
583 strURL += "USERNAME";
584 if (!m_strPassword.empty())
586 strURL += ":PASSWORD";
591 if (!m_strHostName.empty())
593 std::string strHostName;
595 if (URIUtils::ProtocolHasParentInHostname(m_strProtocol))
596 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
598 strHostName = m_strHostName;
600 if (URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
601 strURL += URLEncodeInline(strHostName);
603 strURL += strHostName;
607 strURL += StringUtils::Format(":%i", m_iPort);
611 strURL += m_strFileName;
613 if( m_strOptions.length() > 0 )
614 strURL += m_strOptions;
615 if( m_strProtocolOptions.length() > 0 )
616 strURL += "|"+m_strProtocolOptions;
621 CStdString CURL::GetWithoutFilename() const
623 if (m_strProtocol == "")
626 unsigned int sizeneed = m_strProtocol.length()
627 + m_strDomain.length()
628 + m_strUserName.length()
629 + m_strPassword.length()
630 + m_strHostName.length()
634 strURL.reserve(sizeneed);
636 strURL = m_strProtocol;
639 if (m_strDomain != "")
641 strURL += m_strDomain;
644 else if (m_strUserName != "")
646 strURL += URLEncodeInline(m_strUserName);
647 if (m_strPassword != "")
650 strURL += URLEncodeInline(m_strPassword);
654 else if (m_strDomain != "")
657 if (m_strHostName != "")
659 if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol) )
660 strURL += URLEncodeInline(m_strHostName);
662 strURL += m_strHostName;
665 CStdString strPort = StringUtils::Format("%i", m_iPort);
675 std::string CURL::GetRedacted() const
677 return GetWithoutUserDetails(true);
680 std::string CURL::GetRedacted(const std::string& path)
682 return CURL(path).GetRedacted();
685 bool CURL::IsLocal() const
687 return (IsLocalHost() || m_strProtocol.empty());
690 bool CURL::IsLocalHost() const
692 return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
695 bool CURL::IsFileOnly(const CStdString &url)
697 return url.find_first_of("/\\") == CStdString::npos;
700 bool CURL::IsFullPath(const CStdString &url)
702 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
703 if (url.find("://") != std::string::npos) return true; // foo://bar.ext
704 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
705 if (StringUtils::StartsWith(url, "\\\\")) return true; // \\UNC\path\to\file
709 void CURL::Decode(CStdString& strURLData)
710 //modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
711 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
713 CStdString strResult;
715 /* result will always be less than source */
716 strResult.reserve( strURLData.length() );
718 for (unsigned int i = 0; i < strURLData.size(); ++i)
720 int kar = (unsigned char)strURLData[i];
721 if (kar == '+') strResult += ' ';
724 if (i < strURLData.size() - 2)
727 strTmp.assign(strURLData.substr(i + 1, 2));
729 sscanf(strTmp.c_str(), "%x", (unsigned int *)&dec_num);
730 if (dec_num<0 || dec_num>255)
734 strResult += (char)dec_num;
741 else strResult += kar;
743 strURLData = strResult;
746 std::string CURL::Encode(const std::string& strURLData)
748 std::string strResult;
750 /* wonder what a good value is here is, depends on how often it occurs */
751 strResult.reserve( strURLData.length() * 2 );
753 for (int i = 0; i < (int)strURLData.size(); ++i)
755 int kar = (unsigned char)strURLData[i];
756 //if (kar == ' ') strResult += '+'; // obsolete
757 if (isalnum(kar) || strchr("-_.!()" , kar) ) // Don't URL encode these according to RFC1738
763 CStdString strTmp = StringUtils::Format("%%%02.2x", kar);
771 std::string CURL::Decode(const std::string& strURLData)
773 CStdString url = strURLData;
778 CStdString CURL::TranslateProtocol(const CStdString& prot)
793 void CURL::GetOptions(std::map<CStdString, CStdString> &options) const
795 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
796 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
797 options[option->first] = option->second.asString();
800 bool CURL::HasOption(const CStdString &key) const
802 return m_options.HasOption(key);
805 bool CURL::GetOption(const CStdString &key, CStdString &value) const
808 if (!m_options.GetOption(key, valueObj))
811 value = valueObj.asString();
815 CStdString CURL::GetOption(const CStdString &key) const
818 if (!GetOption(key, value))
824 void CURL::SetOption(const CStdString &key, const CStdString &value)
826 m_options.AddOption(key, value);
827 SetOptions(m_options.GetOptionsString(true));
830 void CURL::RemoveOption(const CStdString &key)
832 m_options.RemoveOption(key);
833 SetOptions(m_options.GetOptionsString(true));
836 void CURL::GetProtocolOptions(std::map<CStdString, CStdString> &options) const
838 CUrlOptions::UrlOptions optionsMap = m_protocolOptions.GetOptions();
839 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
840 options[option->first] = option->second.asString();
843 bool CURL::HasProtocolOption(const CStdString &key) const
845 return m_protocolOptions.HasOption(key);
848 bool CURL::GetProtocolOption(const CStdString &key, CStdString &value) const
851 if (!m_protocolOptions.GetOption(key, valueObj))
854 value = valueObj.asString();
858 CStdString CURL::GetProtocolOption(const CStdString &key) const
861 if (!GetProtocolOption(key, value))
867 void CURL::SetProtocolOption(const CStdString &key, const CStdString &value)
869 m_protocolOptions.AddOption(key, value);
870 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);
873 void CURL::RemoveProtocolOption(const CStdString &key)
875 m_protocolOptions.RemoveOption(key);
876 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);