[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / URL.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://www.xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "URL.h"
22 #include "utils/RegExp.h"
23 #include "utils/log.h"
24 #include "utils/URIUtils.h"
25 #include "Util.h"
26 #include "filesystem/File.h"
27 #include "FileItem.h"
28 #include "filesystem/StackDirectory.h"
29 #include "addons/Addon.h"
30 #ifndef _LINUX
31 #include <sys\types.h>
32 #include <sys\stat.h>
33 #endif
34
35 using namespace std;
36 using namespace ADDON;
37
38 CStdString URLEncodeInline(const CStdString& strData)
39 {
40   CStdString buffer = strData;
41   CURL::Encode(buffer);
42   return buffer;
43 }
44
45 CURL::CURL(const CStdString& strURL1)
46 {
47   Parse(strURL1);
48 }
49
50 CURL::CURL()
51 {
52   m_iPort = 0;
53 }
54
55 CURL::~CURL()
56 {
57 }
58
59 void CURL::Reset()
60 {
61   m_strHostName.clear();
62   m_strDomain.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();
69   m_iPort = 0;
70 }
71
72 void CURL::Parse(const CStdString& strURL1)
73 {
74   Reset();
75   // start by validating the path
76   CStdString strURL = CUtil::ValidatePath(strURL1);
77
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
82   //
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;
86
87   // form is format 1 or 2
88   // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
89   // format 2: protocol://file
90
91   // decode protocol
92   int iPos = strURL.Find("://");
93   if (iPos < 0)
94   {
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
98     iPos = 0;
99     bool is_apk = (strURL.Find(".apk/", iPos) > 0);
100     while (1)
101     {
102       if (is_apk)
103         iPos = strURL.Find(".apk/", iPos);
104       else
105         iPos = strURL.Find(".zip/", iPos);
106
107       int extLen = 3;
108       if (iPos < 0)
109       {
110         /* set filename and update extension*/
111         SetFileName(strURL);
112         return ;
113       }
114       iPos += extLen + 1;
115       CStdString archiveName = strURL.Left(iPos);
116       struct __stat64 s;
117       if (XFILE::CFile::Stat(archiveName, &s) == 0)
118       {
119 #ifdef _LINUX
120         if (!S_ISDIR(s.st_mode))
121 #else
122         if (!(s.st_mode & S_IFDIR))
123 #endif
124         {
125           Encode(archiveName);
126           if (is_apk)
127           {
128             CURL c((CStdString)"apk" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
129             *this = c;
130           }
131           else
132           {
133             CURL c((CStdString)"zip" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
134             *this = c;
135           }
136           return;
137         }
138       }
139     }
140   }
141   else
142   {
143     SetProtocol(strURL.Left(iPos));
144     iPos += 3;
145   }
146
147   // virtual protocols
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.
152   if (
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")
158     )
159   {
160     SetFileName(strURL.Mid(iPos));
161     return;
162   }
163
164   // check for username/password - should occur before first /
165   if (iPos == -1) iPos = 0;
166
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;
171
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"))
181     sep = "?";
182   else
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"))
191     sep = "?;#|";
192   else if(strProtocol2.Equals("ftp")
193        || strProtocol2.Equals("ftps"))
194     sep = "?;";
195
196   if(sep)
197   {
198     int iOptions = strURL.find_first_of(sep, iPos);
199     if (iOptions >= 0 )
200     {
201       // we keep the initial char as it can be any of the above
202       int iProto = strURL.find_first_of("|",iOptions);
203       if (iProto >= 0)
204       {
205         m_strProtocolOptions = strURL.substr(iProto+1);
206         m_strOptions = strURL.substr(iOptions,iProto-iOptions);
207       }
208       else
209         m_strOptions = strURL.substr(iOptions);
210       iEnd = iOptions;
211       m_options.AddOptions(m_strOptions);
212     }
213   }
214
215   int iSlash = strURL.Find("/", iPos);
216   if(iSlash >= iEnd)
217     iSlash = -1; // was an invalid slash as it was contained in options
218
219   if( !m_strProtocol.Equals("iso9660") )
220   {
221     int iAlphaSign = strURL.Find("@", iPos);
222     if (iAlphaSign >= 0 && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash < 0))
223     {
224       // username/password found
225       CStdString strUserNamePassword = strURL.Mid(iPos, iAlphaSign - iPos);
226
227       // first extract domain, if protocol is smb
228       if (m_strProtocol.Equals("smb"))
229       {
230         int iSemiColon = strUserNamePassword.Find(";");
231
232         if (iSemiColon >= 0)
233         {
234           m_strDomain = strUserNamePassword.Left(iSemiColon);
235           strUserNamePassword.Delete(0, iSemiColon + 1);
236         }
237       }
238
239       // username:password
240       int iColon = strUserNamePassword.Find(":");
241       if (iColon >= 0)
242       {
243         m_strUserName = strUserNamePassword.Left(iColon);
244         iColon++;
245         m_strPassword = strUserNamePassword.Right(strUserNamePassword.size() - iColon);
246       }
247       // username
248       else
249       {
250         m_strUserName = strUserNamePassword;
251       }
252
253       iPos = iAlphaSign + 1;
254       iSlash = strURL.Find("/", iAlphaSign);
255
256       if(iSlash >= iEnd)
257         iSlash = -1;
258     }
259   }
260
261   // detect hostname:port/
262   if (iSlash < 0)
263   {
264     CStdString strHostNameAndPort = strURL.Mid(iPos, iEnd - iPos);
265     int iColon = strHostNameAndPort.Find(":");
266     if (iColon >= 0)
267     {
268       m_strHostName = strHostNameAndPort.Left(iColon);
269       iColon++;
270       CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
271       m_iPort = atoi(strPort.c_str());
272     }
273     else
274     {
275       m_strHostName = strHostNameAndPort;
276     }
277
278   }
279   else
280   {
281     CStdString strHostNameAndPort = strURL.Mid(iPos, iSlash - iPos);
282     int iColon = strHostNameAndPort.Find(":");
283     if (iColon >= 0)
284     {
285       m_strHostName = strHostNameAndPort.Left(iColon);
286       iColon++;
287       CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
288       m_iPort = atoi(strPort.c_str());
289     }
290     else
291     {
292       m_strHostName = strHostNameAndPort;
293     }
294     iPos = iSlash + 1;
295     if (iEnd > iPos)
296     {
297       m_strFileName = strURL.Mid(iPos, iEnd - iPos);
298
299       iSlash = m_strFileName.Find("/");
300       if(iSlash < 0)
301         m_strShareName = m_strFileName;
302       else
303         m_strShareName = m_strFileName.Left(iSlash);
304     }
305   }
306
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)
315   {
316     if (m_strHostName != "" && m_strFileName != "")
317     {
318       CStdString strFileName = m_strFileName;
319       m_strFileName.Format("%s/%s", m_strHostName.c_str(), strFileName.c_str());
320       m_strHostName = "";
321     }
322     else
323     {
324       if (!m_strHostName.IsEmpty() && strURL[iEnd-1]=='/')
325         m_strFileName = m_strHostName + "/";
326       else
327         m_strFileName = m_strHostName;
328       m_strHostName = "";
329     }
330   }
331
332   m_strFileName.Replace("\\", "/");
333
334   /* update extension */
335   SetFileName(m_strFileName);
336
337   /* decode urlencoding on this stuff */
338   if(URIUtils::ProtocolHasEncodedHostname(m_strProtocol))
339   {
340     Decode(m_strHostName);
341     // Validate it as it is likely to contain a filename
342     SetHostName(CUtil::ValidatePath(m_strHostName));
343   }
344
345   Decode(m_strUserName);
346   Decode(m_strPassword);
347 }
348
349 void CURL::SetFileName(const CStdString& strFileName)
350 {
351   m_strFileName = strFileName;
352
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);
357   else
358     m_strFileType = "";
359
360   m_strFileType.Normalize();
361 }
362
363 void CURL::SetHostName(const CStdString& strHostName)
364 {
365   m_strHostName = strHostName;
366 }
367
368 void CURL::SetUserName(const CStdString& strUserName)
369 {
370   m_strUserName = strUserName;
371 }
372
373 void CURL::SetPassword(const CStdString& strPassword)
374 {
375   m_strPassword = strPassword;
376 }
377
378 void CURL::SetProtocol(const CStdString& strProtocol)
379 {
380   m_strProtocol = strProtocol;
381   m_strProtocol.ToLower();
382 }
383
384 void CURL::SetOptions(const CStdString& strOptions)
385 {
386   m_strOptions.Empty();
387   m_options.Clear();
388   if( strOptions.length() > 0)
389   {
390     if( strOptions[0] == '?' || strOptions[0] == '#' || strOptions[0] == ';' || strOptions.Find("xml") >=0 )
391     {
392       m_strOptions = strOptions;
393       m_options.AddOptions(m_strOptions);
394     }
395     else
396       CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
397   }
398 }
399
400 void CURL::SetProtocolOptions(const CStdString& strOptions)
401 {
402   m_strProtocolOptions = strOptions;
403 }
404
405 void CURL::SetPort(int port)
406 {
407   m_iPort = port;
408 }
409
410 bool CURL::HasPort() const
411 {
412   return (m_iPort != 0);
413 }
414
415 int CURL::GetPort() const
416 {
417   return m_iPort;
418 }
419
420
421 const CStdString& CURL::GetHostName() const
422 {
423   return m_strHostName;
424 }
425
426 const CStdString&  CURL::GetShareName() const
427 {
428   return m_strShareName;
429 }
430
431 const CStdString& CURL::GetDomain() const
432 {
433   return m_strDomain;
434 }
435
436 const CStdString& CURL::GetUserName() const
437 {
438   return m_strUserName;
439 }
440
441 const CStdString& CURL::GetPassWord() const
442 {
443   return m_strPassword;
444 }
445
446 const CStdString& CURL::GetFileName() const
447 {
448   return m_strFileName;
449 }
450
451 const CStdString& CURL::GetProtocol() const
452 {
453   return m_strProtocol;
454 }
455
456 const CStdString CURL::GetTranslatedProtocol() const
457 {
458   return TranslateProtocol(m_strProtocol);
459 }
460
461 const CStdString& CURL::GetFileType() const
462 {
463   return m_strFileType;
464 }
465
466 const CStdString& CURL::GetOptions() const
467 {
468   return m_strOptions;
469 }
470
471 const CStdString& CURL::GetProtocolOptions() const
472 {
473   return m_strProtocolOptions;
474 }
475
476 const CStdString CURL::GetFileNameWithoutPath() const
477 {
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);
484
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);
489 }
490
491 char CURL::GetDirectorySeparator() const
492 {
493 #ifndef _LINUX
494   if ( IsLocal() )
495     return '\\';
496   else
497 #endif
498     return '/';
499 }
500
501 CStdString CURL::Get() const
502 {
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()
511                         + 10;
512
513   if (m_strProtocol == "")
514     return m_strFileName;
515
516   CStdString strURL;
517   strURL.reserve(sizeneed);
518
519   strURL = GetWithoutFilename();
520   strURL += m_strFileName;
521
522   if( m_strOptions.length() > 0 )
523     strURL += m_strOptions;
524   if (m_strProtocolOptions.length() > 0)
525     strURL += "|"+m_strProtocolOptions;
526
527   return strURL;
528 }
529
530 CStdString CURL::GetWithoutUserDetails() const
531 {
532   CStdString strURL;
533
534   if (m_strProtocol.Equals("stack"))
535   {
536     CFileItemList items;
537     CStdString strURL2;
538     strURL2 = Get();
539     XFILE::CStackDirectory dir;
540     dir.GetDirectory(strURL2,items);
541     vector<CStdString> newItems;
542     for (int i=0;i<items.Size();++i)
543     {
544       CURL url(items[i]->GetPath());
545       items[i]->SetPath(url.GetWithoutUserDetails());
546       newItems.push_back(items[i]->GetPath());
547     }
548     dir.ConstructStackPath(newItems,strURL);
549     return strURL;
550   }
551
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()
558                         + 10;
559
560   strURL.reserve(sizeneed);
561
562   if (m_strProtocol == "")
563     return m_strFileName;
564
565   strURL = m_strProtocol;
566   strURL += "://";
567
568   if (m_strHostName != "")
569   {
570     if (URIUtils::ProtocolHasParentInHostname(m_strProtocol))
571       strURL += CURL(m_strHostName).GetWithoutUserDetails();
572     else
573       strURL += m_strHostName;
574
575     if ( HasPort() )
576     {
577       CStdString strPort;
578       strPort.Format("%i", m_iPort);
579       strURL += ":";
580       strURL += strPort;
581     }
582     strURL += "/";
583   }
584   strURL += m_strFileName;
585
586   if( m_strOptions.length() > 0 )
587     strURL += m_strOptions;
588   if( m_strProtocolOptions.length() > 0 )
589     strURL += "|"+m_strProtocolOptions;
590
591   return strURL;
592 }
593
594 CStdString CURL::GetWithoutFilename() const
595 {
596   if (m_strProtocol == "")
597     return "";
598
599   unsigned int sizeneed = m_strProtocol.length()
600                         + m_strDomain.length()
601                         + m_strUserName.length()
602                         + m_strPassword.length()
603                         + m_strHostName.length()
604                         + 10;
605
606   CStdString strURL;
607   strURL.reserve(sizeneed);
608
609   strURL = m_strProtocol;
610   strURL += "://";
611
612   if (m_strDomain != "")
613   {
614     strURL += m_strDomain;
615     strURL += ";";
616   }
617   else if (m_strUserName != "")
618   {
619     strURL += URLEncodeInline(m_strUserName);
620     if (m_strPassword != "")
621     {
622       strURL += ":";
623       strURL += URLEncodeInline(m_strPassword);
624     }
625     strURL += "@";
626   }
627   else if (m_strDomain != "")
628     strURL += "@";
629
630   if (m_strHostName != "")
631   {
632     if( URIUtils::ProtocolHasEncodedHostname(m_strProtocol) )
633       strURL += URLEncodeInline(m_strHostName);
634     else
635       strURL += m_strHostName;
636     if (HasPort())
637     {
638       CStdString strPort;
639       strPort.Format("%i", m_iPort);
640       strURL += ":";
641       strURL += strPort;
642     }
643     strURL += "/";
644   }
645
646   return strURL;
647 }
648
649 bool CURL::IsLocal() const
650 {
651   return (IsLocalHost() || m_strProtocol.IsEmpty());
652 }
653
654 bool CURL::IsLocalHost() const
655 {
656   return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
657 }
658
659 bool CURL::IsFileOnly(const CStdString &url)
660 {
661   return url.find_first_of("/\\") == CStdString::npos;
662 }
663
664 bool CURL::IsFullPath(const CStdString &url)
665 {
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
670   return false;
671 }
672
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)
676 {
677   CStdString strResult;
678
679   /* result will always be less than source */
680   strResult.reserve( strURLData.length() );
681
682   for (unsigned int i = 0; i < strURLData.size(); ++i)
683   {
684     int kar = (unsigned char)strURLData[i];
685     if (kar == '+') strResult += ' ';
686     else if (kar == '%')
687     {
688       if (i < strURLData.size() - 2)
689       {
690         CStdString strTmp;
691         strTmp.assign(strURLData.substr(i + 1, 2));
692         int dec_num=-1;
693         sscanf(strTmp,"%x",(unsigned int *)&dec_num);
694         if (dec_num<0 || dec_num>255)
695           strResult += kar;
696         else
697         {
698           strResult += (char)dec_num;
699           i += 2;
700         }
701       }
702       else
703         strResult += kar;
704     }
705     else strResult += kar;
706   }
707   strURLData = strResult;
708 }
709
710 void CURL::Encode(CStdString& strURLData)
711 {
712   CStdString strResult;
713
714   /* wonder what a good value is here is, depends on how often it occurs */
715   strResult.reserve( strURLData.length() * 2 );
716
717   for (int i = 0; i < (int)strURLData.size(); ++i)
718   {
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
722     {
723       strResult += kar;
724     }
725     else
726     {
727       CStdString strTmp;
728       strTmp.Format("%%%02.2x", kar);
729       strResult += strTmp;
730     }
731   }
732   strURLData = strResult;
733 }
734
735 std::string CURL::Decode(const std::string& strURLData)
736 {
737   CStdString url = strURLData;
738   Decode(url);
739   return url;
740 }
741
742 std::string CURL::Encode(const std::string& strURLData)
743 {
744   CStdString url = strURLData;
745   Encode(url);
746   return url;
747 }
748
749 CStdString CURL::TranslateProtocol(const CStdString& prot)
750 {
751   if (prot == "shout"
752    || prot == "daap"
753    || prot == "dav"
754    || prot == "tuxbox"
755    || prot == "lastfm"
756    || prot == "rss")
757    return "http";
758
759   if (prot == "davs")
760     return "https";
761
762   return prot;
763 }
764
765 void CURL::GetOptions(std::map<CStdString, CStdString> &options) const
766 {
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();
770 }
771
772 bool CURL::HasOption(const CStdString &key) const
773 {
774   return m_options.HasOption(key);
775 }
776
777 bool CURL::GetOption(const CStdString &key, CStdString &value) const
778 {
779   CVariant valueObj;
780   if (!m_options.GetOption(key, valueObj))
781     return false;
782
783   value = valueObj.asString();
784   return true;
785 }
786
787 CStdString CURL::GetOption(const CStdString &key) const
788 {
789   CStdString value;
790   if (!GetOption(key, value))
791     return "";
792
793   return value;
794 }
795
796 void CURL::SetOption(const CStdString &key, const CStdString &value)
797 {
798   m_options.AddOption(key, value);
799   SetOptions(m_options.GetOptionsString(true));
800 }
801
802 void CURL::RemoveOption(const CStdString &key)
803 {
804   m_options.RemoveOption(key);
805   SetOptions(m_options.GetOptionsString(true));
806 }