CURL::Encode usage refactoring
[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   StringUtils::ToLower(HA1);
573   StringUtils::ToLower(HA2);
574   response = XBMC::XBMC_MD5::GetMD5(HA1 + ":" + nonce + ":" + HA2);
575   StringUtils::ToLower(response);
576   return response;
577 }
578
579 //helper function
580 //from a string field1="value1", field2="value2" it parses the value to a field
581 CStdString getFieldFromString(const CStdString &str, const char* field)
582 {
583   CStdString tmpStr;
584   CStdStringArray tmpAr1;
585   CStdStringArray tmpAr2;
586
587   StringUtils::SplitString(str, ",", tmpAr1);
588
589   for(unsigned int i = 0;i<tmpAr1.size();i++)
590   {
591     if (tmpAr1[i].find(field) != std::string::npos)
592     {
593       if (StringUtils::SplitString(tmpAr1[i], "=", tmpAr2) == 2)
594       {
595         StringUtils::Replace(tmpAr2[1], "\"", "");//remove quotes
596         return tmpAr2[1];
597       }
598     }
599   }
600   return "";
601 }
602
603 bool CAirPlayServer::CTCPClient::checkAuthorization(const CStdString& authStr,
604                                                     const CStdString& method,
605                                                     const CStdString& uri)
606 {
607   bool authValid = true;
608
609   CStdString username;
610
611   if (authStr.empty())
612     return false;
613
614   //first get username - we allow all usernames for airplay (usually it is AirPlay)
615   username = getFieldFromString(authStr, "username");
616   if (username.empty())
617   {
618     authValid = false;
619   }
620
621   //second check realm
622   if (authValid)
623   {
624     if (getFieldFromString(authStr, "realm") != AUTH_REALM)
625     {
626       authValid = false;
627     }
628   }
629
630   //third check nonce
631   if (authValid)
632   {
633     if (getFieldFromString(authStr, "nonce") != m_authNonce)
634     {
635       authValid = false;
636     }
637   }
638
639   //forth check uri
640   if (authValid)
641   {
642     if (getFieldFromString(authStr, "uri") != uri)
643     {
644       authValid = false;
645     }
646   }
647
648   //last check response
649   if (authValid)
650   {
651      CStdString realm = AUTH_REALM;
652      CStdString ourResponse = calcResponse(username, ServerInstance->m_password, realm, method, uri, m_authNonce);
653      CStdString theirResponse = getFieldFromString(authStr, "response");
654      if (!theirResponse.Equals(ourResponse, false))
655      {
656        authValid = false;
657        CLog::Log(LOGDEBUG,"AirAuth: response mismatch - our: %s theirs: %s",ourResponse.c_str(), theirResponse.c_str());
658      }
659      else
660      {
661        CLog::Log(LOGDEBUG, "AirAuth: successfull authentication from AirPlay client");
662      }
663   }
664   m_bAuthenticated = authValid;
665   return m_bAuthenticated;
666 }
667
668 void CAirPlayServer::backupVolume()
669 {
670   if (ServerInstance->m_origVolume == -1)
671     ServerInstance->m_origVolume = (int)g_application.GetVolume();
672 }
673
674 void CAirPlayServer::restoreVolume()
675 {
676   if (ServerInstance->m_origVolume != -1 && CSettings::Get().GetBool("services.airplayvolumecontrol"))
677   {
678     g_application.SetVolume((float)ServerInstance->m_origVolume);
679     ServerInstance->m_origVolume = -1;
680   }
681 }
682
683 int CAirPlayServer::CTCPClient::ProcessRequest( CStdString& responseHeader,
684                                                 CStdString& responseBody)
685 {
686   CStdString method = m_httpParser->getMethod();
687   CStdString uri = m_httpParser->getUri();
688   CStdString queryString = m_httpParser->getQueryString();
689   CStdString body = m_httpParser->getBody();
690   CStdString contentType = m_httpParser->getValue("content-type");
691   m_sessionId = m_httpParser->getValue("x-apple-session-id");
692   CStdString authorization = m_httpParser->getValue("authorization");
693   int status = AIRPLAY_STATUS_OK;
694   bool needAuth = false;
695   
696   if (m_sessionId.empty())
697     m_sessionId = "00000000-0000-0000-0000-000000000000";
698
699   if (ServerInstance->m_usePassword && !m_bAuthenticated)
700   {
701     needAuth = true;
702   }
703
704   size_t startQs = uri.find('?');
705   if (startQs != std::string::npos)
706   {
707     uri.erase(startQs);
708   }
709
710   // This is the socket which will be used for reverse HTTP
711   // negotiate reverse HTTP via upgrade
712   if (uri == "/reverse")
713   {
714     status = AIRPLAY_STATUS_SWITCHING_PROTOCOLS;
715     responseHeader = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n";
716   }
717
718   // The rate command is used to play/pause media.
719   // A value argument should be supplied which indicates media should be played or paused.
720   // 0.000000 => pause
721   // 1.000000 => play
722   else if (uri == "/rate")
723   {
724       const char* found = strstr(queryString.c_str(), "value=");
725       int rate = found ? (int)(atof(found + strlen("value=")) + 0.5f) : 0;
726
727       CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with rate %i", uri.c_str(), rate);
728
729       if (needAuth && !checkAuthorization(authorization, method, uri))
730       {
731         status = AIRPLAY_STATUS_NEED_AUTH;
732       }
733       else if (rate == 0)
734       {
735         if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
736         {
737           CApplicationMessenger::Get().MediaPause();
738         }
739       }
740       else
741       {
742         if (g_application.m_pPlayer->IsPausedPlayback())
743         {
744           CApplicationMessenger::Get().MediaPause();
745         }
746       }
747   }
748   
749   // The volume command is used to change playback volume.
750   // A value argument should be supplied which indicates how loud we should get.
751   // 0.000000 => silent
752   // 1.000000 => loud
753   else if (uri == "/volume")
754   {
755       const char* found = strstr(queryString.c_str(), "volume=");
756       float volume = found ? (float)strtod(found + strlen("volume="), NULL) : 0;
757
758       CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with volume %f", uri.c_str(), volume);
759
760       if (needAuth && !checkAuthorization(authorization, method, uri))
761       {
762         status = AIRPLAY_STATUS_NEED_AUTH;
763       }
764       else if (volume >= 0 && volume <= 1)
765       {
766         float oldVolume = g_application.GetVolume();
767         volume *= 100;
768         if(oldVolume != volume && CSettings::Get().GetBool("services.airplayvolumecontrol"))
769         {
770           backupVolume();
771           g_application.SetVolume(volume);          
772           CApplicationMessenger::Get().ShowVolumeBar(oldVolume < volume);
773         }
774       }
775   }
776
777
778   // Contains a header like format in the request body which should contain a
779   // Content-Location and optionally a Start-Position
780   else if (uri == "/play")
781   {
782     CStdString location;
783     float position = 0.0;
784     m_lastEvent = EVENT_NONE;
785
786     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
787
788     if (needAuth && !checkAuthorization(authorization, method, uri))
789     {
790       status = AIRPLAY_STATUS_NEED_AUTH;
791     }
792     else if (contentType == "application/x-apple-binary-plist")
793     {
794       CAirPlayServer::m_isPlaying++;    
795       
796       if (m_pLibPlist->Load())
797       {
798         m_pLibPlist->EnableDelayedUnload(false);
799
800         const char* bodyChr = m_httpParser->getBody();
801
802         plist_t dict = NULL;
803         m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict);
804
805         if (m_pLibPlist->plist_dict_get_size(dict))
806         {
807           plist_t tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Start-Position");
808           if (tmpNode)
809           {
810             double tmpDouble = 0;
811             m_pLibPlist->plist_get_real_val(tmpNode, &tmpDouble);
812             position = (float)tmpDouble;
813           }
814
815           tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Content-Location");
816           if (tmpNode)
817           {
818             char *tmpStr = NULL;
819             m_pLibPlist->plist_get_string_val(tmpNode, &tmpStr);
820             location=tmpStr;
821 #ifdef TARGET_WINDOWS
822             m_pLibPlist->plist_free_string_val(tmpStr);
823 #else
824             free(tmpStr);
825 #endif
826           }
827
828           if (dict)
829           {
830             m_pLibPlist->plist_free(dict);
831           }
832         }
833         else
834         {
835           CLog::Log(LOGERROR, "Error parsing plist");
836         }
837         m_pLibPlist->Unload();
838       }
839     }
840     else
841     {
842       CAirPlayServer::m_isPlaying++;        
843       // Get URL to play
844       std::string contentLocation = "Content-Location: ";
845       size_t start = body.find(contentLocation);
846       if (start == std::string::npos)
847         return AIRPLAY_STATUS_NOT_IMPLEMENTED;
848       start += contentLocation.size();
849       int end = body.find('\n', start);
850       location = body.substr(start, end - start);
851
852       std::string startPosition = "Start-Position: ";
853       start = body.find(startPosition);
854       if (start != std::string::npos)
855       {
856         start += startPosition.size();
857         int end = body.find('\n', start);
858         std::string positionStr = body.substr(start, end - start);
859         position = (float)atof(positionStr.c_str());
860       }
861     }
862
863     if (status != AIRPLAY_STATUS_NEED_AUTH)
864     {
865       CStdString userAgent(CURL::Encode("AppleCoreMedia/1.0.0.8F455 (AppleTV; U; CPU OS 4_3 like Mac OS X; de_de)"));
866       location += "|User-Agent=" + userAgent;
867
868       CFileItem fileToPlay(location, false);
869       fileToPlay.SetProperty("StartPercent", position*100.0f);
870       ServerInstance->AnnounceToClients(EVENT_LOADING);
871       // froce to internal dvdplayer cause it is the only
872       // one who will work well with airplay
873       g_application.m_eForcedNextPlayer = EPC_DVDPLAYER;
874       CApplicationMessenger::Get().MediaPlay(fileToPlay);
875     }
876   }
877
878   // Used to perform seeking (POST request) and to retrieve current player position (GET request).
879   // GET scrub seems to also set rate 1 - strange but true
880   else if (uri == "/scrub")
881   {
882     if (needAuth && !checkAuthorization(authorization, method, uri))
883     {
884       status = AIRPLAY_STATUS_NEED_AUTH;
885     }
886     else if (method == "GET")
887     {
888       CLog::Log(LOGDEBUG, "AIRPLAY: got GET request %s", uri.c_str());
889       
890       if (g_application.m_pPlayer->GetTotalTime())
891       {
892         float position = ((float) g_application.m_pPlayer->GetTime()) / 1000;
893         responseBody = StringUtils::Format("duration: %.6f\r\nposition: %.6f\r\n", (float)g_application.m_pPlayer->GetTotalTime() / 1000, position);
894       }
895       else 
896       {
897         status = AIRPLAY_STATUS_METHOD_NOT_ALLOWED;
898       }
899     }
900     else
901     {
902       const char* found = strstr(queryString.c_str(), "position=");
903       
904       if (found && g_application.m_pPlayer->HasPlayer())
905       {
906         int64_t position = (int64_t) (atof(found + strlen("position=")) * 1000.0);
907         g_application.m_pPlayer->SeekTime(position);
908         CLog::Log(LOGDEBUG, "AIRPLAY: got POST request %s with pos %"PRId64, uri.c_str(), position);
909       }
910     }
911   }
912
913   // Sent when media playback should be stopped
914   else if (uri == "/stop")
915   {
916     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
917     if (needAuth && !checkAuthorization(authorization, method, uri))
918     {
919       status = AIRPLAY_STATUS_NEED_AUTH;
920     }
921     else
922     {
923       if (IsPlaying()) //only stop player if we started him
924       {
925         CApplicationMessenger::Get().MediaStop();
926         CAirPlayServer::m_isPlaying--;
927       }
928       else //if we are not playing and get the stop request - we just wanna stop picture streaming
929       {
930         CApplicationMessenger::Get().SendAction(ACTION_PREVIOUS_MENU);
931       }
932     }
933   }
934
935   // RAW JPEG data is contained in the request body
936   else if (uri == "/photo")
937   {
938     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
939     if (needAuth && !checkAuthorization(authorization, method, uri))
940     {
941       status = AIRPLAY_STATUS_NEED_AUTH;
942     }
943     else if (m_httpParser->getContentLength() > 0)
944     {
945       XFILE::CFile tmpFile;
946       CStdString tmpFileName = "special://temp/airplay_photo.jpg";
947
948       if( m_httpParser->getContentLength() > 3 &&
949           m_httpParser->getBody()[1] == 'P' &&
950           m_httpParser->getBody()[2] == 'N' &&
951           m_httpParser->getBody()[3] == 'G')
952       {
953         tmpFileName = "special://temp/airplay_photo.png";
954       }
955
956       if (tmpFile.OpenForWrite(tmpFileName, true))
957       {
958         int writtenBytes=0;
959         writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength());
960         tmpFile.Close();
961
962         if (writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength())
963         {
964           CApplicationMessenger::Get().PictureShow(tmpFileName);
965         }
966         else
967         {
968           CLog::Log(LOGERROR,"AirPlayServer: Error writing tmpFile.");
969         }
970       }
971     }
972   }
973
974   else if (uri == "/playback-info")
975   {
976     float position = 0.0f;
977     float duration = 0.0f;
978     float cachePosition = 0.0f;
979     bool playing = false;
980
981     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
982
983     if (needAuth && !checkAuthorization(authorization, method, uri))
984     {
985       status = AIRPLAY_STATUS_NEED_AUTH;
986     }
987     else if (g_application.m_pPlayer->HasPlayer())
988     {
989       if (g_application.m_pPlayer->GetTotalTime())
990       {
991         position = ((float) g_application.m_pPlayer->GetTime()) / 1000;
992         duration = ((float) g_application.m_pPlayer->GetTotalTime()) / 1000;
993         playing = !g_application.m_pPlayer->IsPaused();
994         cachePosition = position + (duration * g_application.m_pPlayer->GetCachePercentage() / 100.0f);
995       }
996
997       responseBody = StringUtils::Format(PLAYBACK_INFO, duration, cachePosition, position, (playing ? 1 : 0), duration);
998       responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
999
1000       if (g_application.m_pPlayer->IsCaching())
1001       {
1002         CAirPlayServer::ServerInstance->AnnounceToClients(EVENT_LOADING);
1003       }
1004     }
1005     else
1006     {
1007       responseBody = StringUtils::Format(PLAYBACK_INFO_NOT_READY, duration, cachePosition, position, (playing ? 1 : 0), duration);
1008       responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";     
1009     }
1010   }
1011
1012   else if (uri == "/server-info")
1013   {
1014     CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
1015     responseBody = StringUtils::Format(SERVER_INFO, g_application.getNetwork().GetFirstConnectedInterface()->GetMacAddress().c_str());
1016     responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
1017   }
1018
1019   else if (uri == "/slideshow-features")
1020   {
1021     // Ignore for now.
1022   }
1023
1024   else if (uri == "/authorize")
1025   {
1026     // DRM, ignore for now.
1027   }
1028   
1029   else if (uri == "/setProperty")
1030   {
1031     status = AIRPLAY_STATUS_NOT_FOUND;
1032   }
1033
1034   else if (uri == "/getProperty")
1035   {
1036     status = AIRPLAY_STATUS_NOT_FOUND;
1037   }  
1038
1039   else if (uri == "200") //response OK from the event reverse message
1040   {
1041     status = AIRPLAY_STATUS_NO_RESPONSE_NEEDED;
1042   }
1043   else
1044   {
1045     CLog::Log(LOGERROR, "AIRPLAY Server: unhandled request [%s]\n", uri.c_str());
1046     status = AIRPLAY_STATUS_NOT_IMPLEMENTED;
1047   }
1048
1049   if (status == AIRPLAY_STATUS_NEED_AUTH)
1050   {
1051     ComposeAuthRequestAnswer(responseHeader, responseBody);
1052   }
1053
1054   return status;
1055 }
1056
1057 #endif