[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / network / AirPlayServer.cpp
1 /*
2  * Many concepts and protocol specification in this code are taken
3  * from Airplayer. https://github.com/PascalW/Airplayer
4  *
5  *      http://xbmc.org
6  *
7  *  This Program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2.1, or (at your option)
10  *  any later version.
11  *
12  *  This Program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with XBMC; see the file COPYING.  If not, see
19  *  <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 #include "network/Network.h"
24 #include "AirPlayServer.h"
25
26 #ifdef HAS_AIRPLAY
27
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include "DllLibPlist.h"
31 #include "utils/log.h"
32 #include "utils/URIUtils.h"
33 #include "utils/StringUtils.h"
34 #include "threads/SingleLock.h"
35 #include "filesystem/File.h"
36 #include "FileItem.h"
37 #include "Application.h"
38 #include "ApplicationMessenger.h"
39 #include "utils/md5.h"
40 #include "utils/Variant.h"
41 #include "settings/Settings.h"
42 #include "guilib/Key.h"
43 #include "URL.h"
44 #include "cores/IPlayer.h"
45 #include "interfaces/AnnouncementManager.h"
46
47 using namespace ANNOUNCEMENT;
48
49 #ifdef TARGET_WINDOWS
50 #define close closesocket
51 #endif
52
53 #define RECEIVEBUFFER 1024
54
55 #define AIRPLAY_STATUS_OK                  200
56 #define AIRPLAY_STATUS_SWITCHING_PROTOCOLS 101
57 #define AIRPLAY_STATUS_NEED_AUTH           401
58 #define AIRPLAY_STATUS_NOT_FOUND           404
59 #define AIRPLAY_STATUS_METHOD_NOT_ALLOWED  405
60 #define AIRPLAY_STATUS_NOT_IMPLEMENTED     501
61 #define AIRPLAY_STATUS_NO_RESPONSE_NEEDED  1000
62
63 CAirPlayServer *CAirPlayServer::ServerInstance = NULL;
64 int CAirPlayServer::m_isPlaying = 0;
65
66 #define EVENT_NONE     -1
67 #define EVENT_PLAYING   0
68 #define EVENT_PAUSED    1
69 #define EVENT_LOADING   2
70 #define EVENT_STOPPED   3
71 const char *eventStrings[] = {"playing", "paused", "loading", "stopped"};
72
73 #define PLAYBACK_INFO  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
74 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
75 "<plist version=\"1.0\">\r\n"\
76 "<dict>\r\n"\
77 "<key>duration</key>\r\n"\
78 "<real>%f</real>\r\n"\
79 "<key>loadedTimeRanges</key>\r\n"\
80 "<array>\r\n"\
81 "\t\t<dict>\r\n"\
82 "\t\t\t<key>duration</key>\r\n"\
83 "\t\t\t<real>%f</real>\r\n"\
84 "\t\t\t<key>start</key>\r\n"\
85 "\t\t\t<real>0.0</real>\r\n"\
86 "\t\t</dict>\r\n"\
87 "</array>\r\n"\
88 "<key>playbackBufferEmpty</key>\r\n"\
89 "<true/>\r\n"\
90 "<key>playbackBufferFull</key>\r\n"\
91 "<false/>\r\n"\
92 "<key>playbackLikelyToKeepUp</key>\r\n"\
93 "<true/>\r\n"\
94 "<key>position</key>\r\n"\
95 "<real>%f</real>\r\n"\
96 "<key>rate</key>\r\n"\
97 "<real>%d</real>\r\n"\
98 "<key>readyToPlay</key>\r\n"\
99 "<true/>\r\n"\
100 "<key>seekableTimeRanges</key>\r\n"\
101 "<array>\r\n"\
102 "\t\t<dict>\r\n"\
103 "\t\t\t<key>duration</key>\r\n"\
104 "\t\t\t<real>%f</real>\r\n"\
105 "\t\t\t<key>start</key>\r\n"\
106 "\t\t\t<real>0.0</real>\r\n"\
107 "\t\t</dict>\r\n"\
108 "</array>\r\n"\
109 "</dict>\r\n"\
110 "</plist>\r\n"
111
112 #define PLAYBACK_INFO_NOT_READY  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
113 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
114 "<plist version=\"1.0\">\r\n"\
115 "<dict>\r\n"\
116 "<key>readyToPlay</key>\r\n"\
117 "<false/>\r\n"\
118 "</dict>\r\n"\
119 "</plist>\r\n"
120
121 #define SERVER_INFO  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
122 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
123 "<plist version=\"1.0\">\r\n"\
124 "<dict>\r\n"\
125 "<key>deviceid</key>\r\n"\
126 "<string>%s</string>\r\n"\
127 "<key>features</key>\r\n"\
128 "<integer>119</integer>\r\n"\
129 "<key>model</key>\r\n"\
130 "<string>Xbmc,1</string>\r\n"\
131 "<key>protovers</key>\r\n"\
132 "<string>1.0</string>\r\n"\
133 "<key>srcvers</key>\r\n"\
134 "<string>"AIRPLAY_SERVER_VERSION_STR"</string>\r\n"\
135 "</dict>\r\n"\
136 "</plist>\r\n"
137
138 #define EVENT_INFO "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r\n"\
139 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\r\n"\
140 "<plist version=\"1.0\">\r\n"\
141 "<dict>\r\n"\
142 "<key>category</key>\r\n"\
143 "<string>video</string>\r\n"\
144 "<key>sessionID</key>\r\n"\
145 "<integer>%d</integer>\r\n"\
146 "<key>state</key>\r\n"\
147 "<string>%s</string>\r\n"\
148 "</dict>\r\n"\
149 "</plist>\r\n"\
150
151 #define AUTH_REALM "AirPlay"
152 #define AUTH_REQUIRED "WWW-Authenticate: Digest realm=\""  AUTH_REALM  "\", nonce=\"%s\"\r\n"
153
154 void CAirPlayServer::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
155 {
156   if ( (flag & Player) && strcmp(sender, "xbmc") == 0 && ServerInstance)
157   {
158     if (strcmp(message, "OnStop") == 0)
159     {
160       restoreVolume();
161       ServerInstance->AnnounceToClients(EVENT_STOPPED);
162     }
163     else if (strcmp(message, "OnPlay") == 0)
164     {
165       ServerInstance->AnnounceToClients(EVENT_PLAYING);
166     }
167     else if (strcmp(message, "OnPause") == 0)
168     {
169       ServerInstance->AnnounceToClients(EVENT_PAUSED);
170     }
171   }
172 }
173
174 bool CAirPlayServer::StartServer(int port, bool nonlocal)
175 {
176   StopServer(true);
177
178   ServerInstance = new CAirPlayServer(port, nonlocal);
179   if (ServerInstance->Initialize())
180   {
181     ServerInstance->Create();
182     return true;
183   }
184   else
185     return false;
186 }
187
188 bool CAirPlayServer::SetCredentials(bool usePassword, const CStdString& password)
189 {
190   bool ret = false;
191
192   if (ServerInstance)
193   {
194     ret = ServerInstance->SetInternalCredentials(usePassword, password);
195   }
196   return ret;
197 }
198
199 bool CAirPlayServer::SetInternalCredentials(bool usePassword, const CStdString& password)
200 {
201   m_usePassword = usePassword;
202   m_password = password;
203   return true;
204 }
205
206 void CAirPlayServer::StopServer(bool bWait)
207 {
208   if (ServerInstance)
209   {
210     ServerInstance->StopThread(bWait);
211     if (bWait)
212     {
213       delete ServerInstance;
214       ServerInstance = NULL;
215     }
216   }
217 }
218
219 bool CAirPlayServer::IsRunning()
220 {
221   if (ServerInstance == NULL)
222     return false;
223
224   return ((CThread*)ServerInstance)->IsRunning();
225 }
226
227 void CAirPlayServer::AnnounceToClients(int state)
228 {
229   CSingleLock lock (m_connectionLock);
230   
231   std::vector<CTCPClient>::iterator it;
232   for (it = m_connections.begin(); it != m_connections.end(); it++)
233   {
234     CStdString reverseHeader;
235     CStdString reverseBody;
236     CStdString response;
237     int reverseSocket = INVALID_SOCKET;
238     it->ComposeReverseEvent(reverseHeader, reverseBody, state);
239   
240     // Send event status per reverse http socket (play, loading, paused)
241     // if we have a reverse header and a reverse socket
242     if (reverseHeader.size() > 0 && m_reverseSockets.find(it->m_sessionId) != m_reverseSockets.end())
243     {
244       //search the reverse socket to this sessionid
245       response = StringUtils::Format("POST /event HTTP/1.1\r\n");
246       reverseSocket = m_reverseSockets[it->m_sessionId]; //that is our reverse socket
247       response += reverseHeader;
248     }
249     response += "\r\n";
250   
251     if (reverseBody.size() > 0)
252     {
253       response += reverseBody;
254     }
255   
256     // don't send it to the connection object
257     // the reverse socket itself belongs to
258     if (reverseSocket != INVALID_SOCKET && reverseSocket != it->m_socket)
259     {
260       send(reverseSocket, response.c_str(), response.size(), 0);//send the event status on the eventSocket
261     }
262   }
263 }
264
265 CAirPlayServer::CAirPlayServer(int port, bool nonlocal) : CThread("AirPlayServer")
266 {
267   m_port = port;
268   m_nonlocal = nonlocal;
269   m_ServerSocket = INVALID_SOCKET;
270   m_usePassword = false;
271   m_origVolume = -1;
272   CAnnouncementManager::AddAnnouncer(this);
273 }
274
275 CAirPlayServer::~CAirPlayServer()
276 {
277   CAnnouncementManager::RemoveAnnouncer(this);
278 }
279
280 void CAirPlayServer::Process()
281 {
282   m_bStop = false;
283   static int sessionCounter = 0;
284
285   while (!m_bStop)
286   {
287     int             max_fd = 0;
288     fd_set          rfds;
289     struct timeval  to     = {1, 0};
290     FD_ZERO(&rfds);
291
292     FD_SET(m_ServerSocket, &rfds);
293     max_fd = m_ServerSocket;
294
295     for (unsigned int i = 0; i < m_connections.size(); i++)
296     {
297       FD_SET(m_connections[i].m_socket, &rfds);
298       if (m_connections[i].m_socket > max_fd)
299         max_fd = m_connections[i].m_socket;
300     }
301
302     int res = select(max_fd+1, &rfds, NULL, NULL, &to);
303     if (res < 0)
304     {
305       CLog::Log(LOGERROR, "AIRPLAY Server: Select failed");
306       Sleep(1000);
307       Initialize();
308     }
309     else if (res > 0)
310     {
311       for (int i = m_connections.size() - 1; i >= 0; i--)
312       {
313         int socket = m_connections[i].m_socket;
314         if (FD_ISSET(socket, &rfds))
315         {
316           char buffer[RECEIVEBUFFER] = {};
317           int  nread = 0;
318           nread = recv(socket, (char*)&buffer, RECEIVEBUFFER, 0);
319           if (nread > 0)
320           {
321             CStdString sessionId;
322             m_connections[i].PushBuffer(this, buffer, nread, sessionId, m_reverseSockets);
323           }
324           if (nread <= 0)
325           {
326             CSingleLock lock (m_connectionLock);
327             CLog::Log(LOGINFO, "AIRPLAY Server: Disconnection detected");
328             m_connections[i].Disconnect();
329             m_connections.erase(m_connections.begin() + i);
330           }
331         }
332       }
333
334       if (FD_ISSET(m_ServerSocket, &rfds))
335       {
336         CLog::Log(LOGDEBUG, "AIRPLAY Server: New connection detected");
337         CTCPClient newconnection;
338         newconnection.m_socket = accept(m_ServerSocket, (struct sockaddr*) &newconnection.m_cliaddr, &newconnection.m_addrlen);
339         sessionCounter++;
340         newconnection.m_sessionCounter = sessionCounter;
341
342         if (newconnection.m_socket == INVALID_SOCKET)
343         {
344           CLog::Log(LOGERROR, "AIRPLAY Server: Accept of new connection failed: %d", errno);
345           if (EBADF == errno)
346           {
347             Sleep(1000);
348             Initialize();
349             break;
350           }
351         }
352         else
353         {
354           CSingleLock lock (m_connectionLock);
355           CLog::Log(LOGINFO, "AIRPLAY Server: New connection added");
356           m_connections.push_back(newconnection);
357         }
358       }
359     }
360   }
361
362   Deinitialize();
363 }
364
365 bool CAirPlayServer::Initialize()
366 {
367   Deinitialize();
368   
369   if ((m_ServerSocket = CreateTCPServerSocket(m_port, !m_nonlocal, 10, "AIRPLAY")) == INVALID_SOCKET)
370     return false;
371   
372   CLog::Log(LOGINFO, "AIRPLAY Server: Successfully initialized");
373   return true;
374 }
375
376 void CAirPlayServer::Deinitialize()
377 {
378   CSingleLock lock (m_connectionLock);
379   for (unsigned int i = 0; i < m_connections.size(); i++)
380     m_connections[i].Disconnect();
381
382   m_connections.clear();
383   m_reverseSockets.clear();
384
385   if (m_ServerSocket != INVALID_SOCKET)
386   {
387     shutdown(m_ServerSocket, SHUT_RDWR);
388     close(m_ServerSocket);
389     m_ServerSocket = INVALID_SOCKET;
390   }
391 }
392
393 CAirPlayServer::CTCPClient::CTCPClient()
394 {
395   m_socket = INVALID_SOCKET;
396   m_httpParser = new HttpParser();
397
398   m_addrlen = sizeof(struct sockaddr_storage);
399   m_pLibPlist = new DllLibPlist();
400
401   m_bAuthenticated = false;
402   m_lastEvent = EVENT_NONE;
403 }
404
405 CAirPlayServer::CTCPClient::CTCPClient(const CTCPClient& client)
406 {
407   Copy(client);
408   m_httpParser = new HttpParser();
409   m_pLibPlist = new DllLibPlist();
410 }
411
412 CAirPlayServer::CTCPClient::~CTCPClient()
413 {
414   if (m_pLibPlist->IsLoaded())
415   {
416     m_pLibPlist->Unload();
417   }
418   delete m_pLibPlist;
419   delete m_httpParser;
420 }
421
422 CAirPlayServer::CTCPClient& CAirPlayServer::CTCPClient::operator=(const CTCPClient& client)
423 {
424   Copy(client);
425   m_httpParser = new HttpParser();
426   m_pLibPlist = new DllLibPlist();
427   return *this;
428 }
429
430 void CAirPlayServer::CTCPClient::PushBuffer(CAirPlayServer *host, const char *buffer,
431                                             int length, CStdString &sessionId, std::map<CStdString,
432                                             int> &reverseSockets)
433 {
434   HttpParser::status_t status = m_httpParser->addBytes(buffer, length);
435
436   if (status == HttpParser::Done)
437   {
438     // Parse the request
439     CStdString responseHeader;
440     CStdString responseBody;
441     int status = ProcessRequest(responseHeader, responseBody);
442     sessionId = m_sessionId;
443     CStdString statusMsg = "OK";
444
445     switch(status)
446     {
447       case AIRPLAY_STATUS_NOT_IMPLEMENTED:
448         statusMsg = "Not Implemented";
449         break;
450       case AIRPLAY_STATUS_SWITCHING_PROTOCOLS:
451         statusMsg = "Switching Protocols";
452         reverseSockets[sessionId] = m_socket;//save this socket as reverse http socket for this sessionid
453         break;
454       case AIRPLAY_STATUS_NEED_AUTH:
455         statusMsg = "Unauthorized";
456         break;
457       case AIRPLAY_STATUS_NOT_FOUND:
458         statusMsg = "Not Found";
459         break;
460       case AIRPLAY_STATUS_METHOD_NOT_ALLOWED:
461         statusMsg = "Method Not Allowed";
462         break;
463     }
464
465     // Prepare the response
466     CStdString response;
467     const time_t ltime = time(NULL);
468     char *date = asctime(gmtime(&ltime)); //Fri, 17 Dec 2010 11:18:01 GMT;
469     date[strlen(date) - 1] = '\0'; // remove \n
470     response = StringUtils::Format("HTTP/1.1 %d %s\nDate: %s\r\n", status, statusMsg.c_str(), date);
471     if (responseHeader.size() > 0)
472     {
473       response += responseHeader;
474     }
475
476     if (responseBody.size() > 0)
477     {
478       response = StringUtils::Format("%sContent-Length: %d\r\n", response.c_str(), responseBody.size());
479     }
480     response += "\r\n";
481
482     if (responseBody.size() > 0)
483     {
484       response += responseBody;
485     }
486
487     // Send the response
488     //don't send response on AIRPLAY_STATUS_NO_RESPONSE_NEEDED
489     if (status != AIRPLAY_STATUS_NO_RESPONSE_NEEDED)
490     {
491       send(m_socket, response.c_str(), response.size(), 0);
492     }
493     // We need a new parser...
494     delete m_httpParser;
495     m_httpParser = new HttpParser;
496   }
497 }
498
499 void CAirPlayServer::CTCPClient::Disconnect()
500 {
501   if (m_socket != INVALID_SOCKET)
502   {
503     CSingleLock lock (m_critSection);
504     shutdown(m_socket, SHUT_RDWR);
505     close(m_socket);
506     m_socket = INVALID_SOCKET;
507     delete m_httpParser;
508     m_httpParser = NULL;
509   }
510 }
511
512 void CAirPlayServer::CTCPClient::Copy(const CTCPClient& client)
513 {
514   m_socket            = client.m_socket;
515   m_cliaddr           = client.m_cliaddr;
516   m_addrlen           = client.m_addrlen;
517   m_httpParser        = client.m_httpParser;
518   m_authNonce         = client.m_authNonce;
519   m_bAuthenticated    = client.m_bAuthenticated;
520   m_sessionCounter    = client.m_sessionCounter;
521 }
522
523
524 void CAirPlayServer::CTCPClient::ComposeReverseEvent( CStdString& reverseHeader,
525                                                       CStdString& reverseBody,
526                                                       int state)
527 {
528
529   if ( m_lastEvent != state )
530   { 
531     switch(state)
532     {
533       case EVENT_PLAYING:
534       case EVENT_LOADING:
535       case EVENT_PAUSED:
536       case EVENT_STOPPED:      
537         reverseBody = StringUtils::Format(EVENT_INFO, m_sessionCounter, eventStrings[state]);
538         CLog::Log(LOGDEBUG, "AIRPLAY: sending event: %s", eventStrings[state]);
539         break;
540     }
541     reverseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
542     reverseHeader = StringUtils::Format("%sContent-Length: %d\r\n",reverseHeader.c_str(), reverseBody.size());
543     reverseHeader = StringUtils::Format("%sx-apple-session-id: %s\r\n",reverseHeader.c_str(), m_sessionId.c_str());
544     m_lastEvent = state;
545   }
546 }
547
548 void CAirPlayServer::CTCPClient::ComposeAuthRequestAnswer(CStdString& responseHeader, CStdString& responseBody)
549 {
550   int16_t random=rand();
551   CStdString randomStr = StringUtils::Format("%i", random);
552   m_authNonce=XBMC::XBMC_MD5::GetMD5(randomStr);
553   responseHeader = StringUtils::Format(AUTH_REQUIRED, m_authNonce.c_str());
554   responseBody.clear();
555 }
556
557
558 //as of rfc 2617
559 CStdString calcResponse(const CStdString& username,
560                         const CStdString& password,
561                         const CStdString& realm,
562                         const CStdString& method,
563                         const CStdString& digestUri,
564                         const CStdString& nonce)
565 {
566   CStdString response;
567   CStdString HA1;
568   CStdString HA2;
569
570   HA1 = XBMC::XBMC_MD5::GetMD5(username + ":" + realm + ":" + password);
571   HA2 = XBMC::XBMC_MD5::GetMD5(method + ":" + digestUri);
572   response = XBMC::XBMC_MD5::GetMD5(HA1.ToLower() + ":" + nonce + ":" + HA2.ToLower());
573   return response.ToLower();
574 }
575
576 //helper function
577 //from a string field1="value1", field2="value2" it parses the value to a field
578 CStdString getFieldFromString(const CStdString &str, const char* field)
579 {
580   CStdString tmpStr;
581   CStdStringArray tmpAr1;
582   CStdStringArray tmpAr2;
583
584   StringUtils::SplitString(str, ",", tmpAr1);
585
586   for(unsigned int i = 0;i<tmpAr1.size();i++)
587   {
588     if (tmpAr1[i].Find(field) != -1)
589     {
590       if (StringUtils::SplitString(tmpAr1[i], "=", tmpAr2) == 2)
591       {
592         tmpAr2[1].Remove('\"');//remove quotes
593         return tmpAr2[1];
594       }
595     }
596   }
597   return "";
598 }
599
600 bool CAirPlayServer::CTCPClient::checkAuthorization(const CStdString& authStr,
601                                                     const CStdString& method,
602                                                     const CStdString& uri)
603 {
604   bool authValid = true;
605
606   CStdString username;
607
608   if (authStr.empty())
609     return false;
610
611   //first get username - we allow all usernames for airplay (usually it is AirPlay)
612   username = getFieldFromString(authStr, "username");
613   if (username.empty())
614   {
615     authValid = false;
616   }
617
618   //second check realm
619   if (authValid)
620   {
621     if (getFieldFromString(authStr, "realm") != AUTH_REALM)
622     {
623       authValid = false;
624     }
625   }
626
627   //third check nonce
628   if (authValid)
629   {
630     if (getFieldFromString(authStr, "nonce") != m_authNonce)
631     {
632       authValid = false;
633     }
634   }
635
636   //forth check uri
637   if (authValid)
638   {
639     if (getFieldFromString(authStr, "uri") != uri)
640     {
641       authValid = false;
642     }
643   }
644
645   //last check response
646   if (authValid)
647   {
648      CStdString realm = AUTH_REALM;
649      CStdString ourResponse = calcResponse(username, ServerInstance->m_password, realm, method, uri, m_authNonce);
650      CStdString theirResponse = getFieldFromString(authStr, "response");
651      if (!theirResponse.Equals(ourResponse, false))
652      {
653        authValid = false;
654        CLog::Log(LOGDEBUG,"AirAuth: response mismatch - our: %s theirs: %s",ourResponse.c_str(), theirResponse.c_str());
655      }
656      else
657      {
658        CLog::Log(LOGDEBUG, "AirAuth: successfull authentication from AirPlay client");
659      }
660   }
661   m_bAuthenticated = authValid;
662   return m_bAuthenticated;
663 }
664
665 void CAirPlayServer::backupVolume()
666 {
667   if (ServerInstance->m_origVolume == -1)
668     ServerInstance->m_origVolume = (int)g_application.GetVolume();
669 }
670
671 void CAirPlayServer::restoreVolume()
672 {
673   if (ServerInstance->m_origVolume != -1 && CSettings::Get().GetBool("services.airplayvolumecontrol"))
674   {
675     g_application.SetVolume((float)ServerInstance->m_origVolume);
676     ServerInstance->m_origVolume = -1;
677   }
678 }
679
680 int CAirPlayServer::CTCPClient::ProcessRequest( CStdString& responseHeader,
681                                                 CStdString& responseBody)
682 {
683   CStdString method = m_httpParser->getMethod();
684   CStdString uri = m_httpParser->getUri();
685   CStdString queryString = m_httpParser->getQueryString();
686   CStdString body = m_httpParser->getBody();
687   CStdString contentType = m_httpParser->getValue("content-type");
688   m_sessionId = m_httpParser->getValue("x-apple-session-id");
689   CStdString authorization = m_httpParser->getValue("authorization");
690   int status = AIRPLAY_STATUS_OK;
691   bool needAuth = false;
692   
693   if (m_sessionId.IsEmpty())
694     m_sessionId = "00000000-0000-0000-0000-000000000000";
695
696   if (ServerInstance->m_usePassword && !m_bAuthenticated)
697   {
698     needAuth = true;
699   }
700
701   int startQs = uri.Find('?');
702   if (startQs != -1)
703   {
704     uri = uri.Left(startQs);
705   }
706
707   // This is the socket which will be used for reverse HTTP
708   // negotiate reverse HTTP via upgrade
709   if (uri == "/reverse")
710   {
711     status = AIRPLAY_STATUS_SWITCHING_PROTOCOLS;
712     responseHeader = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n";
713   }
714
715   // The rate command is used to play/pause media.
716   // A value argument should be supplied which indicates media should be played or paused.
717   // 0.000000 => pause
718   // 1.000000 => play
719   else if (uri == "/rate")
720   {
721       const char* found = strstr(queryString.c_str(), "value=");
722       int rate = found ? (int)(atof(found + strlen("value=")) + 0.5f) : 0;
723
724       CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with rate %i", uri.c_str(), rate);
725
726       if (needAuth && !checkAuthorization(authorization, method, uri))
727       {
728         status = AIRPLAY_STATUS_NEED_AUTH;
729       }
730       else if (rate == 0)
731       {
732         if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
733         {
734           CApplicationMessenger::Get().MediaPause();
735         }
736       }
737       else
738       {
739         if (g_application.m_pPlayer->IsPausedPlayback())
740         {
741           CApplicationMessenger::Get().MediaPause();
742         }
743       }
744   }
745   
746   // The volume command is used to change playback volume.
747   // A value argument should be supplied which indicates how loud we should get.
748   // 0.000000 => silent
749   // 1.000000 => loud
750   else if (uri == "/volume")
751   {
752       const char* found = strstr(queryString.c_str(), "volume=");
753       float volume = found ? (float)strtod(found + strlen("volume="), NULL) : 0;
754
755       CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with volume %f", uri.c_str(), volume);
756
757       if (needAuth && !checkAuthorization(authorization, method, uri))
758       {
759         status = AIRPLAY_STATUS_NEED_AUTH;
760       }
761       else if (volume >= 0 && volume <= 1)
762       {
763         float oldVolume = g_application.GetVolume();
764         volume *= 100;
765         if(oldVolume != volume && CSettings::Get().GetBool("services.airplayvolumecontrol"))
766         {
767           backupVolume();
768           g_application.SetVolume(volume);          
769           CApplicationMessenger::Get().ShowVolumeBar(oldVolume < volume);
770         }
771       }
772   }
773
774
775   // Contains a header like format in the request body which should contain a
776   // Content-Location and optionally a Start-Position
777   else if (uri == "/play")
778   {
779     CStdString location;
780     float position = 0.0;
781     m_lastEvent = EVENT_NONE;
782
783     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
784
785     if (needAuth && !checkAuthorization(authorization, method, uri))
786     {
787       status = AIRPLAY_STATUS_NEED_AUTH;
788     }
789     else if (contentType == "application/x-apple-binary-plist")
790     {
791       CAirPlayServer::m_isPlaying++;    
792       
793       if (m_pLibPlist->Load())
794       {
795         m_pLibPlist->EnableDelayedUnload(false);
796
797         const char* bodyChr = m_httpParser->getBody();
798
799         plist_t dict = NULL;
800         m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict);
801
802         if (m_pLibPlist->plist_dict_get_size(dict))
803         {
804           plist_t tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Start-Position");
805           if (tmpNode)
806           {
807             double tmpDouble = 0;
808             m_pLibPlist->plist_get_real_val(tmpNode, &tmpDouble);
809             position = (float)tmpDouble;
810           }
811
812           tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Content-Location");
813           if (tmpNode)
814           {
815             char *tmpStr = NULL;
816             m_pLibPlist->plist_get_string_val(tmpNode, &tmpStr);
817             location=tmpStr;
818 #ifdef TARGET_WINDOWS
819             m_pLibPlist->plist_free_string_val(tmpStr);
820 #else
821             free(tmpStr);
822 #endif
823           }
824
825           if (dict)
826           {
827             m_pLibPlist->plist_free(dict);
828           }
829         }
830         else
831         {
832           CLog::Log(LOGERROR, "Error parsing plist");
833         }
834         m_pLibPlist->Unload();
835       }
836     }
837     else
838     {
839       CAirPlayServer::m_isPlaying++;        
840       // Get URL to play
841       int start = body.Find("Content-Location: ");
842       if (start == -1)
843         return AIRPLAY_STATUS_NOT_IMPLEMENTED;
844       start += strlen("Content-Location: ");
845       int end = body.Find('\n', start);
846       location = body.Mid(start, end - start);
847
848       start = body.Find("Start-Position");
849       if (start != -1)
850       {
851         start += strlen("Start-Position: ");
852         int end = body.Find('\n', start);
853         CStdString positionStr = body.Mid(start, end - start);
854         position = (float)atof(positionStr.c_str());
855       }
856     }
857
858     if (status != AIRPLAY_STATUS_NEED_AUTH)
859     {
860       CStdString userAgent="AppleCoreMedia/1.0.0.8F455 (AppleTV; U; CPU OS 4_3 like Mac OS X; de_de)";
861       CURL::Encode(userAgent);
862       location += "|User-Agent=" + userAgent;
863
864       CFileItem fileToPlay(location, false);
865       fileToPlay.SetProperty("StartPercent", position*100.0f);
866       ServerInstance->AnnounceToClients(EVENT_LOADING);
867       // froce to internal dvdplayer cause it is the only
868       // one who will work well with airplay
869       g_application.m_eForcedNextPlayer = EPC_DVDPLAYER;
870       CApplicationMessenger::Get().MediaPlay(fileToPlay);
871     }
872   }
873
874   // Used to perform seeking (POST request) and to retrieve current player position (GET request).
875   // GET scrub seems to also set rate 1 - strange but true
876   else if (uri == "/scrub")
877   {
878     if (needAuth && !checkAuthorization(authorization, method, uri))
879     {
880       status = AIRPLAY_STATUS_NEED_AUTH;
881     }
882     else if (method == "GET")
883     {
884       CLog::Log(LOGDEBUG, "AIRPLAY: got GET request %s", uri.c_str());
885       
886       if (g_application.m_pPlayer->GetTotalTime())
887       {
888         float position = ((float) g_application.m_pPlayer->GetTime()) / 1000;
889         responseBody = StringUtils::Format("duration: %.6f\r\nposition: %.6f\r\n", (float)g_application.m_pPlayer->GetTotalTime() / 1000, position);
890       }
891       else 
892       {
893         status = AIRPLAY_STATUS_METHOD_NOT_ALLOWED;
894       }
895     }
896     else
897     {
898       const char* found = strstr(queryString.c_str(), "position=");
899       
900       if (found && g_application.m_pPlayer->HasPlayer())
901       {
902         int64_t position = (int64_t) (atof(found + strlen("position=")) * 1000.0);
903         g_application.m_pPlayer->SeekTime(position);
904         CLog::Log(LOGDEBUG, "AIRPLAY: got POST request %s with pos %"PRId64, uri.c_str(), position);
905       }
906     }
907   }
908
909   // Sent when media playback should be stopped
910   else if (uri == "/stop")
911   {
912     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
913     if (needAuth && !checkAuthorization(authorization, method, uri))
914     {
915       status = AIRPLAY_STATUS_NEED_AUTH;
916     }
917     else
918     {
919       if (IsPlaying()) //only stop player if we started him
920       {
921         CApplicationMessenger::Get().MediaStop();
922         CAirPlayServer::m_isPlaying--;
923       }
924       else //if we are not playing and get the stop request - we just wanna stop picture streaming
925       {
926         CApplicationMessenger::Get().SendAction(ACTION_PREVIOUS_MENU);
927       }
928     }
929   }
930
931   // RAW JPEG data is contained in the request body
932   else if (uri == "/photo")
933   {
934     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
935     if (needAuth && !checkAuthorization(authorization, method, uri))
936     {
937       status = AIRPLAY_STATUS_NEED_AUTH;
938     }
939     else if (m_httpParser->getContentLength() > 0)
940     {
941       XFILE::CFile tmpFile;
942       CStdString tmpFileName = "special://temp/airplay_photo.jpg";
943
944       if( m_httpParser->getContentLength() > 3 &&
945           m_httpParser->getBody()[1] == 'P' &&
946           m_httpParser->getBody()[2] == 'N' &&
947           m_httpParser->getBody()[3] == 'G')
948       {
949         tmpFileName = "special://temp/airplay_photo.png";
950       }
951
952       if (tmpFile.OpenForWrite(tmpFileName, true))
953       {
954         int writtenBytes=0;
955         writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength());
956         tmpFile.Close();
957
958         if (writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength())
959         {
960           CApplicationMessenger::Get().PictureShow(tmpFileName);
961         }
962         else
963         {
964           CLog::Log(LOGERROR,"AirPlayServer: Error writing tmpFile.");
965         }
966       }
967     }
968   }
969
970   else if (uri == "/playback-info")
971   {
972     float position = 0.0f;
973     float duration = 0.0f;
974     float cachePosition = 0.0f;
975     bool playing = false;
976
977     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
978
979     if (needAuth && !checkAuthorization(authorization, method, uri))
980     {
981       status = AIRPLAY_STATUS_NEED_AUTH;
982     }
983     else if (g_application.m_pPlayer->HasPlayer())
984     {
985       if (g_application.m_pPlayer->GetTotalTime())
986       {
987         position = ((float) g_application.m_pPlayer->GetTime()) / 1000;
988         duration = ((float) g_application.m_pPlayer->GetTotalTime()) / 1000;
989         playing = !g_application.m_pPlayer->IsPaused();
990         cachePosition = position + (duration * g_application.m_pPlayer->GetCachePercentage() / 100.0f);
991       }
992
993       responseBody = StringUtils::Format(PLAYBACK_INFO, duration, cachePosition, position, (playing ? 1 : 0), duration);
994       responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
995
996       if (g_application.m_pPlayer->IsCaching())
997       {
998         CAirPlayServer::ServerInstance->AnnounceToClients(EVENT_LOADING);
999       }
1000     }
1001     else
1002     {
1003       responseBody = StringUtils::Format(PLAYBACK_INFO_NOT_READY, duration, cachePosition, position, (playing ? 1 : 0), duration);
1004       responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";     
1005     }
1006   }
1007
1008   else if (uri == "/server-info")
1009   {
1010     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
1011     responseBody = StringUtils::Format(SERVER_INFO, g_application.getNetwork().GetFirstConnectedInterface()->GetMacAddress().c_str());
1012     responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
1013   }
1014
1015   else if (uri == "/slideshow-features")
1016   {
1017     // Ignore for now.
1018   }
1019
1020   else if (uri == "/authorize")
1021   {
1022     // DRM, ignore for now.
1023   }
1024   
1025   else if (uri == "/setProperty")
1026   {
1027     status = AIRPLAY_STATUS_NOT_FOUND;
1028   }
1029
1030   else if (uri == "/getProperty")
1031   {
1032     status = AIRPLAY_STATUS_NOT_FOUND;
1033   }  
1034
1035   else if (uri == "200") //response OK from the event reverse message
1036   {
1037     status = AIRPLAY_STATUS_NO_RESPONSE_NEEDED;
1038   }
1039   else
1040   {
1041     CLog::Log(LOGERROR, "AIRPLAY Server: unhandled request [%s]\n", uri.c_str());
1042     status = AIRPLAY_STATUS_NOT_IMPLEMENTED;
1043   }
1044
1045   if (status == AIRPLAY_STATUS_NEED_AUTH)
1046   {
1047     ComposeAuthRequestAnswer(responseHeader, responseBody);
1048   }
1049
1050   return status;
1051 }
1052
1053 #endif