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 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))
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.CompareNoCase("iso9660") == 0
309 || m_strProtocol.CompareNoCase("musicdb") == 0
310 || m_strProtocol.CompareNoCase("videodb") == 0
311 || m_strProtocol.CompareNoCase("sources") == 0
312 || m_strProtocol.CompareNoCase("pvr") == 0
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 // Validate it as it is likely to contain a filename
340 SetHostName(CUtil::ValidatePath(m_strHostName));
343 Decode(m_strUserName);
344 Decode(m_strPassword);
347 void CURL::SetFileName(const CStdString& strFileName)
349 m_strFileName = strFileName;
351 int slash = m_strFileName.find_last_of(GetDirectorySeparator());
352 int period = m_strFileName.find_last_of('.');
353 if(period != -1 && (slash == -1 || period > slash))
354 m_strFileType = m_strFileName.substr(period+1);
358 StringUtils::Trim(m_strFileType);
359 StringUtils::ToLower(m_strFileType);
362 void CURL::SetHostName(const CStdString& strHostName)
364 m_strHostName = strHostName;
367 void CURL::SetUserName(const CStdString& strUserName)
369 m_strUserName = strUserName;
372 void CURL::SetPassword(const CStdString& strPassword)
374 m_strPassword = strPassword;
377 void CURL::SetProtocol(const CStdString& strProtocol)
379 m_strProtocol = strProtocol;
380 StringUtils::ToLower(m_strProtocol);
383 void CURL::SetOptions(const CStdString& strOptions)
385 m_strOptions.clear();
387 if( strOptions.length() > 0)
389 if(strOptions[0] == '?' ||
390 strOptions[0] == '#' ||
391 strOptions[0] == ';' ||
392 strOptions.find("xml") != std::string::npos)
394 m_strOptions = strOptions;
395 m_options.AddOptions(m_strOptions);
398 CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
402 void CURL::SetProtocolOptions(const CStdString& strOptions)
404 m_strProtocolOptions.clear();
405 m_protocolOptions.Clear();
406 if (strOptions.length() > 0)
408 if (strOptions[0] == '|')
409 m_strProtocolOptions = strOptions.substr(1);
411 m_strProtocolOptions = strOptions;
412 m_protocolOptions.AddOptions(m_strProtocolOptions);
416 void CURL::SetPort(int port)
421 bool CURL::HasPort() const
423 return (m_iPort != 0);
426 int CURL::GetPort() const
432 const CStdString& CURL::GetHostName() const
434 return m_strHostName;
437 const CStdString& CURL::GetShareName() const
439 return m_strShareName;
442 const CStdString& CURL::GetDomain() const
447 const CStdString& CURL::GetUserName() const
449 return m_strUserName;
452 const CStdString& CURL::GetPassWord() const
454 return m_strPassword;
457 const CStdString& CURL::GetFileName() const
459 return m_strFileName;
462 const CStdString& CURL::GetProtocol() const
464 return m_strProtocol;
467 const CStdString CURL::GetTranslatedProtocol() const
469 return TranslateProtocol(m_strProtocol);
472 const CStdString& CURL::GetFileType() const
474 return m_strFileType;
477 const CStdString& CURL::GetOptions() const
482 const CStdString& CURL::GetProtocolOptions() const
484 return m_strProtocolOptions;
487 const CStdString CURL::GetFileNameWithoutPath() const
489 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
490 if ((m_strProtocol == "rar" ||
491 m_strProtocol == "zip" ||
492 m_strProtocol == "apk") &&
493 m_strFileName.empty())
494 return URIUtils::GetFileName(m_strHostName);
496 // otherwise, we've already got the filepath, so just grab the filename portion
497 CStdString file(m_strFileName);
498 URIUtils::RemoveSlashAtEnd(file);
499 return URIUtils::GetFileName(file);
502 char CURL::GetDirectorySeparator() const
512 CStdString CURL::Get() const
514 unsigned int sizeneed = m_strProtocol.length()
515 + m_strDomain.length()
516 + m_strUserName.length()
517 + m_strPassword.length()
518 + m_strHostName.length()
519 + m_strFileName.length()
520 + m_strOptions.length()
521 + m_strProtocolOptions.length()
524 if (m_strProtocol == "")
525 return m_strFileName;
528 strURL.reserve(sizeneed);
530 strURL = GetWithoutFilename();
531 strURL += m_strFileName;
533 if( !m_strOptions.empty() )
534 strURL += m_strOptions;
535 if (!m_strProtocolOptions.empty())
536 strURL += "|"+m_strProtocolOptions;
541 std::string CURL::GetWithoutUserDetails(bool redact) const
545 if (m_strProtocol.Equals("stack"))
550 XFILE::CStackDirectory dir;
551 dir.GetDirectory(strURL2,items);
552 vector<std::string> newItems;
553 for (int i=0;i<items.Size();++i)
555 CURL url(items[i]->GetPath());
556 items[i]->SetPath(url.GetWithoutUserDetails(redact));
557 newItems.push_back(items[i]->GetPath());
559 dir.ConstructStackPath(newItems, strURL);
563 unsigned int sizeneed = m_strProtocol.length()
564 + m_strDomain.length()
565 + m_strHostName.length()
566 + m_strFileName.length()
567 + m_strOptions.length()
568 + m_strProtocolOptions.length()
572 sizeneed += sizeof("USERNAME:PASSWORD@");
574 strURL.reserve(sizeneed);
576 if (m_strProtocol == "")
577 return m_strFileName;
579 strURL = m_strProtocol;
582 if (redact && !m_strUserName.empty())
584 strURL += "USERNAME";
585 if (!m_strPassword.empty())
587 strURL += ":PASSWORD";
592 if (!m_strHostName.empty())
594 std::string strHostName;
596 if (URIUtils::ProtocolHasParentInHostname(m_strProtocol))
597 strHostName = CURL(m_strHostName).GetWithoutUserDetails();
599 strHostName = m_strHostName;
601 if (URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
602 strURL += URLEncodeInline(strHostName);
604 strURL += strHostName;
608 strURL += StringUtils::Format(":%i", m_iPort);
612 strURL += m_strFileName;
614 if( m_strOptions.length() > 0 )
615 strURL += m_strOptions;
616 if( m_strProtocolOptions.length() > 0 )
617 strURL += "|"+m_strProtocolOptions;
622 CStdString CURL::GetWithoutFilename() const
624 if (m_strProtocol == "")
627 unsigned int sizeneed = m_strProtocol.length()
628 + m_strDomain.length()
629 + m_strUserName.length()
630 + m_strPassword.length()
631 + m_strHostName.length()
635 strURL.reserve(sizeneed);
637 strURL = m_strProtocol;
640 if (m_strDomain != "")
642 strURL += m_strDomain;
645 else if (m_strUserName != "")
647 strURL += URLEncodeInline(m_strUserName);
648 if (m_strPassword != "")
651 strURL += URLEncodeInline(m_strPassword);
655 else if (m_strDomain != "")
658 if (m_strHostName != "")
660 if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol) )
661 strURL += URLEncodeInline(m_strHostName);
663 strURL += m_strHostName;
666 CStdString strPort = StringUtils::Format("%i", m_iPort);
676 std::string CURL::GetRedacted() const
678 return GetWithoutUserDetails(true);
681 std::string CURL::GetRedacted(const std::string& path)
683 return CURL(path).GetRedacted();
686 bool CURL::IsLocal() const
688 return (IsLocalHost() || m_strProtocol.empty());
691 bool CURL::IsLocalHost() const
693 return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
696 bool CURL::IsFileOnly(const CStdString &url)
698 return url.find_first_of("/\\") == CStdString::npos;
701 bool CURL::IsFullPath(const CStdString &url)
703 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
704 if (url.find("://") != std::string::npos) return true; // foo://bar.ext
705 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
706 if (StringUtils::StartsWith(url, "\\\\")) return true; // \\UNC\path\to\file
710 void CURL::Decode(CStdString& strURLData)
711 //modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
712 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
714 CStdString strResult;
716 /* result will always be less than source */
717 strResult.reserve( strURLData.length() );
719 for (unsigned int i = 0; i < strURLData.size(); ++i)
721 int kar = (unsigned char)strURLData[i];
722 if (kar == '+') strResult += ' ';
725 if (i < strURLData.size() - 2)
728 strTmp.assign(strURLData.substr(i + 1, 2));
730 sscanf(strTmp,"%x",(unsigned int *)&dec_num);
731 if (dec_num<0 || dec_num>255)
735 strResult += (char)dec_num;
742 else strResult += kar;
744 strURLData = strResult;
747 void CURL::Encode(CStdString& strURLData)
749 CStdString strResult;
751 /* wonder what a good value is here is, depends on how often it occurs */
752 strResult.reserve( strURLData.length() * 2 );
754 for (int i = 0; i < (int)strURLData.size(); ++i)
756 int kar = (unsigned char)strURLData[i];
757 //if (kar == ' ') strResult += '+'; // obsolete
758 if (isalnum(kar) || strchr("-_.!()" , kar) ) // Don't URL encode these according to RFC1738
764 CStdString strTmp = StringUtils::Format("%%%02.2x", kar);
768 strURLData = strResult;
771 std::string CURL::Decode(const std::string& strURLData)
773 CStdString url = strURLData;
778 std::string CURL::Encode(const std::string& strURLData)
780 CStdString url = strURLData;
785 CStdString CURL::TranslateProtocol(const CStdString& prot)
800 void CURL::GetOptions(std::map<CStdString, CStdString> &options) const
802 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
803 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
804 options[option->first] = option->second.asString();
807 bool CURL::HasOption(const CStdString &key) const
809 return m_options.HasOption(key);
812 bool CURL::GetOption(const CStdString &key, CStdString &value) const
815 if (!m_options.GetOption(key, valueObj))
818 value = valueObj.asString();
822 CStdString CURL::GetOption(const CStdString &key) const
825 if (!GetOption(key, value))
831 void CURL::SetOption(const CStdString &key, const CStdString &value)
833 m_options.AddOption(key, value);
834 SetOptions(m_options.GetOptionsString(true));
837 void CURL::RemoveOption(const CStdString &key)
839 m_options.RemoveOption(key);
840 SetOptions(m_options.GetOptionsString(true));
843 void CURL::GetProtocolOptions(std::map<CStdString, CStdString> &options) const
845 CUrlOptions::UrlOptions optionsMap = m_protocolOptions.GetOptions();
846 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
847 options[option->first] = option->second.asString();
850 bool CURL::HasProtocolOption(const CStdString &key) const
852 return m_protocolOptions.HasOption(key);
855 bool CURL::GetProtocolOption(const CStdString &key, CStdString &value) const
858 if (!m_protocolOptions.GetOption(key, valueObj))
861 value = valueObj.asString();
865 CStdString CURL::GetProtocolOption(const CStdString &key) const
868 if (!GetProtocolOption(key, value))
874 void CURL::SetProtocolOption(const CStdString &key, const CStdString &value)
876 m_protocolOptions.AddOption(key, value);
877 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);
880 void CURL::RemoveProtocolOption(const CStdString &key)
882 m_protocolOptions.RemoveOption(key);
883 m_strProtocolOptions = m_protocolOptions.GetOptionsString(false);