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