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;
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 int iPos = strURL.Find("://");
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) > 0);
109 iPos = strURL.Find(".apk/", iPos);
111 iPos = strURL.Find(".zip/", iPos);
116 /* set filename and update extension*/
121 CStdString archiveName = strURL.Left(iPos);
123 if (XFILE::CFile::Stat(archiveName, &s) == 0)
126 if (!S_ISDIR(s.st_mode))
128 if (!(s.st_mode & S_IFDIR))
134 CURL c((CStdString)"apk" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
139 CURL c((CStdString)"zip" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
149 SetProtocol(strURL.Left(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.Mid(iPos));
170 // check for username/password - should occur before first /
171 if (iPos == -1) iPos = 0;
173 // for protocols supporting options, chop that part off here
174 // maybe we should invert this list instead?
175 int 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 int iOptions = strURL.find_first_of(sep, iPos);
207 // we keep the initial char as it can be any of the above
208 int iProto = strURL.find_first_of("|",iOptions);
211 SetProtocolOptions(strURL.substr(iProto+1));
212 SetOptions(strURL.substr(iOptions,iProto-iOptions));
215 SetOptions(strURL.substr(iOptions));
220 int iSlash = strURL.Find("/", iPos);
222 iSlash = -1; // was an invalid slash as it was contained in options
224 if( !m_strProtocol.Equals("iso9660") )
226 int iAlphaSign = strURL.Find("@", iPos);
227 if (iAlphaSign >= 0 && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash < 0))
229 // username/password found
230 CStdString strUserNamePassword = strURL.Mid(iPos, iAlphaSign - iPos);
232 // first extract domain, if protocol is smb
233 if (m_strProtocol.Equals("smb"))
235 int iSemiColon = strUserNamePassword.Find(";");
239 m_strDomain = strUserNamePassword.Left(iSemiColon);
240 strUserNamePassword.Delete(0, iSemiColon + 1);
245 int iColon = strUserNamePassword.Find(":");
248 m_strUserName = strUserNamePassword.Left(iColon);
250 m_strPassword = strUserNamePassword.Right(strUserNamePassword.size() - iColon);
255 m_strUserName = strUserNamePassword;
258 iPos = iAlphaSign + 1;
259 iSlash = strURL.Find("/", iAlphaSign);
266 // detect hostname:port/
269 CStdString strHostNameAndPort = strURL.Mid(iPos, iEnd - iPos);
270 int iColon = strHostNameAndPort.Find(":");
273 m_strHostName = strHostNameAndPort.Left(iColon);
275 CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
276 m_iPort = atoi(strPort.c_str());
280 m_strHostName = strHostNameAndPort;
286 CStdString strHostNameAndPort = strURL.Mid(iPos, iSlash - iPos);
287 int iColon = strHostNameAndPort.Find(":");
290 m_strHostName = strHostNameAndPort.Left(iColon);
292 CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
293 m_iPort = atoi(strPort.c_str());
297 m_strHostName = strHostNameAndPort;
302 m_strFileName = strURL.Mid(iPos, iEnd - iPos);
304 iSlash = m_strFileName.Find("/");
306 m_strShareName = m_strFileName;
308 m_strShareName = m_strFileName.Left(iSlash);
312 // iso9960 doesnt have an hostname;-)
313 if (m_strProtocol.CompareNoCase("iso9660") == 0
314 || m_strProtocol.CompareNoCase("musicdb") == 0
315 || m_strProtocol.CompareNoCase("videodb") == 0
316 || m_strProtocol.CompareNoCase("sources") == 0
317 || m_strProtocol.CompareNoCase("pvr") == 0
318 || m_strProtocol.Left(3).CompareNoCase("mem") == 0)
320 if (m_strHostName != "" && m_strFileName != "")
322 m_strFileName = StringUtils::Format("%s/%s", m_strHostName.c_str(), m_strFileName.c_str());
327 if (!m_strHostName.IsEmpty() && strURL[iEnd-1]=='/')
328 m_strFileName = m_strHostName + "/";
330 m_strFileName = m_strHostName;
335 m_strFileName.Replace("\\", "/");
337 /* update extension */
338 SetFileName(m_strFileName);
340 /* decode urlencoding on this stuff */
341 if(URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
343 Decode(m_strHostName);
344 // Validate it as it is likely to contain a filename
345 SetHostName(CUtil::ValidatePath(m_strHostName));
348 Decode(m_strUserName);
349 Decode(m_strPassword);
352 void CURL::SetFileName(const CStdString& strFileName)
354 m_strFileName = strFileName;
356 int slash = m_strFileName.find_last_of(GetDirectorySeparator());
357 int period = m_strFileName.find_last_of('.');
358 if(period != -1 && (slash == -1 || period > slash))
359 m_strFileType = m_strFileName.substr(period+1);
363 m_strFileType.Normalize();
366 void CURL::SetHostName(const CStdString& strHostName)
368 m_strHostName = strHostName;
371 void CURL::SetUserName(const CStdString& strUserName)
373 m_strUserName = strUserName;
376 void CURL::SetPassword(const CStdString& strPassword)
378 m_strPassword = strPassword;
381 void CURL::SetProtocol(const CStdString& strProtocol)
383 m_strProtocol = strProtocol;
384 m_strProtocol.ToLower();
387 void CURL::SetOptions(const CStdString& strOptions)
389 m_strOptions.Empty();
391 if( strOptions.length() > 0)
393 if( strOptions[0] == '?' || strOptions[0] == '#' || strOptions[0] == ';' || strOptions.Find("xml") >=0 )
395 m_strOptions = strOptions;
396 m_options.AddOptions(m_strOptions);
399 CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
403 void CURL::SetProtocolOptions(const CStdString& strOptions)
405 m_strProtocolOptions.Empty();
406 m_protocolOptions.Clear();
407 if (strOptions.length() > 0)
409 if (strOptions[0] == '|')
410 m_strProtocolOptions = strOptions.Mid(1);
412 m_strProtocolOptions = strOptions;
413 m_protocolOptions.AddOptions(m_strProtocolOptions);
417 void CURL::SetPort(int port)
422 bool CURL::HasPort() const
424 return (m_iPort != 0);
427 int CURL::GetPort() const
433 const CStdString& CURL::GetHostName() const
435 return m_strHostName;
438 const CStdString& CURL::GetShareName() const
440 return m_strShareName;
443 const CStdString& CURL::GetDomain() const
448 const CStdString& CURL::GetUserName() const
450 return m_strUserName;
453 const CStdString& CURL::GetPassWord() const
455 return m_strPassword;
458 const CStdString& CURL::GetFileName() const
460 return m_strFileName;
463 const CStdString& CURL::GetProtocol() const
465 return m_strProtocol;
468 const CStdString CURL::GetTranslatedProtocol() const
470 return TranslateProtocol(m_strProtocol);
473 const CStdString& CURL::GetFileType() const
475 return m_strFileType;
478 const CStdString& CURL::GetOptions() const
483 const CStdString& CURL::GetProtocolOptions() const
485 return m_strProtocolOptions;
488 const CStdString CURL::GetFileNameWithoutPath() const
490 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
491 if ((m_strProtocol == "rar" ||
492 m_strProtocol == "zip" ||
493 m_strProtocol == "apk") &&
494 m_strFileName.IsEmpty())
495 return URIUtils::GetFileName(m_strHostName);
497 // otherwise, we've already got the filepath, so just grab the filename portion
498 CStdString file(m_strFileName);
499 URIUtils::RemoveSlashAtEnd(file);
500 return URIUtils::GetFileName(file);
503 char CURL::GetDirectorySeparator() const
513 CStdString CURL::Get() const
515 unsigned int sizeneed = m_strProtocol.length()
516 + m_strDomain.length()
517 + m_strUserName.length()
518 + m_strPassword.length()
519 + m_strHostName.length()
520 + m_strFileName.length()
521 + m_strOptions.length()
522 + m_strProtocolOptions.length()
525 if (m_strProtocol == "")
526 return m_strFileName;
529 strURL.reserve(sizeneed);
531 strURL = GetWithoutFilename();
532 strURL += m_strFileName;
534 if( !m_strOptions.empty() )
535 strURL += m_strOptions;
536 if (!m_strProtocolOptions.empty())
537 strURL += "|"+m_strProtocolOptions;
542 std::string CURL::GetWithoutUserDetails(bool redact) const
546 if (m_strProtocol.Equals("stack"))
551 XFILE::CStackDirectory dir;
552 dir.GetDirectory(strURL2,items);
553 vector<std::string> newItems;
554 for (int i=0;i<items.Size();++i)
556 CURL url(items[i]->GetPath());
557 items[i]->SetPath(url.GetWithoutUserDetails(redact));
558 newItems.push_back(items[i]->GetPath());
560 dir.ConstructStackPath(newItems, strURL);
564 unsigned int sizeneed = m_strProtocol.length()
565 + m_strDomain.length()
566 + m_strHostName.length()
567 + m_strFileName.length()
568 + m_strOptions.length()
569 + m_strProtocolOptions.length()
573 sizeneed += sizeof("USERNAME:PASSWORD@");
575 strURL.reserve(sizeneed);
577 if (m_strProtocol == "")
578 return m_strFileName;
580 strURL = m_strProtocol;
583 if (redact && !m_strUserName.empty())
585 strURL += "USERNAME";
586 if (!m_strPassword.empty())
588 strURL += ":PASSWORD";
593 if (!m_strHostName.empty())
595 std::string strHostName;
597 if (URIUtils::ProtocolHasParentInHostname(m_strProtocol))
598 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
600 strHostName = m_strHostName;
602 if (URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
603 strURL += URLEncodeInline(strHostName);
605 strURL += strHostName;
609 strURL += StringUtils::Format(":%i", m_iPort);
613 strURL += m_strFileName;
615 if( m_strOptions.length() > 0 )
616 strURL += m_strOptions;
617 if( m_strProtocolOptions.length() > 0 )
618 strURL += "|"+m_strProtocolOptions;
623 CStdString CURL::GetWithoutFilename() const
625 if (m_strProtocol == "")
628 unsigned int sizeneed = m_strProtocol.length()
629 + m_strDomain.length()
630 + m_strUserName.length()
631 + m_strPassword.length()
632 + m_strHostName.length()
636 strURL.reserve(sizeneed);
638 strURL = m_strProtocol;
641 if (m_strDomain != "")
643 strURL += m_strDomain;
646 else if (m_strUserName != "")
648 strURL += URLEncodeInline(m_strUserName);
649 if (m_strPassword != "")
652 strURL += URLEncodeInline(m_strPassword);
656 else if (m_strDomain != "")
659 if (m_strHostName != "")
661 if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol) )
662 strURL += URLEncodeInline(m_strHostName);
664 strURL += m_strHostName;
667 CStdString strPort = StringUtils::Format("%i", m_iPort);
677 std::string CURL::GetRedacted() const
679 return GetWithoutUserDetails(true);
682 std::string CURL::GetRedacted(const std::string& path)
684 return CURL(path).GetRedacted();
687 bool CURL::IsLocal() const
689 return (IsLocalHost() || m_strProtocol.IsEmpty());
692 bool CURL::IsLocalHost() const
694 return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
697 bool CURL::IsFileOnly(const CStdString &url)
699 return url.find_first_of("/\\") == CStdString::npos;
702 bool CURL::IsFullPath(const CStdString &url)
704 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
705 if (url.Find("://") >= 0) return true; // foo://bar.ext
706 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
707 if (StringUtils::StartsWith(url, "\\\\")) return true; // \\UNC\path\to\file
711 void CURL::Decode(CStdString& strURLData)
712 //modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
713 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
715 CStdString strResult;
717 /* result will always be less than source */
718 strResult.reserve( strURLData.length() );
720 for (unsigned int i = 0; i < strURLData.size(); ++i)
722 int kar = (unsigned char)strURLData[i];
723 if (kar == '+') strResult += ' ';
726 if (i < strURLData.size() - 2)
729 strTmp.assign(strURLData.substr(i + 1, 2));
731 sscanf(strTmp,"%x",(unsigned int *)&dec_num);
732 if (dec_num<0 || dec_num>255)
736 strResult += (char)dec_num;
743 else strResult += kar;
745 strURLData = strResult;
748 void CURL::Encode(CStdString& strURLData)
750 CStdString strResult;
752 /* wonder what a good value is here is, depends on how often it occurs */
753 strResult.reserve( strURLData.length() * 2 );
755 for (int i = 0; i < (int)strURLData.size(); ++i)
757 int kar = (unsigned char)strURLData[i];
758 //if (kar == ' ') strResult += '+'; // obsolete
759 if (isalnum(kar) || strchr("-_.!()" , kar) ) // Don't URL encode these according to RFC1738
765 CStdString strTmp = StringUtils::Format("%%%02.2x", kar);
769 strURLData = strResult;
772 std::string CURL::Decode(const std::string& strURLData)
774 CStdString url = strURLData;
779 std::string CURL::Encode(const std::string& strURLData)
781 CStdString url = strURLData;
786 CStdString CURL::TranslateProtocol(const CStdString& prot)
801 void CURL::GetOptions(std::map<CStdString, CStdString> &options) const
803 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
804 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
805 options[option->first] = option->second.asString();
808 bool CURL::HasOption(const CStdString &key) const
810 return m_options.HasOption(key);
813 bool CURL::GetOption(const CStdString &key, CStdString &value) const
816 if (!m_options.GetOption(key, valueObj))
819 value = valueObj.asString();
823 CStdString CURL::GetOption(const CStdString &key) const
826 if (!GetOption(key, value))
832 void CURL::SetOption(const CStdString &key, const CStdString &value)
834 m_options.AddOption(key, value);
835 SetOptions(m_options.GetOptionsString(true));
838 void CURL::RemoveOption(const CStdString &key)
840 m_options.RemoveOption(key);
841 SetOptions(m_options.GetOptionsString(true));
844 void CURL::GetProtocolOptions(std::map<CStdString, CStdString> &options) const
846 CUrlOptions::UrlOptions optionsMap = m_protocolOptions.GetOptions();
847 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
848 options[option->first] = option->second.asString();
851 bool CURL::HasProtocolOption(const CStdString &key) const
853 return m_protocolOptions.HasOption(key);
856 bool CURL::GetProtocolOption(const CStdString &key, CStdString &value) const
859 if (!m_protocolOptions.GetOption(key, valueObj))
862 value = valueObj.asString();
866 CStdString CURL::GetProtocolOption(const CStdString &key) const
869 if (!GetProtocolOption(key, value))
875 void CURL::SetProtocolOption(const CStdString &key, const CStdString &value)
877 m_protocolOptions.AddOption(key, value);
878 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);
881 void CURL::RemoveProtocolOption(const CStdString &key)
883 m_protocolOptions.RemoveOption(key);
884 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);