1 /*****************************************************************
2 |
3 |   Platinum - Device Host
4 |
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
8 |
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
13 |
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
21 |
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 | GNU General Public License for more details.
26 |
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
32 |
33 ****************************************************************/
34 
35 /*----------------------------------------------------------------------
36 |   includes
37 +---------------------------------------------------------------------*/
38 #include "PltService.h"
39 #include "PltDeviceHost.h"
40 #include "PltUPnP.h"
41 #include "PltUtilities.h"
42 #include "PltSsdp.h"
43 #include "PltHttpServer.h"
44 #include "PltVersion.h"
45 
46 NPT_SET_LOCAL_LOGGER("platinum.core.devicehost")
47 
48 /*----------------------------------------------------------------------
49 |   externals
50 +---------------------------------------------------------------------*/
51 extern NPT_UInt8 Platinum_120x120_jpg[16096];
52 extern NPT_UInt8 Platinum_120x120_png[26577];
53 extern NPT_UInt8 Platinum_48x48_jpg[3041];
54 extern NPT_UInt8 Platinum_48x48_png[4681];
55 
56 /*----------------------------------------------------------------------
57 |   PLT_DeviceHost::PLT_DeviceHost
58 +---------------------------------------------------------------------*/
PLT_DeviceHost(const char * description_path,const char * uuid,const char * device_type,const char * friendly_name,bool show_ip,NPT_UInt16 port,bool port_rebind)59 PLT_DeviceHost::PLT_DeviceHost(const char*  description_path /* = "/" */,
60                                const char*  uuid             /* = "" */,
61                                const char*  device_type      /* = "" */,
62                                const char*  friendly_name    /* = "" */,
63                                bool         show_ip          /* = false */,
64                                NPT_UInt16   port             /* = 0 */,
65                                bool         port_rebind      /* = false */) :
66     PLT_DeviceData(NPT_HttpUrl(NULL, 0, description_path),
67                    uuid,
68                    *PLT_Constants::GetInstance().GetDefaultDeviceLease(),
69                    device_type,
70                    friendly_name),
71     m_TaskManager(NULL),
72     m_HttpServer(NULL),
73     m_ExtraBroascast(false),
74     m_Port(port),
75     m_PortRebind(port_rebind),
76     m_ByeByeFirst(true),
77     m_Started(false)
78 {
79     if (show_ip) {
80         NPT_List<NPT_IpAddress> ips;
81         PLT_UPnPMessageHelper::GetIPAddresses(ips);
82         if (ips.GetItemCount()) {
83             m_FriendlyName += " (" + ips.GetFirstItem()->ToString() + ")";
84         }
85     }
86 }
87 
88 /*----------------------------------------------------------------------
89 |   PLT_DeviceHost::~PLT_DeviceHost
90 +---------------------------------------------------------------------*/
~PLT_DeviceHost()91 PLT_DeviceHost::~PLT_DeviceHost()
92 {
93 }
94 
95 /*----------------------------------------------------------------------
96 |   PLT_DeviceHost::AddIcon
97 +---------------------------------------------------------------------*/
98 NPT_Result
AddIcon(const PLT_DeviceIcon & icon,const char * fileroot,const char * urlroot)99 PLT_DeviceHost::AddIcon(const PLT_DeviceIcon& icon,
100                         const char*           fileroot,
101                         const char*           urlroot /* = "/" */)
102 {
103     // verify the url of the icon starts with the url root
104     if (!icon.m_UrlPath.StartsWith(urlroot)) return NPT_ERROR_INVALID_PARAMETERS;
105 
106     NPT_HttpFileRequestHandler* icon_handler = new NPT_HttpFileRequestHandler(urlroot, fileroot);
107     m_HttpServer->AddRequestHandler(icon_handler, icon.m_UrlPath, false, true);
108     return m_Icons.Add(icon);
109 }
110 
111 /*----------------------------------------------------------------------
112 |   PLT_DeviceHost::AddIcon
113 +---------------------------------------------------------------------*/
114 NPT_Result
AddIcon(const PLT_DeviceIcon & icon,const void * data,NPT_Size size,bool copy)115 PLT_DeviceHost::AddIcon(const PLT_DeviceIcon& icon,
116                         const void*           data,
117                         NPT_Size              size,
118                         bool                  copy /* = true */)
119 {
120     NPT_HttpStaticRequestHandler* icon_handler =
121         new NPT_HttpStaticRequestHandler(
122             data,
123             size,
124             icon.m_MimeType,
125             copy);
126     m_HttpServer->AddRequestHandler(icon_handler, icon.m_UrlPath, false, true);
127     return m_Icons.Add(icon);
128 }
129 
130 /*----------------------------------------------------------------------
131 |   PLT_DeviceHost::SetupIcons
132 +---------------------------------------------------------------------*/
133 NPT_Result
SetupIcons()134 PLT_DeviceHost::SetupIcons()
135 {
136     /*if (m_Icons.GetItemCount() == 0) {
137         AddIcon(
138             PLT_DeviceIcon("image/jpeg", 120, 120, 24, "/images/platinum-120x120.jpg"),
139             Platinum_120x120_jpg, sizeof(Platinum_120x120_jpg), false);
140         AddIcon(
141             PLT_DeviceIcon("image/jpeg", 48, 48, 24, "/images/platinum-48x48.jpg"),
142             Platinum_48x48_jpg, sizeof(Platinum_48x48_jpg), false);
143         AddIcon(
144             PLT_DeviceIcon("image/png", 120, 120, 24, "/images/platinum-120x120.png"),
145             Platinum_120x120_png, sizeof(Platinum_120x120_png), false);
146         AddIcon(
147             PLT_DeviceIcon("image/png", 48, 48, 24, "/images/platinum-48x48.png"),
148             Platinum_48x48_png, sizeof(Platinum_48x48_png), false);
149     }*/
150     return NPT_SUCCESS;
151 }
152 
153 /*----------------------------------------------------------------------
154 |   PLT_DeviceHost::SetupDevice
155 +---------------------------------------------------------------------*/
156 NPT_Result
SetupDevice()157 PLT_DeviceHost::SetupDevice()
158 {
159     NPT_CHECK_FATAL(SetupServices());
160     NPT_CHECK_WARNING(SetupIcons());
161     return NPT_SUCCESS;
162 }
163 
164 /*----------------------------------------------------------------------
165 |   PLT_DeviceHost::Start
166 +---------------------------------------------------------------------*/
167 NPT_Result
Start(PLT_SsdpListenTask * task)168 PLT_DeviceHost::Start(PLT_SsdpListenTask* task)
169 {
170     NPT_Result result;
171 
172     if (m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
173 
174     // setup
175     m_TaskManager = new PLT_TaskManager();
176     m_HttpServer = new PLT_HttpServer(NPT_IpAddress::Any, m_Port, m_PortRebind, 100); // limit to 100 clients max
177     if (NPT_FAILED(result = m_HttpServer->Start())) {
178         m_TaskManager = NULL;
179         m_HttpServer = NULL;
180         NPT_CHECK_FATAL(result);
181     }
182 
183     // read back assigned port in case we passed 0 to randomly select one
184     m_Port = m_HttpServer->GetPort();
185     m_URLDescription.SetPort(m_Port);
186 
187     // callback to initialize the device
188     if (NPT_FAILED(result = SetupDevice())) {
189         m_TaskManager = NULL;
190         m_HttpServer = NULL;
191         NPT_CHECK_FATAL(result);
192     }
193 
194     // all other requests including description document
195     // and service control are dynamically handled
196     m_HttpServer->AddRequestHandler(new PLT_HttpRequestHandler(this), "/", true, true);
197 
198     // we should not advertise right away
199     // spec says randomly less than 100ms
200     NPT_TimeInterval delay(((NPT_Int64)NPT_System::GetRandomInteger()%100)*1000000);
201 
202     // calculate when we should send another announcement
203     // we announce a bit before half way through leasetime to make sure
204     // clients don't expire us.
205     NPT_Size leaseTime = (NPT_Size)GetLeaseTime().ToSeconds();
206     NPT_TimeInterval repeat;
207     repeat.SetSeconds(leaseTime?(int)((leaseTime >> 1) - 10):30);
208 
209     PLT_ThreadTask* announce_task = new PLT_SsdpDeviceAnnounceTask(
210         this,
211         repeat,
212         m_ByeByeFirst,
213         m_ExtraBroascast);
214     m_TaskManager->StartTask(announce_task, &delay);
215 
216     // register ourselves as a listener for SSDP search requests
217     task->AddListener(this);
218 
219     m_Started = true;
220     return NPT_SUCCESS;
221 }
222 
223 /*----------------------------------------------------------------------
224 |   PLT_DeviceHost::Stop
225 +---------------------------------------------------------------------*/
226 NPT_Result
Stop(PLT_SsdpListenTask * task)227 PLT_DeviceHost::Stop(PLT_SsdpListenTask* task)
228 {
229     if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
230 
231     // mark immediately we're stopping
232     m_Started = false;
233 
234     // unregister ourselves as a listener for ssdp requests
235     task->RemoveListener(this);
236 
237     // remove all our running tasks
238     m_TaskManager->Abort();
239 
240     // stop our internal http server
241     m_HttpServer->Stop();
242 
243     // announce we're leaving
244     NPT_List<NPT_NetworkInterface*> if_list;
245     PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list, true);
246     if_list.Apply(PLT_SsdpAnnounceInterfaceIterator(this, PLT_ANNOUNCETYPE_BYEBYE, m_ExtraBroascast));
247     if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
248 
249     // Cleanup all services and embedded devices
250     PLT_DeviceData::Cleanup();
251 
252     m_HttpServer = NULL;
253     m_TaskManager = NULL;
254 
255     return NPT_SUCCESS;
256 }
257 
258 /*----------------------------------------------------------------------
259 |   PLT_DeviceHost::Announce
260 +---------------------------------------------------------------------*/
261 NPT_Result
Announce(PLT_DeviceData * device,NPT_HttpRequest & req,NPT_UdpSocket & socket,PLT_SsdpAnnounceType type)262 PLT_DeviceHost::Announce(PLT_DeviceData*      device,
263                          NPT_HttpRequest&     req,
264                          NPT_UdpSocket&       socket,
265                          PLT_SsdpAnnounceType type)
266 {
267     NPT_Result res = NPT_SUCCESS;
268 
269     // target address
270     NPT_IpAddress ip;
271     NPT_CHECK_FATAL(ip.ResolveName(req.GetUrl().GetHost()));
272     NPT_SocketAddress addr(ip, req.GetUrl().GetPort());
273 
274 //    // UPnP 1.1 BOOTID.UPNP.ORG header
275 //    PLT_UPnPMessageHelper::SetBootId(req, device->m_BootId);
276 //
277 //    // UPnP 1.1 CONFIGID.UPNP.ORG header
278 //    if (device->m_ConfigId > 0) {
279 //        PLT_UPnPMessageHelper::SetConfigId(req, device->m_ConfigId);
280 //    }
281 
282     // NTS header
283     NPT_String nts;
284     switch (type) {
285         case PLT_ANNOUNCETYPE_ALIVE:
286             nts = "ssdp:alive";
287             PLT_UPnPMessageHelper::SetLeaseTime(req, device->GetLeaseTime());
288             PLT_UPnPMessageHelper::SetServer(req, PLT_HTTP_DEFAULT_SERVER, false);
289             break;
290 
291         case PLT_ANNOUNCETYPE_BYEBYE:
292             nts = "ssdp:byebye";
293             break;
294 
295         case PLT_ANNOUNCETYPE_UPDATE:
296             nts = "ssdp:update";
297             // update requires valid UPNP 1.1 NEXTBOOTID.UPNP.ORG Header
298             if (device->m_NextBootId == 0) {
299                 NPT_CHECK_FATAL(NPT_ERROR_INTERNAL);
300             }
301             PLT_UPnPMessageHelper::SetNextBootId(req, device->m_NextBootId);
302             break;
303 
304         default:
305             break;
306     }
307     PLT_UPnPMessageHelper::SetNTS(req, nts);
308 
309     NPT_LOG_FINER_3("Sending SSDP NOTIFY (%s) Request to %s (%s)",
310                     nts.GetChars(),
311                     (const char*)req.GetUrl().ToString(),
312                     (const char*)(PLT_UPnPMessageHelper::GetLocation(req)?*PLT_UPnPMessageHelper::GetLocation(req):""));
313 
314     // upnp:rootdevice
315     if (device->m_ParentUUID.IsEmpty()) {
316         PLT_SsdpSender::SendSsdp(req,
317             NPT_String("uuid:" + device->m_UUID + "::upnp:rootdevice"),
318             "upnp:rootdevice",
319             socket,
320             true,
321             &addr);
322     }
323 
324     // on byebye, don't sleep otherwise it hangs when we stop upnp
325     if (type != PLT_ANNOUNCETYPE_BYEBYE) {
326         NPT_System::Sleep(NPT_TimeInterval(PLT_DLNA_SSDP_DELAY));
327     }
328 
329     // uuid:device-UUID
330     PLT_SsdpSender::SendSsdp(req,
331         "uuid:" + device->m_UUID,
332         "uuid:" + device->m_UUID,
333         socket,
334         true,
335         &addr);
336 
337     // on byebye, don't sleep otherwise it hangs when we stop upnp
338     if (type != PLT_ANNOUNCETYPE_BYEBYE) {
339         NPT_System::Sleep(NPT_TimeInterval(PLT_DLNA_SSDP_DELAY));
340     }
341 
342     // uuid:device-UUID::urn:schemas-upnp-org:device:deviceType:ver
343     PLT_SsdpSender::SendSsdp(req,
344         NPT_String("uuid:" + device->m_UUID + "::" + device->m_DeviceType),
345         device->m_DeviceType,
346         socket,
347         true,
348         &addr);
349 
350     // on byebye, don't sleep otherwise it hangs when we stop upnp
351     if (type != PLT_ANNOUNCETYPE_BYEBYE) {
352         NPT_System::Sleep(NPT_TimeInterval(PLT_DLNA_SSDP_DELAY));
353     }
354 
355     // services
356     for (int i=0; i < (int)device->m_Services.GetItemCount(); i++) {
357         // uuid:device-UUID::urn:schemas-upnp-org:service:serviceType:ver
358         PLT_SsdpSender::SendSsdp(req,
359             NPT_String("uuid:" + device->m_UUID + "::" + device->m_Services[i]->GetServiceType()),
360             device->m_Services[i]->GetServiceType(),
361             socket,
362             true,
363             &addr);
364 
365         // on byebye, don't sleep otherwise it hangs when we stop upnp
366         if (type != PLT_ANNOUNCETYPE_BYEBYE) {
367             NPT_System::Sleep(NPT_TimeInterval(PLT_DLNA_SSDP_DELAY));
368         }
369     }
370 
371     // embedded devices
372     for (int j=0; j < (int)device->m_EmbeddedDevices.GetItemCount(); j++) {
373         Announce(device->m_EmbeddedDevices[j].AsPointer(),
374             req,
375             socket,
376             type);
377     }
378 
379     return res;
380 }
381 
382 /*----------------------------------------------------------------------
383 |   PLT_DeviceHost::SetupResponse
384 +---------------------------------------------------------------------*/
385 NPT_Result
SetupResponse(NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)386 PLT_DeviceHost::SetupResponse(NPT_HttpRequest&              request,
387                               const NPT_HttpRequestContext& context,
388                               NPT_HttpResponse&             response)
389 {
390     // get the address of who sent us some data back*/
391     NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
392     NPT_String method     = request.GetMethod();
393     NPT_String protocol   = request.GetProtocol();
394 
395     PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER, "PLT_DeviceHost::SetupResponse:", &request);
396 
397     if (method.Compare("POST") == 0) {
398         return ProcessHttpPostRequest(request, context, response);
399     } else if (method.Compare("SUBSCRIBE") == 0 || method.Compare("UNSUBSCRIBE") == 0) {
400         return ProcessHttpSubscriberRequest(request, context, response);
401     } else if (method.Compare("GET") == 0 || method.Compare("HEAD") == 0) {
402         // process SCPD requests
403         PLT_Service* service;
404         if (NPT_SUCCEEDED(FindServiceBySCPDURL(request.GetUrl().ToRequestString(), service, true))) {
405             return ProcessGetSCPD(service, request, context, response);
406         }
407 
408         // process Description document requests
409         if (request.GetUrl().GetPath() == m_URLDescription.GetPath()) {
410             return ProcessGetDescription(request, context, response);
411         }
412 
413         // process other requests
414         return ProcessHttpGetRequest(request, context, response);
415     }
416 
417     response.SetStatus(405, "Bad Request");
418     return NPT_SUCCESS;
419 }
420 
421 /*----------------------------------------------------------------------
422 |   PLT_DeviceHost::ProcessHttpGetRequest
423 +---------------------------------------------------------------------*/
424 NPT_Result
ProcessHttpGetRequest(NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)425 PLT_DeviceHost::ProcessHttpGetRequest(NPT_HttpRequest&              request,
426                                       const NPT_HttpRequestContext& context,
427                                       NPT_HttpResponse&             response)
428 {
429     NPT_COMPILER_UNUSED(request);
430     NPT_COMPILER_UNUSED(context);
431     NPT_COMPILER_UNUSED(response);
432 
433     return NPT_ERROR_NO_SUCH_ITEM;
434 }
435 
436 /*----------------------------------------------------------------------
437 |   PLT_DeviceHost::ProcessGetDescription
438 +---------------------------------------------------------------------*/
439 NPT_Result
ProcessGetDescription(NPT_HttpRequest &,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)440 PLT_DeviceHost::ProcessGetDescription(NPT_HttpRequest&              /*request*/,
441                                       const NPT_HttpRequestContext& context,
442                                       NPT_HttpResponse&             response)
443 {
444     NPT_COMPILER_UNUSED(context);
445 
446     NPT_String doc;
447     NPT_CHECK_FATAL(GetDescription(doc));
448     NPT_LOG_FINEST_2("Returning description to %s: %s",
449         (const char*)context.GetRemoteAddress().GetIpAddress().ToString(),
450         (const char*)doc);
451 
452     NPT_HttpEntity* entity;
453     PLT_HttpHelper::SetBody(response, doc, &entity);
454     entity->SetContentType("text/xml; charset=\"utf-8\"");
455     return NPT_SUCCESS;
456 }
457 
458 /*----------------------------------------------------------------------
459 |   PLT_DeviceHost::ProcessGetSCPD
460 +---------------------------------------------------------------------*/
461 NPT_Result
ProcessGetSCPD(PLT_Service * service,NPT_HttpRequest &,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)462 PLT_DeviceHost::ProcessGetSCPD(PLT_Service*                  service,
463                                NPT_HttpRequest&              /*request*/,
464                                const NPT_HttpRequestContext& context,
465                                NPT_HttpResponse&             response)
466 {
467     NPT_COMPILER_UNUSED(context);
468     NPT_CHECK_POINTER_FATAL(service);
469 
470     NPT_String doc;
471     NPT_CHECK_FATAL(service->GetSCPDXML(doc));
472     NPT_LOG_FINEST_2("Returning SCPD to %s: %s",
473         (const char*)context.GetRemoteAddress().GetIpAddress().ToString(),
474         (const char*)doc);
475 
476     NPT_HttpEntity* entity;
477     PLT_HttpHelper::SetBody(response, doc, &entity);
478     entity->SetContentType("text/xml; charset=\"utf-8\"");
479     return NPT_SUCCESS;
480 }
481 
482 /*----------------------------------------------------------------------
483 |   PLT_DeviceHost::ProcessPostRequest
484 +---------------------------------------------------------------------*/
485 NPT_Result
ProcessHttpPostRequest(NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)486 PLT_DeviceHost::ProcessHttpPostRequest(NPT_HttpRequest&              request,
487                                        const NPT_HttpRequestContext& context,
488                                        NPT_HttpResponse&             response)
489 {
490     NPT_Result                res;
491     NPT_String                service_type;
492     NPT_String                str;
493     NPT_XmlElementNode*       xml = NULL;
494     NPT_String                soap_action_header;
495     PLT_Service*              service;
496     NPT_XmlElementNode*       soap_body;
497     NPT_XmlElementNode*       soap_action;
498     PLT_ActionDesc*           action_desc;
499     PLT_ActionReference       action;
500     NPT_MemoryStreamReference resp(new NPT_MemoryStream);
501     NPT_String                ip_address  = context.GetRemoteAddress().GetIpAddress().ToString();
502     NPT_String                method      = request.GetMethod();
503     NPT_String                url         = request.GetUrl().ToRequestString();
504     NPT_String                protocol    = request.GetProtocol();
505     NPT_List<NPT_String>      components;
506     NPT_String                soap_action_name;
507 
508 #if defined(PLATINUM_UPNP_SPECS_STRICT)
509     const NPT_String*         attr;
510 #endif
511 
512     if (NPT_FAILED(FindServiceByControlURL(url, service, true)))
513         goto bad_request;
514 
515     if (!request.GetHeaders().GetHeaderValue("SOAPAction"))
516         goto bad_request;
517 
518     // extract the soap action name from the header
519     soap_action_header = *request.GetHeaders().GetHeaderValue("SOAPAction");
520     soap_action_header.TrimLeft('"');
521     soap_action_header.TrimRight('"');
522 
523     components = soap_action_header.Split("#");
524     if (components.GetItemCount() != 2)
525         goto bad_request;
526 
527     soap_action_name = *components.GetItem(1);
528 
529     // read the xml body and parse it
530     if (NPT_FAILED(PLT_HttpHelper::ParseBody(request, xml)))
531         goto bad_request;
532 
533     // check envelope
534     if (xml->GetTag().Compare("Envelope", true))
535         goto bad_request;
536 
537 #if defined(PLATINUM_UPNP_SPECS_STRICT)
538     // check namespace
539     if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/"))
540         goto bad_request;
541 
542     // check encoding
543     attr = xml->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/");
544     if (!attr || attr->Compare("http://schemas.xmlsoap.org/soap/encoding/"))
545         goto bad_request;
546 #endif
547 
548     // read action
549     soap_body = PLT_XmlHelper::GetChild(xml, "Body");
550     if (soap_body == NULL)
551         goto bad_request;
552 
553     PLT_XmlHelper::GetChild(soap_body, soap_action);
554     if (soap_action == NULL)
555         goto bad_request;
556 
557     // verify action name is identical to SOAPACTION header*/
558     if (soap_action->GetTag().Compare(soap_action_name, true))
559         goto bad_request;
560 
561     // verify namespace
562     if (!soap_action->GetNamespace() || soap_action->GetNamespace()->Compare(service->GetServiceType()))
563         goto bad_request;
564 
565     // create a buffer for our response body and call the service
566     if ((action_desc = service->FindActionDesc(soap_action_name)) == NULL) {
567         // create a bastard soap response
568         PLT_Action::FormatSoapError(401, "Invalid Action", *resp);
569         goto error;
570     }
571 
572     // create a new action object
573     action = new PLT_Action(*action_desc);
574 
575     // read all the arguments if any
576     for (NPT_List<NPT_XmlNode*>::Iterator args = soap_action->GetChildren().GetFirstItem();
577          args;
578          args++) {
579         NPT_XmlElementNode* child = (*args)->AsElementNode();
580         if (!child) continue;
581 
582         // Total HACK for xbox360 upnp uncompliance!
583         NPT_String name = child->GetTag();
584         if (action_desc->GetName() == "Browse" && name == "ContainerID") {
585             name = "ObjectID";
586         }
587 
588         res = action->SetArgumentValue(
589             name,
590             child->GetText()?*child->GetText():"");
591 
592         // test if value was correct
593         if (res == NPT_ERROR_INVALID_PARAMETERS) {
594             action->SetError(701, "Invalid Name");
595             goto error;
596         }
597     }
598 
599     // verify all required arguments were passed
600     if (NPT_FAILED(action->VerifyArguments(true))) {
601         action->SetError(402, "Invalid or Missing Args");
602         goto error;
603     }
604 
605     NPT_LOG_FINE_2("Processing action \"%s\" from %s",
606                    (const char*)action->GetActionDesc().GetName(),
607                    (const char*)context.GetRemoteAddress().GetIpAddress().ToString());
608 
609     // call the virtual function, it's all good
610     if (NPT_FAILED(OnAction(action, PLT_HttpRequestContext(request, context)))) {
611         goto error;
612     }
613 
614     // create the soap response now
615     action->FormatSoapResponse(*resp);
616     goto done;
617 
618 error:
619     if (!action.IsNull()) {
620         // set the error in case it wasn't done already
621         if (action->GetErrorCode() == 0) {
622             action->SetError(501, "Action Failed");
623         }
624         NPT_LOG_WARNING_3("Error while processing action %s: %d %s",
625             (const char*)action->GetActionDesc().GetName(),
626             action->GetErrorCode(),
627             action->GetError());
628 
629         action->FormatSoapResponse(*resp);
630     }
631 
632     response.SetStatus(500, "Internal Server Error");
633 
634 done:
635     NPT_LargeSize resp_body_size;
636     if (NPT_SUCCEEDED(resp->GetAvailable(resp_body_size))) {
637         NPT_HttpEntity* entity;
638         PLT_HttpHelper::SetBody(response,
639                                 (NPT_InputStreamReference)resp,
640                                 &entity);
641         entity->SetContentType("text/xml; charset=\"utf-8\"");
642         response.GetHeaders().SetHeader("Ext", ""); // should only be for M-POST but oh well
643     }
644 
645     delete xml;
646     return NPT_SUCCESS;
647 
648 bad_request:
649     delete xml;
650     response.SetStatus(500, "Bad Request");
651     return NPT_SUCCESS;
652 }
653 
654 /*----------------------------------------------------------------------
655 |   PLT_DeviceHost::ProcessHttpSubscriberRequest
656 +---------------------------------------------------------------------*/
657 NPT_Result
ProcessHttpSubscriberRequest(NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)658 PLT_DeviceHost::ProcessHttpSubscriberRequest(NPT_HttpRequest&              request,
659                                              const NPT_HttpRequestContext& context,
660                                              NPT_HttpResponse&             response)
661 {
662     NPT_String  ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
663     NPT_String  method     = request.GetMethod();
664     NPT_String  url        = request.GetUrl().ToRequestString();
665     NPT_String  protocol   = request.GetProtocol();
666 
667     const NPT_String* nt            = PLT_UPnPMessageHelper::GetNT(request);
668     const NPT_String* callback_urls = PLT_UPnPMessageHelper::GetCallbacks(request);
669     const NPT_String* sid           = PLT_UPnPMessageHelper::GetSID(request);
670 
671     PLT_Service* service;
672     NPT_CHECK_LABEL_WARNING(FindServiceByEventSubURL(url, service, true), cleanup);
673 
674     if (method.Compare("SUBSCRIBE") == 0) {
675         // Do we have a sid ?
676         if (sid) {
677             // make sure we don't have a callback nor a nt
678             if (nt || callback_urls) {
679                 goto cleanup;
680             }
681 
682             // default lease
683             NPT_Int32 timeout = (NPT_Int32)*PLT_Constants::GetInstance().GetDefaultSubscribeLease().AsPointer();
684 
685             // subscription renewed
686             // send the info to the service
687             service->ProcessRenewSubscription(context.GetLocalAddress(),
688                                               *sid,
689                                               timeout,
690                                               response);
691             return NPT_SUCCESS;
692         } else {
693             // new subscription ?
694             // verify nt is present and valid
695             if (!nt || nt->Compare("upnp:event", true)) {
696                 response.SetStatus(412, "Precondition failed");
697                 return NPT_SUCCESS;
698             }
699             // verify callback is present
700             if (!callback_urls) {
701                 response.SetStatus(412, "Precondition failed");
702                 return NPT_SUCCESS;
703             }
704 
705             // default lease time
706             NPT_Int32 timeout = (NPT_Int32)*PLT_Constants::GetInstance().GetDefaultSubscribeLease().AsPointer();
707 
708             // send the info to the service
709             service->ProcessNewSubscription(m_TaskManager,
710                                             context.GetLocalAddress(),
711                                             *callback_urls,
712                                             timeout,
713                                             response);
714             return NPT_SUCCESS;
715         }
716     } else if (method.Compare("UNSUBSCRIBE") == 0) {
717         // Do we have a sid ?
718         if (sid && sid->GetLength() > 0) {
719             // make sure we don't have a callback nor a nt
720             if (nt || callback_urls) {
721                 goto cleanup;
722             }
723 
724             // subscription cancelled
725             // send the info to the service
726             service->ProcessCancelSubscription(context.GetLocalAddress(),
727                                                *sid,
728                                                response);
729             return NPT_SUCCESS;
730         }
731 
732         response.SetStatus(412, "Precondition failed");
733         return NPT_SUCCESS;
734     }
735 
736 cleanup:
737     response.SetStatus(400, "Bad Request");
738     return NPT_SUCCESS;
739 }
740 
741 /*----------------------------------------------------------------------
742 |   PLT_DeviceHost::OnSsdpPacket
743 +---------------------------------------------------------------------*/
744 NPT_Result
OnSsdpPacket(const NPT_HttpRequest & request,const NPT_HttpRequestContext & context)745 PLT_DeviceHost::OnSsdpPacket(const NPT_HttpRequest&        request,
746                              const NPT_HttpRequestContext& context)
747 {
748     // get the address of who sent us some data back*/
749     NPT_String ip_address  = context.GetRemoteAddress().GetIpAddress().ToString();
750     NPT_String method      = request.GetMethod();
751     NPT_String url         = request.GetUrl().ToRequestString(true);
752     NPT_String protocol    = request.GetProtocol();
753     NPT_IpPort remote_port = context.GetRemoteAddress().GetPort();
754     const NPT_String* st   = PLT_UPnPMessageHelper::GetST(request);
755 
756     if (method.Compare("M-SEARCH") == 0) {
757         NPT_String prefix = NPT_String::Format("PLT_DeviceHost::OnSsdpPacket M-SEARCH for %s from %s:%d",
758             st?st->GetChars():"Unknown",
759             (const char*) ip_address, remote_port);
760         PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, prefix, &request);
761 
762         /*
763         // DLNA 7.2.3.5 support
764         if (remote_port < 1024 || remote_port == 1900) {
765             NPT_LOG_INFO_2("Ignoring M-SEARCH from %s:%d (invalid source port)",
766                 (const char*) ip_address,
767                 remote_port);
768             return NPT_FAILURE;
769         }
770          */
771 
772         NPT_CHECK_POINTER_SEVERE(st);
773 
774         if (url.Compare("*") || protocol.Compare("HTTP/1.1"))
775             return NPT_FAILURE;
776 
777         const NPT_String* man = PLT_UPnPMessageHelper::GetMAN(request);
778         if (!man || man->Compare("\"ssdp:discover\"", true))
779             return NPT_FAILURE;
780 
781         NPT_UInt32 mx;
782         NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetMX(request, mx));
783 
784         // create a task to respond to the request
785         NPT_TimeInterval timer((mx==0)?0.:(double)(NPT_System::GetRandomInteger()%(mx>5?5:mx)));
786         PLT_SsdpDeviceSearchResponseTask* task = new PLT_SsdpDeviceSearchResponseTask(this, context.GetRemoteAddress(), *st);
787         m_TaskManager->StartTask(task, &timer);
788         return NPT_SUCCESS;
789     } else {
790         NPT_String prefix = NPT_String::Format("Ignoring %s request from %s:%d",
791                                                method.GetChars(),
792                                                (const char*) ip_address, remote_port);
793         PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINE, prefix, &request);
794     }
795 
796     return NPT_FAILURE;
797 }
798 
799 /*----------------------------------------------------------------------
800 |   PLT_DeviceHost::SendSsdpSearchResponse
801 +---------------------------------------------------------------------*/
802 NPT_Result
SendSsdpSearchResponse(PLT_DeviceData * device,NPT_HttpResponse & response,NPT_UdpSocket & socket,const char * st,const NPT_SocketAddress * addr)803 PLT_DeviceHost::SendSsdpSearchResponse(PLT_DeviceData*    device,
804                                        NPT_HttpResponse&  response,
805                                        NPT_UdpSocket&     socket,
806                                        const char*        st,
807                                        const NPT_SocketAddress* addr /* = NULL */)
808 {
809     // UPnP 1.1 BOOTID.UPNP.ORG header
810     PLT_UPnPMessageHelper::SetBootId(response, device->m_BootId);
811 
812     // UPnP 1.1 CONFIGID.UPNP.ORG header
813     if (device->m_ConfigId > 0) {
814         PLT_UPnPMessageHelper::SetConfigId(response, device->m_ConfigId);
815     }
816 
817     // ssdp:all or upnp:rootdevice
818     if (NPT_String::Compare(st, "ssdp:all") == 0 ||
819         NPT_String::Compare(st, "upnp:rootdevice") == 0) {
820 
821         if (device->m_ParentUUID.IsEmpty()) {
822             NPT_LOG_FINE_1("Responding to a M-SEARCH request for %s", st);
823 
824            // upnp:rootdevice
825            PLT_SsdpSender::SendSsdp(response,
826                     NPT_String("uuid:" + device->m_UUID + "::upnp:rootdevice"),
827                     "upnp:rootdevice",
828                     socket,
829                     false,
830                     addr);
831         }
832     }
833 
834     // uuid:device-UUID
835     if (NPT_String::Compare(st, "ssdp:all") == 0 ||
836         NPT_String::Compare(st, (const char*)("uuid:" + device->m_UUID)) == 0) {
837 
838         NPT_LOG_FINE_1("Responding to a M-SEARCH request for %s", st);
839 
840         // uuid:device-UUID
841         PLT_SsdpSender::SendSsdp(response,
842                  "uuid:" + device->m_UUID,
843                  "uuid:" + device->m_UUID,
844                  socket,
845                  false,
846                  addr);
847     }
848 
849     // urn:schemas-upnp-org:device:deviceType:ver
850     if (NPT_String::Compare(st, "ssdp:all") == 0 ||
851         NPT_String::Compare(st, (const char*)(device->m_DeviceType)) == 0) {
852 
853         NPT_LOG_FINE_1("Responding to a M-SEARCH request for %s", st);
854 
855         // uuid:device-UUID::urn:schemas-upnp-org:device:deviceType:ver
856         PLT_SsdpSender::SendSsdp(response,
857                  NPT_String("uuid:" + device->m_UUID + "::" + device->m_DeviceType),
858                  device->m_DeviceType,
859                  socket,
860                  false,
861                  addr);
862     }
863 
864     // services
865     for (int i=0; i < (int)device->m_Services.GetItemCount(); i++) {
866         if (NPT_String::Compare(st, "ssdp:all") == 0 ||
867             NPT_String::Compare(st, (const char*)(device->m_Services[i]->GetServiceType())) == 0) {
868 
869             NPT_LOG_FINE_1("Responding to a M-SEARCH request for %s", st);
870 
871             // uuid:device-UUID::urn:schemas-upnp-org:service:serviceType:ver
872             PLT_SsdpSender::SendSsdp(response,
873                      NPT_String("uuid:" + device->m_UUID + "::" + device->m_Services[i]->GetServiceType()),
874                      device->m_Services[i]->GetServiceType(),
875                      socket,
876                      false,
877                      addr);
878         }
879     }
880 
881     // embedded devices
882     for (int j=0; j < (int)device->m_EmbeddedDevices.GetItemCount(); j++) {
883         SendSsdpSearchResponse(device->m_EmbeddedDevices[j].AsPointer(),
884             response,
885             socket,
886             st,
887             addr);
888     }
889 
890     return NPT_SUCCESS;
891 }
892 
893 /*----------------------------------------------------------------------
894 |   PLT_DeviceHost::OnAction
895 +---------------------------------------------------------------------*/
896 NPT_Result
OnAction(PLT_ActionReference & action,const PLT_HttpRequestContext & context)897 PLT_DeviceHost::OnAction(PLT_ActionReference&          action,
898                          const PLT_HttpRequestContext& context)
899 {
900     NPT_COMPILER_UNUSED(context);
901     action->SetError(401, "Invalid Action");
902     return NPT_FAILURE;
903 }
904 
905