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