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"
26 #include "filesystem/File.h"
28 #include "filesystem/StackDirectory.h"
29 #include "addons/Addon.h"
31 #include <sys\types.h>
36 using namespace ADDON;
38 CStdString URLEncodeInline(const CStdString& strData)
40 CStdString buffer = strData;
45 CURL::CURL(const CStdString& strURL1)
61 m_strHostName.clear();
63 m_strUserName.clear();
64 m_strPassword.clear();
65 m_strShareName.clear();
66 m_strFileName.clear();
67 m_strProtocol.clear();
68 m_strFileType.clear();
72 void CURL::Parse(const CStdString& strURL1)
75 // start by validating the path
76 CStdString strURL = CUtil::ValidatePath(strURL1);
78 // strURL can be one of the following:
79 // format 1: protocol://[username:password]@hostname[:port]/directoryandfile
80 // format 2: protocol://file
81 // format 3: drive:directoryandfile
83 // first need 2 check if this is a protocol or just a normal drive & path
84 if (!strURL.size()) return ;
85 if (strURL.Equals("?", true)) return;
87 // form is format 1 or 2
88 // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
89 // format 2: protocol://file
92 int iPos = strURL.Find("://");
95 // This is an ugly hack that needs some work.
96 // example: filename /foo/bar.zip/alice.rar/bob.avi
97 // This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
99 bool is_apk = (strURL.Find(".apk/", iPos) > 0);
103 iPos = strURL.Find(".apk/", iPos);
105 iPos = strURL.Find(".zip/", iPos);
110 /* set filename and update extension*/
115 CStdString archiveName = strURL.Left(iPos);
117 if (XFILE::CFile::Stat(archiveName, &s) == 0)
120 if (!S_ISDIR(s.st_mode))
122 if (!(s.st_mode & S_IFDIR))
128 CURL c((CStdString)"apk" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
133 CURL c((CStdString)"zip" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
143 SetProtocol(strURL.Left(iPos));
148 // why not handle all format 2 (protocol://file) style urls here?
149 // ones that come to mind are iso9660, cdda, musicdb, etc.
150 // they are all local protocols and have no server part, port number, special options, etc.
151 // this removes the need for special handling below.
153 m_strProtocol.Equals("stack") ||
154 m_strProtocol.Equals("virtualpath") ||
155 m_strProtocol.Equals("multipath") ||
156 m_strProtocol.Equals("filereader") ||
157 m_strProtocol.Equals("special")
160 SetFileName(strURL.Mid(iPos));
164 // check for username/password - should occur before first /
165 if (iPos == -1) iPos = 0;
167 // for protocols supporting options, chop that part off here
168 // maybe we should invert this list instead?
169 int iEnd = strURL.length();
170 const char* sep = NULL;
172 //TODO fix all Addon paths
173 CStdString strProtocol2 = GetTranslatedProtocol();
174 if(m_strProtocol.Equals("rss") ||
175 m_strProtocol.Equals("rar") ||
176 m_strProtocol.Equals("addons") ||
177 m_strProtocol.Equals("image") ||
178 m_strProtocol.Equals("videodb") ||
179 m_strProtocol.Equals("musicdb") ||
180 m_strProtocol.Equals("androidapp"))
183 if(strProtocol2.Equals("http")
184 || strProtocol2.Equals("https")
185 || strProtocol2.Equals("plugin")
186 || strProtocol2.Equals("addons")
187 || strProtocol2.Equals("hdhomerun")
188 || strProtocol2.Equals("rtsp")
189 || strProtocol2.Equals("apk")
190 || strProtocol2.Equals("zip"))
192 else if(strProtocol2.Equals("ftp")
193 || strProtocol2.Equals("ftps"))
198 int iOptions = strURL.find_first_of(sep, iPos);
201 // we keep the initial char as it can be any of the above
202 int iProto = strURL.find_first_of("|",iOptions);
205 m_strProtocolOptions = strURL.substr(iProto+1);
206 m_strOptions = strURL.substr(iOptions,iProto-iOptions);
209 m_strOptions = strURL.substr(iOptions);
211 m_options.AddOptions(m_strOptions);
215 int iSlash = strURL.Find("/", iPos);
217 iSlash = -1; // was an invalid slash as it was contained in options
219 if( !m_strProtocol.Equals("iso9660") )
221 int iAlphaSign = strURL.Find("@", iPos);
222 if (iAlphaSign >= 0 && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash < 0))
224 // username/password found
225 CStdString strUserNamePassword = strURL.Mid(iPos, iAlphaSign - iPos);
227 // first extract domain, if protocol is smb
228 if (m_strProtocol.Equals("smb"))
230 int iSemiColon = strUserNamePassword.Find(";");
234 m_strDomain = strUserNamePassword.Left(iSemiColon);
235 strUserNamePassword.Delete(0, iSemiColon + 1);
240 int iColon = strUserNamePassword.Find(":");
243 m_strUserName = strUserNamePassword.Left(iColon);
245 m_strPassword = strUserNamePassword.Right(strUserNamePassword.size() - iColon);
250 m_strUserName = strUserNamePassword;
253 iPos = iAlphaSign + 1;
254 iSlash = strURL.Find("/", iAlphaSign);
261 // detect hostname:port/
264 CStdString strHostNameAndPort = strURL.Mid(iPos, iEnd - iPos);
265 int iColon = strHostNameAndPort.Find(":");
268 m_strHostName = strHostNameAndPort.Left(iColon);
270 CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
271 m_iPort = atoi(strPort.c_str());
275 m_strHostName = strHostNameAndPort;
281 CStdString strHostNameAndPort = strURL.Mid(iPos, iSlash - iPos);
282 int iColon = strHostNameAndPort.Find(":");
285 m_strHostName = strHostNameAndPort.Left(iColon);
287 CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
288 m_iPort = atoi(strPort.c_str());
292 m_strHostName = strHostNameAndPort;
297 m_strFileName = strURL.Mid(iPos, iEnd - iPos);
299 iSlash = m_strFileName.Find("/");
301 m_strShareName = m_strFileName;
303 m_strShareName = m_strFileName.Left(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("lastfm") == 0
313 || m_strProtocol.CompareNoCase("pvr") == 0
314 || m_strProtocol.Left(3).CompareNoCase("mem") == 0)
316 if (m_strHostName != "" && m_strFileName != "")
318 CStdString strFileName = m_strFileName;
319 m_strFileName.Format("%s/%s", m_strHostName.c_str(), strFileName.c_str());
324 if (!m_strHostName.IsEmpty() && strURL[iEnd-1]=='/')
325 m_strFileName = m_strHostName + "/";
327 m_strFileName = m_strHostName;
332 m_strFileName.Replace("\\", "/");
334 /* update extension */
335 SetFileName(m_strFileName);
337 /* decode urlencoding on this stuff */
338 if(URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
340 Decode(m_strHostName);
341 // Validate it as it is likely to contain a filename
342 SetHostName(CUtil::ValidatePath(m_strHostName));
345 Decode(m_strUserName);
346 Decode(m_strPassword);
349 void CURL::SetFileName(const CStdString& strFileName)
351 m_strFileName = strFileName;
353 int slash = m_strFileName.find_last_of(GetDirectorySeparator());
354 int period = m_strFileName.find_last_of('.');
355 if(period != -1 && (slash == -1 || period > slash))
356 m_strFileType = m_strFileName.substr(period+1);
360 m_strFileType.Normalize();
363 void CURL::SetHostName(const CStdString& strHostName)
365 m_strHostName = strHostName;
368 void CURL::SetUserName(const CStdString& strUserName)
370 m_strUserName = strUserName;
373 void CURL::SetPassword(const CStdString& strPassword)
375 m_strPassword = strPassword;
378 void CURL::SetProtocol(const CStdString& strProtocol)
380 m_strProtocol = strProtocol;
381 m_strProtocol.ToLower();
384 void CURL::SetOptions(const CStdString& strOptions)
386 m_strOptions.Empty();
388 if( strOptions.length() > 0)
390 if( strOptions[0] == '?' || strOptions[0] == '#' || strOptions[0] == ';' || strOptions.Find("xml") >=0 )
392 m_strOptions = strOptions;
393 m_options.AddOptions(m_strOptions);
396 CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
400 void CURL::SetProtocolOptions(const CStdString& strOptions)
402 m_strProtocolOptions = strOptions;
405 void CURL::SetPort(int port)
410 bool CURL::HasPort() const
412 return (m_iPort != 0);
415 int CURL::GetPort() const
421 const CStdString& CURL::GetHostName() const
423 return m_strHostName;
426 const CStdString& CURL::GetShareName() const
428 return m_strShareName;
431 const CStdString& CURL::GetDomain() const
436 const CStdString& CURL::GetUserName() const
438 return m_strUserName;
441 const CStdString& CURL::GetPassWord() const
443 return m_strPassword;
446 const CStdString& CURL::GetFileName() const
448 return m_strFileName;
451 const CStdString& CURL::GetProtocol() const
453 return m_strProtocol;
456 const CStdString CURL::GetTranslatedProtocol() const
458 return TranslateProtocol(m_strProtocol);
461 const CStdString& CURL::GetFileType() const
463 return m_strFileType;
466 const CStdString& CURL::GetOptions() const
471 const CStdString& CURL::GetProtocolOptions() const
473 return m_strProtocolOptions;
476 const CStdString CURL::GetFileNameWithoutPath() const
478 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
479 if ((m_strProtocol == "rar" ||
480 m_strProtocol == "zip" ||
481 m_strProtocol == "apk") &&
482 m_strFileName.IsEmpty())
483 return URIUtils::GetFileName(m_strHostName);
485 // otherwise, we've already got the filepath, so just grab the filename portion
486 CStdString file(m_strFileName);
487 URIUtils::RemoveSlashAtEnd(file);
488 return URIUtils::GetFileName(file);
491 char CURL::GetDirectorySeparator() const
501 CStdString CURL::Get() const
503 unsigned int sizeneed = m_strProtocol.length()
504 + m_strDomain.length()
505 + m_strUserName.length()
506 + m_strPassword.length()
507 + m_strHostName.length()
508 + m_strFileName.length()
509 + m_strOptions.length()
510 + m_strProtocolOptions.length()
513 if (m_strProtocol == "")
514 return m_strFileName;
517 strURL.reserve(sizeneed);
519 strURL = GetWithoutFilename();
520 strURL += m_strFileName;
522 if( m_strOptions.length() > 0 )
523 strURL += m_strOptions;
524 if (m_strProtocolOptions.length() > 0)
525 strURL += "|"+m_strProtocolOptions;
530 CStdString CURL::GetWithoutUserDetails() const
534 if (m_strProtocol.Equals("stack"))
539 XFILE::CStackDirectory dir;
540 dir.GetDirectory(strURL2,items);
541 vector<CStdString> newItems;
542 for (int i=0;i<items.Size();++i)
544 CURL url(items[i]->GetPath());
545 items[i]->SetPath(url.GetWithoutUserDetails());
546 newItems.push_back(items[i]->GetPath());
548 dir.ConstructStackPath(newItems,strURL);
552 unsigned int sizeneed = m_strProtocol.length()
553 + m_strDomain.length()
554 + m_strHostName.length()
555 + m_strFileName.length()
556 + m_strOptions.length()
557 + m_strProtocolOptions.length()
560 strURL.reserve(sizeneed);
562 if (m_strProtocol == "")
563 return m_strFileName;
565 strURL = m_strProtocol;
568 if (m_strHostName != "")
570 if (URIUtils::ProtocolHasParentInHostname(m_strProtocol))
571 strURL += CURL(m_strHostName).GetWithoutUserDetails();
573 strURL += m_strHostName;
578 strPort.Format("%i", m_iPort);
584 strURL += m_strFileName;
586 if( m_strOptions.length() > 0 )
587 strURL += m_strOptions;
588 if( m_strProtocolOptions.length() > 0 )
589 strURL += "|"+m_strProtocolOptions;
594 CStdString CURL::GetWithoutFilename() const
596 if (m_strProtocol == "")
599 unsigned int sizeneed = m_strProtocol.length()
600 + m_strDomain.length()
601 + m_strUserName.length()
602 + m_strPassword.length()
603 + m_strHostName.length()
607 strURL.reserve(sizeneed);
609 strURL = m_strProtocol;
612 if (m_strDomain != "")
614 strURL += m_strDomain;
617 else if (m_strUserName != "")
619 strURL += URLEncodeInline(m_strUserName);
620 if (m_strPassword != "")
623 strURL += URLEncodeInline(m_strPassword);
627 else if (m_strDomain != "")
630 if (m_strHostName != "")
632 if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol) )
633 strURL += URLEncodeInline(m_strHostName);
635 strURL += m_strHostName;
639 strPort.Format("%i", m_iPort);
649 bool CURL::IsLocal() const
651 return (IsLocalHost() || m_strProtocol.IsEmpty());
654 bool CURL::IsLocalHost() const
656 return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
659 bool CURL::IsFileOnly(const CStdString &url)
661 return url.find_first_of("/\\") == CStdString::npos;
664 bool CURL::IsFullPath(const CStdString &url)
666 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
667 if (url.Find("://") >= 0) return true; // foo://bar.ext
668 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
669 if (url.compare(0,2,"\\\\") == 0) return true; // \\UNC\path\to\file
673 void CURL::Decode(CStdString& strURLData)
674 //modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
675 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
677 CStdString strResult;
679 /* result will always be less than source */
680 strResult.reserve( strURLData.length() );
682 for (unsigned int i = 0; i < strURLData.size(); ++i)
684 int kar = (unsigned char)strURLData[i];
685 if (kar == '+') strResult += ' ';
688 if (i < strURLData.size() - 2)
691 strTmp.assign(strURLData.substr(i + 1, 2));
693 sscanf(strTmp,"%x",(unsigned int *)&dec_num);
694 if (dec_num<0 || dec_num>255)
698 strResult += (char)dec_num;
705 else strResult += kar;
707 strURLData = strResult;
710 void CURL::Encode(CStdString& strURLData)
712 CStdString strResult;
714 /* wonder what a good value is here is, depends on how often it occurs */
715 strResult.reserve( strURLData.length() * 2 );
717 for (int i = 0; i < (int)strURLData.size(); ++i)
719 int kar = (unsigned char)strURLData[i];
720 //if (kar == ' ') strResult += '+'; // obsolete
721 if (isalnum(kar) || strchr("-_.!()" , kar) ) // Don't URL encode these according to RFC1738
728 strTmp.Format("%%%02.2x", kar);
732 strURLData = strResult;
735 std::string CURL::Decode(const std::string& strURLData)
737 CStdString url = strURLData;
742 std::string CURL::Encode(const std::string& strURLData)
744 CStdString url = strURLData;
749 CStdString CURL::TranslateProtocol(const CStdString& prot)
765 void CURL::GetOptions(std::map<CStdString, CStdString> &options) const
767 CUrlOptions::UrlOptions optionsMap = m_options.GetOptions();
768 for (CUrlOptions::UrlOptions::const_iterator option = optionsMap.begin(); option != optionsMap.end(); option++)
769 options[option->first] = option->second.asString();
772 bool CURL::HasOption(const CStdString &key) const
774 return m_options.HasOption(key);
777 bool CURL::GetOption(const CStdString &key, CStdString &value) const
780 if (!m_options.GetOption(key, valueObj))
783 value = valueObj.asString();
787 CStdString CURL::GetOption(const CStdString &key) const
790 if (!GetOption(key, value))
796 void CURL::SetOption(const CStdString &key, const CStdString &value)
798 m_options.AddOption(key, value);
799 SetOptions(m_options.GetOptionsString(true));
802 void CURL::RemoveOption(const CStdString &key)
804 m_options.RemoveOption(key);
805 SetOptions(m_options.GetOptionsString(true));