2 * Copyright (C) 2005-2008 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, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 #include "utils/RegExp.h"
24 #include "utils/log.h"
25 #include "utils/URIUtils.h"
27 #include "filesystem/File.h"
29 #include "filesystem/StackDirectory.h"
30 #include "addons/Addon.h"
32 #include <sys\types.h>
37 using namespace ADDON;
39 CStdString URLEncodeInline(const CStdString& strData)
41 CStdString buffer = strData;
46 CURL::CURL(const CStdString& strURL1)
62 m_strHostName.clear();
64 m_strUserName.clear();
65 m_strPassword.clear();
66 m_strShareName.clear();
67 m_strFileName.clear();
68 m_strProtocol.clear();
69 m_strFileType.clear();
73 void CURL::Parse(const CStdString& strURL1)
76 // start by validating the path
77 CStdString strURL = CUtil::ValidatePath(strURL1);
79 // strURL can be one of the following:
80 // format 1: protocol://[username:password]@hostname[:port]/directoryandfile
81 // format 2: protocol://file
82 // format 3: drive:directoryandfile
84 // first need 2 check if this is a protocol or just a normal drive & path
85 if (!strURL.size()) return ;
86 if (strURL.Equals("?", true)) return;
88 // form is format 1 or 2
89 // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
90 // format 2: protocol://file
93 int iPos = strURL.Find("://");
96 // This is an ugly hack that needs some work.
97 // example: filename /foo/bar.zip/alice.rar/bob.avi
98 // This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
102 iPos = strURL.Find(".zip/", iPos);
106 /* set filename and update extension*/
111 CStdString archiveName = strURL.Left(iPos);
113 if (XFILE::CFile::Stat(archiveName, &s) == 0)
116 if (!S_ISDIR(s.st_mode))
118 if (!(s.st_mode & S_IFDIR))
122 CURL c((CStdString)"zip" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
131 SetProtocol(strURL.Left(iPos));
136 // why not handle all format 2 (protocol://file) style urls here?
137 // ones that come to mind are iso9660, cdda, musicdb, etc.
138 // they are all local protocols and have no server part, port number, special options, etc.
139 // this removes the need for special handling below.
141 m_strProtocol.Equals("stack") ||
142 m_strProtocol.Equals("virtualpath") ||
143 m_strProtocol.Equals("multipath") ||
144 m_strProtocol.Equals("filereader") ||
145 m_strProtocol.Equals("special")
148 SetFileName(strURL.Mid(iPos));
152 // check for username/password - should occur before first /
153 if (iPos == -1) iPos = 0;
155 // for protocols supporting options, chop that part off here
156 // maybe we should invert this list instead?
157 int iEnd = strURL.length();
158 const char* sep = NULL;
160 CStdString strProtocol2 = GetTranslatedProtocol();
161 if(m_strProtocol.Equals("rss") ||
162 m_strProtocol.Equals("addons"))
165 if(strProtocol2.Equals("http")
166 || strProtocol2.Equals("https")
167 || strProtocol2.Equals("plugin")
168 || strProtocol2.Equals("hdhomerun")
169 || strProtocol2.Equals("rtsp")
170 || strProtocol2.Equals("zip"))
172 else if(strProtocol2.Equals("ftp")
173 || strProtocol2.Equals("ftps"))
178 int iOptions = strURL.find_first_of(sep, iPos);
181 // we keep the initial char as it can be any of the above
182 int iProto = strURL.find_first_of("|",iOptions);
185 m_strProtocolOptions = strURL.substr(iProto+1);
186 m_strOptions = strURL.substr(iOptions,iProto-iOptions);
189 m_strOptions = strURL.substr(iOptions);
194 int iSlash = strURL.Find("/", iPos);
196 iSlash = -1; // was an invalid slash as it was contained in options
198 if( !m_strProtocol.Equals("iso9660") )
200 int iAlphaSign = strURL.Find("@", iPos);
201 if (iAlphaSign >= 0 && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash < 0))
203 // username/password found
204 CStdString strUserNamePassword = strURL.Mid(iPos, iAlphaSign - iPos);
206 // first extract domain, if protocol is smb
207 if (m_strProtocol.Equals("smb"))
209 int iSemiColon = strUserNamePassword.Find(";");
213 m_strDomain = strUserNamePassword.Left(iSemiColon);
214 strUserNamePassword.Delete(0, iSemiColon + 1);
219 int iColon = strUserNamePassword.Find(":");
222 m_strUserName = strUserNamePassword.Left(iColon);
224 m_strPassword = strUserNamePassword.Right(strUserNamePassword.size() - iColon);
229 m_strUserName = strUserNamePassword;
232 iPos = iAlphaSign + 1;
233 iSlash = strURL.Find("/", iAlphaSign);
240 // detect hostname:port/
243 CStdString strHostNameAndPort = strURL.Mid(iPos, iEnd - iPos);
244 int iColon = strHostNameAndPort.Find(":");
247 m_strHostName = strHostNameAndPort.Left(iColon);
249 CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
250 m_iPort = atoi(strPort.c_str());
254 m_strHostName = strHostNameAndPort;
260 CStdString strHostNameAndPort = strURL.Mid(iPos, iSlash - iPos);
261 int iColon = strHostNameAndPort.Find(":");
264 m_strHostName = strHostNameAndPort.Left(iColon);
266 CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
267 m_iPort = atoi(strPort.c_str());
271 m_strHostName = strHostNameAndPort;
276 m_strFileName = strURL.Mid(iPos, iEnd - iPos);
278 iSlash = m_strFileName.Find("/");
280 m_strShareName = m_strFileName;
282 m_strShareName = m_strFileName.Left(iSlash);
286 // iso9960 doesnt have an hostname;-)
287 if (m_strProtocol.CompareNoCase("iso9660") == 0
288 || m_strProtocol.CompareNoCase("musicdb") == 0
289 || m_strProtocol.CompareNoCase("videodb") == 0
290 || m_strProtocol.CompareNoCase("sources") == 0
291 || m_strProtocol.CompareNoCase("lastfm") == 0
292 || m_strProtocol.Left(3).CompareNoCase("mem") == 0)
294 if (m_strHostName != "" && m_strFileName != "")
296 CStdString strFileName = m_strFileName;
297 m_strFileName.Format("%s/%s", m_strHostName.c_str(), strFileName.c_str());
302 if (!m_strHostName.IsEmpty() && strURL[iEnd-1]=='/')
303 m_strFileName = m_strHostName + "/";
305 m_strFileName = m_strHostName;
310 m_strFileName.Replace("\\", "/");
312 /* update extension */
313 SetFileName(m_strFileName);
315 /* decode urlencoding on this stuff */
316 if( m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip") || m_strProtocol.Equals("musicsearch"))
318 Decode(m_strHostName);
319 // Validate it as it is likely to contain a filename
320 SetHostName(CUtil::ValidatePath(m_strHostName));
323 Decode(m_strUserName);
324 Decode(m_strPassword);
327 void CURL::SetFileName(const CStdString& strFileName)
329 m_strFileName = strFileName;
331 int slash = m_strFileName.find_last_of(GetDirectorySeparator());
332 int period = m_strFileName.find_last_of('.');
333 if(period != -1 && (slash == -1 || period > slash))
334 m_strFileType = m_strFileName.substr(period+1);
338 m_strFileType.Normalize();
341 void CURL::SetHostName(const CStdString& strHostName)
343 m_strHostName = strHostName;
346 void CURL::SetUserName(const CStdString& strUserName)
348 m_strUserName = strUserName;
351 void CURL::SetPassword(const CStdString& strPassword)
353 m_strPassword = strPassword;
356 void CURL::SetProtocol(const CStdString& strProtocol)
358 m_strProtocol = strProtocol;
359 m_strProtocol.ToLower();
362 void CURL::SetOptions(const CStdString& strOptions)
364 m_strOptions.Empty();
365 if( strOptions.length() > 0)
367 if( strOptions[0] == '?' || strOptions[0] == '#' || strOptions[0] == ';' || strOptions.Find("xml") >=0 )
369 m_strOptions = strOptions;
372 CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
376 void CURL::SetProtocolOptions(const CStdString& strOptions)
378 m_strProtocolOptions = strOptions;
381 void CURL::SetPort(int port)
386 bool CURL::HasPort() const
388 return (m_iPort != 0);
391 int CURL::GetPort() const
397 const CStdString& CURL::GetHostName() const
399 return m_strHostName;
402 const CStdString& CURL::GetShareName() const
404 return m_strShareName;
407 const CStdString& CURL::GetDomain() const
412 const CStdString& CURL::GetUserName() const
414 return m_strUserName;
417 const CStdString& CURL::GetPassWord() const
419 return m_strPassword;
422 const CStdString& CURL::GetFileName() const
424 return m_strFileName;
427 const CStdString& CURL::GetProtocol() const
429 return m_strProtocol;
432 const CStdString CURL::GetTranslatedProtocol() const
434 if (m_strProtocol == "ftpx")
437 if (m_strProtocol == "shout"
438 || m_strProtocol == "daap"
439 || m_strProtocol == "dav"
440 || m_strProtocol == "tuxbox"
441 || m_strProtocol == "lastfm"
442 || m_strProtocol == "mms"
443 || m_strProtocol == "rss")
446 if (m_strProtocol == "davs")
449 return m_strProtocol;
452 const CStdString& CURL::GetFileType() const
454 return m_strFileType;
457 const CStdString& CURL::GetOptions() const
462 const CStdString& CURL::GetProtocolOptions() const
464 return m_strProtocolOptions;
467 const CStdString CURL::GetFileNameWithoutPath() const
469 // *.zip and *.rar store the actual zip/rar path in the hostname of the url
470 if ((m_strProtocol == "rar" || m_strProtocol == "zip") && m_strFileName.IsEmpty())
471 return URIUtils::GetFileName(m_strHostName);
473 // otherwise, we've already got the filepath, so just grab the filename portion
474 CStdString file(m_strFileName);
475 URIUtils::RemoveSlashAtEnd(file);
476 return URIUtils::GetFileName(file);
479 char CURL::GetDirectorySeparator() const
489 CStdString CURL::Get() const
491 unsigned int sizeneed = m_strProtocol.length()
492 + m_strDomain.length()
493 + m_strUserName.length()
494 + m_strPassword.length()
495 + m_strHostName.length()
496 + m_strFileName.length()
497 + m_strOptions.length()
498 + m_strProtocolOptions.length()
501 if (m_strProtocol == "")
502 return m_strFileName;
505 strURL.reserve(sizeneed);
507 strURL = GetWithoutFilename();
508 strURL += m_strFileName;
510 if( m_strOptions.length() > 0 )
511 strURL += m_strOptions;
512 if (m_strProtocolOptions.length() > 0)
513 strURL += "|"+m_strProtocolOptions;
518 CStdString CURL::GetWithoutUserDetails() const
522 if (m_strProtocol.Equals("stack"))
527 XFILE::CStackDirectory dir;
528 dir.GetDirectory(strURL2,items);
529 vector<CStdString> newItems;
530 for (int i=0;i<items.Size();++i)
532 CURL url(items[i]->GetPath());
533 items[i]->SetPath(url.GetWithoutUserDetails());
534 newItems.push_back(items[i]->GetPath());
536 dir.ConstructStackPath(newItems,strURL);
540 unsigned int sizeneed = m_strProtocol.length()
541 + m_strDomain.length()
542 + m_strHostName.length()
543 + m_strFileName.length()
544 + m_strOptions.length()
545 + m_strProtocolOptions.length()
548 strURL.reserve(sizeneed);
550 if (m_strProtocol == "")
551 return m_strFileName;
553 strURL = m_strProtocol;
556 if (m_strHostName != "")
558 if (m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip"))
559 strURL += CURL(m_strHostName).GetWithoutUserDetails();
561 strURL += m_strHostName;
566 strPort.Format("%i", m_iPort);
572 strURL += m_strFileName;
574 if( m_strOptions.length() > 0 )
575 strURL += m_strOptions;
576 if( m_strProtocolOptions.length() > 0 )
577 strURL += "|"+m_strProtocolOptions;
582 CStdString CURL::GetWithoutFilename() const
584 if (m_strProtocol == "")
587 unsigned int sizeneed = m_strProtocol.length()
588 + m_strDomain.length()
589 + m_strUserName.length()
590 + m_strPassword.length()
591 + m_strHostName.length()
595 strURL.reserve(sizeneed);
597 strURL = m_strProtocol;
600 if (m_strDomain != "")
602 strURL += m_strDomain;
605 else if (m_strUserName != "")
607 strURL += URLEncodeInline(m_strUserName);
608 if (m_strPassword != "")
611 strURL += URLEncodeInline(m_strPassword);
615 else if (m_strDomain != "")
618 if (m_strHostName != "")
620 if( m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip") || m_strProtocol.Equals("musicsearch"))
621 strURL += URLEncodeInline(m_strHostName);
623 strURL += m_strHostName;
627 strPort.Format("%i", m_iPort);
637 bool CURL::IsLocal() const
639 return (IsLocalHost() || m_strProtocol.IsEmpty());
642 bool CURL::IsLocalHost() const
644 return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
647 bool CURL::IsFileOnly(const CStdString &url)
649 return url.find_first_of("/\\") == CStdString::npos;
652 bool CURL::IsFullPath(const CStdString &url)
654 if (url.size() && url[0] == '/') return true; // /foo/bar.ext
655 if (url.Find("://") >= 0) return true; // foo://bar.ext
656 if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
660 void CURL::Decode(CStdString& strURLData)
661 //modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
662 // However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
664 CStdString strResult;
666 /* result will always be less than source */
667 strResult.reserve( strURLData.length() );
669 for (unsigned int i = 0; i < strURLData.size(); ++i)
671 int kar = (unsigned char)strURLData[i];
672 if (kar == '+') strResult += ' ';
675 if (i < strURLData.size() - 2)
678 strTmp.assign(strURLData.substr(i + 1, 2));
680 sscanf(strTmp,"%x",(unsigned int *)&dec_num);
681 if (dec_num<0 || dec_num>255)
685 strResult += (char)dec_num;
692 else strResult += kar;
694 strURLData = strResult;
697 void CURL::Encode(CStdString& strURLData)
699 CStdString strResult;
701 /* wonder what a good value is here is, depends on how often it occurs */
702 strResult.reserve( strURLData.length() * 2 );
704 for (int i = 0; i < (int)strURLData.size(); ++i)
706 int kar = (unsigned char)strURLData[i];
707 //if (kar == ' ') strResult += '+';
708 if (isalnum(kar)) strResult += kar;
712 strTmp.Format("%%%02.2x", kar);
716 strURLData = strResult;