only call IPlayerCallback::OnPlayBackSpeedChanged if the speed has actually changed
[vuplus_xbmc] / xbmc / URL.cpp
1 /*
2  *      Copyright (C) 2005-2008 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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "URL.h"
23 #include "utils/RegExp.h"
24 #include "utils/log.h"
25 #include "utils/URIUtils.h"
26 #include "Util.h"
27 #include "filesystem/File.h"
28 #include "FileItem.h"
29 #include "filesystem/StackDirectory.h"
30 #include "addons/Addon.h"
31 #ifndef _LINUX
32 #include <sys\types.h>
33 #include <sys\stat.h>
34 #endif
35
36 using namespace std;
37 using namespace ADDON;
38
39 CStdString URLEncodeInline(const CStdString& strData)
40 {
41   CStdString buffer = strData;
42   CURL::Encode(buffer);
43   return buffer;
44 }
45
46 CURL::CURL(const CStdString& strURL1)
47 {
48   Parse(strURL1);
49 }
50
51 CURL::CURL()
52 {
53   m_iPort = 0;
54 }
55
56 CURL::~CURL()
57 {
58 }
59
60 void CURL::Reset()
61 {
62   m_strHostName.clear();
63   m_strDomain.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();
70   m_iPort = 0;
71 }
72
73 void CURL::Parse(const CStdString& strURL1)
74 {
75   Reset();
76   // start by validating the path
77   CStdString strURL = CUtil::ValidatePath(strURL1);
78
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
83   //
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;
87
88   // form is format 1 or 2
89   // format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
90   // format 2: protocol://file
91
92   // decode protocol
93   int iPos = strURL.Find("://");
94   if (iPos < 0)
95   {
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
99     iPos = 0;
100     while (1)
101     {
102       iPos = strURL.Find(".zip/", iPos);
103       int extLen = 3;
104       if (iPos < 0)
105       {
106         /* set filename and update extension*/
107         SetFileName(strURL);
108         return ;
109       }
110       iPos += extLen + 1;
111       CStdString archiveName = strURL.Left(iPos);
112       struct __stat64 s;
113       if (XFILE::CFile::Stat(archiveName, &s) == 0)
114       {
115 #ifdef _LINUX
116         if (!S_ISDIR(s.st_mode))
117 #else
118         if (!(s.st_mode & S_IFDIR))
119 #endif
120         {
121           Encode(archiveName);
122           CURL c((CStdString)"zip" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
123           *this = c;
124           return;
125         }
126       }
127     }
128   }
129   else
130   {
131     SetProtocol(strURL.Left(iPos));
132     iPos += 3;
133   }
134
135   // virtual protocols
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.
140   if (
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")
146     )
147   {
148     SetFileName(strURL.Mid(iPos));
149     return;
150   }
151
152   // check for username/password - should occur before first /
153   if (iPos == -1) iPos = 0;
154
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;
159
160   CStdString strProtocol2 = GetTranslatedProtocol();
161   if(m_strProtocol.Equals("rss") ||
162      m_strProtocol.Equals("addons"))
163     sep = "?";
164   else
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"))
171     sep = "?;#|";
172   else if(strProtocol2.Equals("ftp")
173        || strProtocol2.Equals("ftps"))
174     sep = "?;";
175
176   if(sep)
177   {
178     int iOptions = strURL.find_first_of(sep, iPos);
179     if (iOptions >= 0 )
180     {
181       // we keep the initial char as it can be any of the above
182       int iProto = strURL.find_first_of("|",iOptions);
183       if (iProto >= 0)
184       {
185         m_strProtocolOptions = strURL.substr(iProto+1);
186         m_strOptions = strURL.substr(iOptions,iProto-iOptions);
187       }
188       else
189         m_strOptions = strURL.substr(iOptions);
190       iEnd = iOptions;
191     }
192   }
193
194   int iSlash = strURL.Find("/", iPos);
195   if(iSlash >= iEnd)
196     iSlash = -1; // was an invalid slash as it was contained in options
197
198   if( !m_strProtocol.Equals("iso9660") )
199   {
200     int iAlphaSign = strURL.Find("@", iPos);
201     if (iAlphaSign >= 0 && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash < 0))
202     {
203       // username/password found
204       CStdString strUserNamePassword = strURL.Mid(iPos, iAlphaSign - iPos);
205
206       // first extract domain, if protocol is smb
207       if (m_strProtocol.Equals("smb"))
208       {
209         int iSemiColon = strUserNamePassword.Find(";");
210
211         if (iSemiColon >= 0)
212         {
213           m_strDomain = strUserNamePassword.Left(iSemiColon);
214           strUserNamePassword.Delete(0, iSemiColon + 1);
215         }
216       }
217
218       // username:password
219       int iColon = strUserNamePassword.Find(":");
220       if (iColon >= 0)
221       {
222         m_strUserName = strUserNamePassword.Left(iColon);
223         iColon++;
224         m_strPassword = strUserNamePassword.Right(strUserNamePassword.size() - iColon);
225       }
226       // username
227       else
228       {
229         m_strUserName = strUserNamePassword;
230       }
231
232       iPos = iAlphaSign + 1;
233       iSlash = strURL.Find("/", iAlphaSign);
234
235       if(iSlash >= iEnd)
236         iSlash = -1;
237     }
238   }
239
240   // detect hostname:port/
241   if (iSlash < 0)
242   {
243     CStdString strHostNameAndPort = strURL.Mid(iPos, iEnd - iPos);
244     int iColon = strHostNameAndPort.Find(":");
245     if (iColon >= 0)
246     {
247       m_strHostName = strHostNameAndPort.Left(iColon);
248       iColon++;
249       CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
250       m_iPort = atoi(strPort.c_str());
251     }
252     else
253     {
254       m_strHostName = strHostNameAndPort;
255     }
256
257   }
258   else
259   {
260     CStdString strHostNameAndPort = strURL.Mid(iPos, iSlash - iPos);
261     int iColon = strHostNameAndPort.Find(":");
262     if (iColon >= 0)
263     {
264       m_strHostName = strHostNameAndPort.Left(iColon);
265       iColon++;
266       CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
267       m_iPort = atoi(strPort.c_str());
268     }
269     else
270     {
271       m_strHostName = strHostNameAndPort;
272     }
273     iPos = iSlash + 1;
274     if (iEnd > iPos)
275     {
276       m_strFileName = strURL.Mid(iPos, iEnd - iPos);
277
278       iSlash = m_strFileName.Find("/");
279       if(iSlash < 0)
280         m_strShareName = m_strFileName;
281       else
282         m_strShareName = m_strFileName.Left(iSlash);
283     }
284   }
285
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)
293   {
294     if (m_strHostName != "" && m_strFileName != "")
295     {
296       CStdString strFileName = m_strFileName;
297       m_strFileName.Format("%s/%s", m_strHostName.c_str(), strFileName.c_str());
298       m_strHostName = "";
299     }
300     else
301     {
302       if (!m_strHostName.IsEmpty() && strURL[iEnd-1]=='/')
303         m_strFileName = m_strHostName + "/";
304       else
305         m_strFileName = m_strHostName;
306       m_strHostName = "";
307     }
308   }
309
310   m_strFileName.Replace("\\", "/");
311
312   /* update extension */
313   SetFileName(m_strFileName);
314
315   /* decode urlencoding on this stuff */
316   if( m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip") || m_strProtocol.Equals("musicsearch"))
317   {
318     Decode(m_strHostName);
319     // Validate it as it is likely to contain a filename
320     SetHostName(CUtil::ValidatePath(m_strHostName));
321   }
322
323   Decode(m_strUserName);
324   Decode(m_strPassword);
325 }
326
327 void CURL::SetFileName(const CStdString& strFileName)
328 {
329   m_strFileName = strFileName;
330
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);
335   else
336     m_strFileType = "";
337
338   m_strFileType.Normalize();
339 }
340
341 void CURL::SetHostName(const CStdString& strHostName)
342 {
343   m_strHostName = strHostName;
344 }
345
346 void CURL::SetUserName(const CStdString& strUserName)
347 {
348   m_strUserName = strUserName;
349 }
350
351 void CURL::SetPassword(const CStdString& strPassword)
352 {
353   m_strPassword = strPassword;
354 }
355
356 void CURL::SetProtocol(const CStdString& strProtocol)
357 {
358   m_strProtocol = strProtocol;
359   m_strProtocol.ToLower();
360 }
361
362 void CURL::SetOptions(const CStdString& strOptions)
363 {
364   m_strOptions.Empty();
365   if( strOptions.length() > 0)
366   {
367     if( strOptions[0] == '?' || strOptions[0] == '#' || strOptions[0] == ';' || strOptions.Find("xml") >=0 )
368     {
369       m_strOptions = strOptions;
370     }
371     else
372       CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
373   }
374 }
375
376 void CURL::SetProtocolOptions(const CStdString& strOptions)
377 {
378   m_strProtocolOptions = strOptions;
379 }
380
381 void CURL::SetPort(int port)
382 {
383   m_iPort = port;
384 }
385
386 bool CURL::HasPort() const
387 {
388   return (m_iPort != 0);
389 }
390
391 int CURL::GetPort() const
392 {
393   return m_iPort;
394 }
395
396
397 const CStdString& CURL::GetHostName() const
398 {
399   return m_strHostName;
400 }
401
402 const CStdString&  CURL::GetShareName() const
403 {
404   return m_strShareName;
405 }
406
407 const CStdString& CURL::GetDomain() const
408 {
409   return m_strDomain;
410 }
411
412 const CStdString& CURL::GetUserName() const
413 {
414   return m_strUserName;
415 }
416
417 const CStdString& CURL::GetPassWord() const
418 {
419   return m_strPassword;
420 }
421
422 const CStdString& CURL::GetFileName() const
423 {
424   return m_strFileName;
425 }
426
427 const CStdString& CURL::GetProtocol() const
428 {
429   return m_strProtocol;
430 }
431
432 const CStdString CURL::GetTranslatedProtocol() const
433 {
434   if (m_strProtocol == "ftpx")
435     return "ftp";
436
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")
444    return "http";
445   
446   if (m_strProtocol == "davs")
447     return "https";
448   
449   return m_strProtocol;
450 }
451
452 const CStdString& CURL::GetFileType() const
453 {
454   return m_strFileType;
455 }
456
457 const CStdString& CURL::GetOptions() const
458 {
459   return m_strOptions;
460 }
461
462 const CStdString& CURL::GetProtocolOptions() const
463 {
464   return m_strProtocolOptions;
465 }
466
467 const CStdString CURL::GetFileNameWithoutPath() const
468 {
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);
472
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);
477 }
478
479 char CURL::GetDirectorySeparator() const
480 {
481 #ifndef _LINUX
482   if ( IsLocal() )
483     return '\\';
484   else
485 #endif
486     return '/';
487 }
488
489 CStdString CURL::Get() const
490 {
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()
499                         + 10;
500
501   if (m_strProtocol == "")
502     return m_strFileName;
503
504   CStdString strURL;
505   strURL.reserve(sizeneed);
506
507   strURL = GetWithoutFilename();
508   strURL += m_strFileName;
509
510   if( m_strOptions.length() > 0 )
511     strURL += m_strOptions;
512   if (m_strProtocolOptions.length() > 0)
513     strURL += "|"+m_strProtocolOptions;
514
515   return strURL;
516 }
517
518 CStdString CURL::GetWithoutUserDetails() const
519 {
520   CStdString strURL;
521
522   if (m_strProtocol.Equals("stack"))
523   {
524     CFileItemList items;
525     CStdString strURL2;
526     strURL2 = Get();
527     XFILE::CStackDirectory dir;
528     dir.GetDirectory(strURL2,items);
529     vector<CStdString> newItems;
530     for (int i=0;i<items.Size();++i)
531     {
532       CURL url(items[i]->GetPath());
533       items[i]->SetPath(url.GetWithoutUserDetails());
534       newItems.push_back(items[i]->GetPath());
535     }
536     dir.ConstructStackPath(newItems,strURL);
537     return strURL;
538   }
539
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()
546                         + 10;
547
548   strURL.reserve(sizeneed);
549
550   if (m_strProtocol == "")
551     return m_strFileName;
552
553   strURL = m_strProtocol;
554   strURL += "://";
555
556   if (m_strHostName != "")
557   {
558     if (m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip"))
559       strURL += CURL(m_strHostName).GetWithoutUserDetails();
560     else
561       strURL += m_strHostName;
562
563     if ( HasPort() )
564     {
565       CStdString strPort;
566       strPort.Format("%i", m_iPort);
567       strURL += ":";
568       strURL += strPort;
569     }
570     strURL += "/";
571   }
572   strURL += m_strFileName;
573
574   if( m_strOptions.length() > 0 )
575     strURL += m_strOptions;
576   if( m_strProtocolOptions.length() > 0 )
577     strURL += "|"+m_strProtocolOptions;
578
579   return strURL;
580 }
581
582 CStdString CURL::GetWithoutFilename() const
583 {
584   if (m_strProtocol == "")
585     return "";
586
587   unsigned int sizeneed = m_strProtocol.length()
588                         + m_strDomain.length()
589                         + m_strUserName.length()
590                         + m_strPassword.length()
591                         + m_strHostName.length()
592                         + 10;
593
594   CStdString strURL;
595   strURL.reserve(sizeneed);
596
597   strURL = m_strProtocol;
598   strURL += "://";
599
600   if (m_strDomain != "")
601   {
602     strURL += m_strDomain;
603     strURL += ";";
604   }
605   else if (m_strUserName != "")
606   {
607     strURL += URLEncodeInline(m_strUserName);
608     if (m_strPassword != "")
609     {
610       strURL += ":";
611       strURL += URLEncodeInline(m_strPassword);
612     }
613     strURL += "@";
614   }
615   else if (m_strDomain != "")
616     strURL += "@";
617
618   if (m_strHostName != "")
619   {
620     if( m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip") || m_strProtocol.Equals("musicsearch"))
621       strURL += URLEncodeInline(m_strHostName);
622     else
623       strURL += m_strHostName;
624     if (HasPort())
625     {
626       CStdString strPort;
627       strPort.Format("%i", m_iPort);
628       strURL += ":";
629       strURL += strPort;
630     }
631     strURL += "/";
632   }
633
634   return strURL;
635 }
636
637 bool CURL::IsLocal() const
638 {
639   return (IsLocalHost() || m_strProtocol.IsEmpty());
640 }
641
642 bool CURL::IsLocalHost() const
643 {
644   return (m_strHostName.Equals("localhost") || m_strHostName.Equals("127.0.0.1"));
645 }
646
647 bool CURL::IsFileOnly(const CStdString &url)
648 {
649   return url.find_first_of("/\\") == CStdString::npos;
650 }
651
652 bool CURL::IsFullPath(const CStdString &url)
653 {
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
657   return false;
658 }
659
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)
663 {
664   CStdString strResult;
665
666   /* result will always be less than source */
667   strResult.reserve( strURLData.length() );
668
669   for (unsigned int i = 0; i < strURLData.size(); ++i)
670   {
671     int kar = (unsigned char)strURLData[i];
672     if (kar == '+') strResult += ' ';
673     else if (kar == '%')
674     {
675       if (i < strURLData.size() - 2)
676       {
677         CStdString strTmp;
678         strTmp.assign(strURLData.substr(i + 1, 2));
679         int dec_num=-1;
680         sscanf(strTmp,"%x",(unsigned int *)&dec_num);
681         if (dec_num<0 || dec_num>255)
682           strResult += kar;
683         else
684         {
685           strResult += (char)dec_num;
686           i += 2;
687         }
688       }
689       else
690         strResult += kar;
691     }
692     else strResult += kar;
693   }
694   strURLData = strResult;
695 }
696
697 void CURL::Encode(CStdString& strURLData)
698 {
699   CStdString strResult;
700
701   /* wonder what a good value is here is, depends on how often it occurs */
702   strResult.reserve( strURLData.length() * 2 );
703
704   for (int i = 0; i < (int)strURLData.size(); ++i)
705   {
706     int kar = (unsigned char)strURLData[i];
707     //if (kar == ' ') strResult += '+';
708     if (isalnum(kar)) strResult += kar;
709     else
710     {
711       CStdString strTmp;
712       strTmp.Format("%%%02.2x", kar);
713       strResult += strTmp;
714     }
715   }
716   strURLData = strResult;
717 }