comparison deps/Platinum/Source/Core/PltCtrlPoint.cpp @ 0:3425707ddbf6

Initial import (hopefully this mercurial stuff works...)
author fraserofthenight
date Mon, 06 Jul 2009 08:06:28 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3425707ddbf6
1 /*****************************************************************
2 |
3 | Platinum - Control Point
4 |
5 | Copyright (c) 2004-2008, 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 |
21 | This program is distributed in the hope that it will be useful,
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | GNU General Public License for more details.
25 |
26 | You should have received a copy of the GNU General Public License
27 | along with this program; see the file LICENSE.txt. If not, write to
28 | the Free Software Foundation, Inc.,
29 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 | http://www.gnu.org/licenses/gpl-2.0.html
31 |
32 ****************************************************************/
33
34 /*----------------------------------------------------------------------
35 | includes
36 +---------------------------------------------------------------------*/
37 #include "PltCtrlPoint.h"
38 #include "PltUPnP.h"
39 #include "PltDeviceData.h"
40 #include "PltXmlHelper.h"
41 #include "PltCtrlPointTask.h"
42 #include "PltSsdp.h"
43 #include "PltHttpServer.h"
44
45 NPT_SET_LOCAL_LOGGER("platinum.core.ctrlpoint")
46
47 /*----------------------------------------------------------------------
48 | typedef
49 +---------------------------------------------------------------------*/
50 typedef PLT_HttpRequestHandler<PLT_CtrlPoint> PLT_HttpCtrlPointRequestHandler;
51
52 /*----------------------------------------------------------------------
53 | PLT_CtrlPointListenerOnDeviceAddedIterator class
54 +---------------------------------------------------------------------*/
55 class PLT_CtrlPointListenerOnDeviceAddedIterator
56 {
57 public:
58 PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference& device) :
59 m_Device(device) {}
60
61 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
62 return listener->OnDeviceAdded(m_Device);
63 }
64
65 private:
66 PLT_DeviceDataReference& m_Device;
67 };
68
69 /*----------------------------------------------------------------------
70 | PLT_CtrlPointListenerOnDeviceRemovedIterator class
71 +---------------------------------------------------------------------*/
72 class PLT_CtrlPointListenerOnDeviceRemovedIterator
73 {
74 public:
75 PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference& device) :
76 m_Device(device) {}
77
78 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
79 return listener->OnDeviceRemoved(m_Device);
80 }
81
82 private:
83 PLT_DeviceDataReference& m_Device;
84 };
85
86 /*----------------------------------------------------------------------
87 | PLT_CtrlPointListenerOnActionResponseIterator class
88 +---------------------------------------------------------------------*/
89 class PLT_CtrlPointListenerOnActionResponseIterator
90 {
91 public:
92 PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res,
93 PLT_ActionReference& action,
94 void* userdata) :
95 m_Res(res), m_Action(action), m_Userdata(userdata) {}
96
97 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
98 return listener->OnActionResponse(m_Res, m_Action, m_Userdata);
99 }
100
101 private:
102 NPT_Result m_Res;
103 PLT_ActionReference& m_Action;
104 void* m_Userdata;
105 };
106
107 /*----------------------------------------------------------------------
108 | PLT_CtrlPointListenerOnEventNotifyIterator class
109 +---------------------------------------------------------------------*/
110 class PLT_CtrlPointListenerOnEventNotifyIterator
111 {
112 public:
113 PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service* service,
114 NPT_List<PLT_StateVariable*>* vars) :
115 m_Service(service), m_Vars(vars) {}
116
117 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
118 return listener->OnEventNotify(m_Service, m_Vars);
119 }
120
121 private:
122 PLT_Service* m_Service;
123 NPT_List<PLT_StateVariable*>* m_Vars;
124 };
125
126 /*----------------------------------------------------------------------
127 | PLT_AddGetSCPDRequestIterator class
128 +---------------------------------------------------------------------*/
129 class PLT_AddGetSCPDRequestIterator
130 {
131 public:
132 PLT_AddGetSCPDRequestIterator(PLT_TaskManager* task_manager,
133 PLT_CtrlPoint* ctrl_point,
134 PLT_DeviceDataReference& device) :
135 m_TaskManager(task_manager),
136 m_CtrlPoint(ctrl_point),
137 m_Device(device) {}
138
139 NPT_Result operator()(PLT_Service*& service) const {
140 // look for the host and port of the device
141 PLT_DeviceData* device = service->GetDevice();
142 NPT_String scpd_url = service->GetSCPDURL();
143
144 NPT_LOG_INFO_2("Fetching SCPD for service \"%s\" of device \"%s\"",
145 (const char*)service->GetServiceID(),
146 (const char*)device->GetFriendlyName());
147
148 // if the SCPD Url starts with a "/", this means we should not append it to the base URI
149 // but instead go there directly
150 if (!scpd_url.StartsWith("/")) {
151 scpd_url = device->GetURLBase().GetPath() + scpd_url;
152 }
153
154 NPT_HttpUrl url(device->GetURLBase().GetHost(),
155 device->GetURLBase().GetPort(),
156 scpd_url);
157
158 // Add a delay, some devices need it (aka Rhapsody)
159 NPT_TimeInterval delay(0.1f);
160 return m_TaskManager->StartTask(
161 new PLT_CtrlPointGetSCPDTask(url,
162 m_CtrlPoint,
163 (PLT_DeviceDataReference&)m_Device),
164 &delay);
165 }
166
167 private:
168 PLT_TaskManager* m_TaskManager;
169 PLT_CtrlPoint* m_CtrlPoint;
170 PLT_DeviceDataReference m_Device;
171 };
172
173 /*----------------------------------------------------------------------
174 | PLT_EventSubscriberRemoverIterator class
175 +---------------------------------------------------------------------*/
176 class PLT_EventSubscriberRemoverIterator
177 {
178 public:
179 PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint* ctrl_point) :
180 m_CtrlPoint(ctrl_point) {
181 m_CtrlPoint->m_Subscribers.Lock();
182 }
183 ~PLT_EventSubscriberRemoverIterator() {
184 m_CtrlPoint->m_Subscribers.Unlock();
185 }
186
187 NPT_Result operator()(PLT_Service*& service) const {
188 PLT_EventSubscriber* sub = NULL;
189 if (NPT_SUCCEEDED(NPT_ContainerFind(m_CtrlPoint->m_Subscribers,
190 PLT_EventSubscriberFinderByService(service), sub))) {
191 m_CtrlPoint->m_Subscribers.Remove(sub);
192 delete sub;
193 }
194
195 return NPT_SUCCESS;
196 }
197
198 private:
199 PLT_CtrlPoint* m_CtrlPoint;
200 };
201
202 /*----------------------------------------------------------------------
203 | PLT_ServiceReadyIterator class
204 +---------------------------------------------------------------------*/
205 class PLT_ServiceReadyIterator
206 {
207 public:
208 PLT_ServiceReadyIterator() {}
209
210 NPT_Result operator()(PLT_Service*& service) const {
211 return service->IsInitted()?NPT_SUCCESS:NPT_FAILURE;
212 }
213 };
214
215 /*----------------------------------------------------------------------
216 | PLT_DeviceReadyIterator class
217 +---------------------------------------------------------------------*/
218 class PLT_DeviceReadyIterator
219 {
220 public:
221 PLT_DeviceReadyIterator() {}
222 NPT_Result operator()(PLT_DeviceDataReference& device) const {
223 NPT_CHECK(device->m_Services.ApplyUntil(
224 PLT_ServiceReadyIterator(),
225 NPT_UntilResultNotEquals(NPT_SUCCESS)));
226
227 NPT_CHECK(device->m_EmbeddedDevices.ApplyUntil(
228 PLT_DeviceReadyIterator(),
229 NPT_UntilResultNotEquals(NPT_SUCCESS)));
230
231 // a device must have at least one service or embedded device
232 // otherwise it's not ready
233 if (device->m_Services.GetItemCount() == 0 &&
234 device->m_EmbeddedDevices.GetItemCount() == 0) {
235 return NPT_FAILURE;
236 }
237
238 return NPT_SUCCESS;
239 }
240 };
241
242 /*----------------------------------------------------------------------
243 | PLT_CtrlPoint::PLT_CtrlPoint
244 +---------------------------------------------------------------------*/
245 PLT_CtrlPoint::PLT_CtrlPoint(const char* autosearch /* = "upnp:rootdevice" */) :
246 m_EventHttpServer(new PLT_HttpServer()),
247 m_AutoSearch(autosearch)
248 {
249 m_EventHttpServerHandler = new PLT_HttpCtrlPointRequestHandler(this);
250 m_EventHttpServer->AddRequestHandler(m_EventHttpServerHandler, "/", true);
251 }
252
253 /*----------------------------------------------------------------------
254 | PLT_CtrlPoint::~PLT_CtrlPoint
255 +---------------------------------------------------------------------*/
256 PLT_CtrlPoint::~PLT_CtrlPoint()
257 {
258 delete m_EventHttpServer;
259 delete m_EventHttpServerHandler;
260 }
261
262 /*----------------------------------------------------------------------
263 | PLT_CtrlPoint::IgnoreUUID
264 +---------------------------------------------------------------------*/
265 void
266 PLT_CtrlPoint::IgnoreUUID(const char* uuid)
267 {
268 if (!m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
269 m_UUIDsToIgnore.Add(uuid);
270 }
271 }
272
273 /*----------------------------------------------------------------------
274 | PLT_CtrlPoint::Start
275 +---------------------------------------------------------------------*/
276 NPT_Result
277 PLT_CtrlPoint::Start(PLT_SsdpListenTask* task)
278 {
279 m_EventHttpServer->Start();
280
281 // house keeping task
282 m_TaskManager.StartTask(new PLT_CtrlPointHouseKeepingTask(this));
283
284 task->AddListener(this);
285 return m_AutoSearch.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_AutoSearch):NPT_SUCCESS;
286 }
287
288 /*----------------------------------------------------------------------
289 | PLT_CtrlPoint::Stop
290 +---------------------------------------------------------------------*/
291 NPT_Result
292 PLT_CtrlPoint::Stop(PLT_SsdpListenTask* task)
293 {
294 task->RemoveListener(this);
295
296 m_TaskManager.StopAllTasks();
297 m_EventHttpServer->Stop();
298
299 // we can safely clear everything without a lock
300 // as there are no more tasks pending
301 m_Devices.Clear();
302
303 m_Subscribers.Apply(NPT_ObjectDeleter<PLT_EventSubscriber>());
304 m_Subscribers.Clear();
305
306 return NPT_SUCCESS;
307 }
308
309 /*----------------------------------------------------------------------
310 | PLT_CtrlPoint::AddListener
311 +---------------------------------------------------------------------*/
312 NPT_Result
313 PLT_CtrlPoint::AddListener(PLT_CtrlPointListener* listener)
314 {
315 NPT_AutoLock lock(m_ListenerList);
316 if (!m_ListenerList.Contains(listener)) {
317 m_ListenerList.Add(listener);
318 }
319 return NPT_SUCCESS;
320 }
321
322 /*----------------------------------------------------------------------
323 | PLT_CtrlPoint::RemoveListener
324 +---------------------------------------------------------------------*/
325 NPT_Result
326 PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener* listener)
327 {
328 NPT_AutoLock lock(m_ListenerList);
329 m_ListenerList.Remove(listener);
330 return NPT_SUCCESS;
331 }
332
333 /*----------------------------------------------------------------------
334 | PLT_CtrlPoint::CreateSearchTask
335 +---------------------------------------------------------------------*/
336 PLT_SsdpSearchTask*
337 PLT_CtrlPoint::CreateSearchTask(const NPT_HttpUrl& url,
338 const char* target,
339 NPT_Cardinal mx,
340 const NPT_IpAddress& address)
341 {
342 // make sure mx is at least 1
343 if (mx<1) mx=1;
344
345 // create socket
346 NPT_UdpMulticastSocket* socket = new NPT_UdpMulticastSocket();
347 socket->SetInterface(address);
348 socket->SetTimeToLive(4);
349
350 // create request
351 NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1);
352 PLT_UPnPMessageHelper::SetMX(*request, mx);
353 PLT_UPnPMessageHelper::SetST(*request, target);
354 PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\"");
355 request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, NPT_HttpClient::m_UserAgentHeader);
356
357 // create task
358 PLT_SsdpSearchTask* task = new PLT_SsdpSearchTask(
359 socket,
360 this,
361 request,
362 mx*10000);
363 return task;
364 }
365
366 /*----------------------------------------------------------------------
367 | PLT_CtrlPoint::Search
368 +---------------------------------------------------------------------*/
369 NPT_Result
370 PLT_CtrlPoint::Search(const NPT_HttpUrl& url,
371 const char* target,
372 NPT_Cardinal mx /* = 5 */)
373 {
374 NPT_List<NPT_NetworkInterface*> if_list;
375 NPT_List<NPT_NetworkInterface*>::Iterator net_if;
376 NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr;
377
378 NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list));
379
380 for (net_if = if_list.GetFirstItem(); net_if; net_if++) {
381 // make sure the interface is at least broadcast or multicast
382 if (!((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST) &&
383 !((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST)) {
384 continue;
385 }
386
387 for (net_if_addr = (*net_if)->GetAddresses().GetFirstItem(); net_if_addr; net_if_addr++) {
388 // create task
389 PLT_SsdpSearchTask* task = CreateSearchTask(url,
390 target,
391 mx,
392 (*net_if_addr).GetPrimaryAddress());
393 m_TaskManager.StartTask(task);
394 }
395 }
396
397 // {
398 // // create task on 127.0.0.1
399 // NPT_IpAddress address;
400 // address.ResolveName("127.0.0.1");
401 //
402 // PLT_ThreadTask* task = CreateSearchTask(url,
403 // target,
404 // mx,
405 // address);
406 // m_TaskManager.StartTask(task);
407 // }
408
409 if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
410 return NPT_SUCCESS;
411 }
412
413 /*----------------------------------------------------------------------
414 | PLT_CtrlPoint::Discover
415 +---------------------------------------------------------------------*/
416 NPT_Result
417 PLT_CtrlPoint::Discover(const NPT_HttpUrl& url,
418 const char* target,
419 NPT_Cardinal mx /* = 5 */,
420 NPT_Timeout repeat /* = 50000 */)
421 {
422 // make sure mx is at least 1
423 if (mx<1) mx = 1;
424
425 // create socket
426 NPT_UdpSocket* socket = new NPT_UdpSocket();
427
428 // create request
429 NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1);
430 PLT_UPnPMessageHelper::SetMX(*request, mx);
431 PLT_UPnPMessageHelper::SetST(*request, target);
432 PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\"");
433 request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, NPT_HttpClient::m_UserAgentHeader);
434
435 // force HOST to be the regular multicast address:port
436 // Some servers do care (like WMC) otherwise they won't respond to us
437 request->GetHeaders().SetHeader(NPT_HTTP_HEADER_HOST, "239.255.255.250:1900");
438
439 // create task
440 PLT_ThreadTask* task = new PLT_SsdpSearchTask(
441 socket,
442 this,
443 request,
444 repeat<(NPT_Timeout)mx*5000?(NPT_Timeout)mx*5000:repeat); /* repeat no less than every 5 secs */
445 return m_TaskManager.StartTask(task);
446 }
447
448 /*----------------------------------------------------------------------
449 | PLT_CtrlPoint::DoHouseKeeping
450 +---------------------------------------------------------------------*/
451 NPT_Result
452 PLT_CtrlPoint::DoHouseKeeping()
453 {
454 NPT_AutoLock lock_devices(m_Devices);
455 NPT_TimeStamp now;
456 int count = m_Devices.GetItemCount();
457 NPT_System::GetCurrentTimeStamp(now);
458
459 PLT_DeviceDataReference device;
460 while (count--) {
461 NPT_Result res = m_Devices.PopHead(device);
462 if (NPT_SUCCEEDED(res)) {
463 NPT_TimeStamp last_update = device->GetLeaseTimeLastUpdate();
464 NPT_TimeInterval lease_time = device->GetLeaseTime();
465
466 // check if device lease time has expired
467 if (now > last_update + NPT_TimeInterval((unsigned long)(((float)lease_time)*2), 0)) {
468 RemoveDevice(device);
469 } else {
470 // add the device back to our list since it is still alive
471 m_Devices.Add(device);
472 }
473 } else {
474 NPT_LOG_SEVERE("DoHouseKeeping failure!");
475 return NPT_FAILURE;
476 }
477 }
478
479 return NPT_SUCCESS;
480 }
481
482 /*----------------------------------------------------------------------
483 | PLT_CtrlPoint::FindDevice
484 +---------------------------------------------------------------------*/
485 NPT_Result
486 PLT_CtrlPoint::FindDevice(const char* uuid,
487 PLT_DeviceDataReference& device)
488 {
489 NPT_AutoLock lock(m_Devices);
490 return NPT_ContainerFind(m_Devices, PLT_DeviceDataFinder(uuid), device);
491 }
492
493 /*----------------------------------------------------------------------
494 | PLT_CtrlPoint::ProcessHttpRequest
495 +---------------------------------------------------------------------*/
496 NPT_Result
497 PLT_CtrlPoint::ProcessHttpRequest(NPT_HttpRequest& request,
498 const NPT_HttpRequestContext& context,
499 NPT_HttpResponse& response)
500 {
501 NPT_COMPILER_UNUSED(context);
502 if (!request.GetMethod().Compare("NOTIFY")) {
503 return ProcessHttpNotify(request, context, response);
504 }
505
506 NPT_LOG_SEVERE("CtrlPoint received bad http request\r\n");
507 response.SetStatus(412, "Precondition Failed");
508 return NPT_SUCCESS;
509 }
510
511 /*----------------------------------------------------------------------
512 | PLT_CtrlPoint::ProcessHttpNotify
513 +---------------------------------------------------------------------*/
514 NPT_Result
515 PLT_CtrlPoint::ProcessHttpNotify(NPT_HttpRequest& request,
516 const NPT_HttpRequestContext& context,
517 NPT_HttpResponse& response)
518 {
519 NPT_COMPILER_UNUSED(context);
520
521 NPT_List<PLT_StateVariable*> vars;
522 PLT_EventSubscriber* sub = NULL;
523 NPT_String str;
524 NPT_XmlElementNode* xml = NULL;
525 NPT_String callback_uri;
526 NPT_String uuid;
527 NPT_String service_id;
528 NPT_UInt32 seq = 0;
529 PLT_Service* service = NULL;
530 PLT_DeviceData* device = NULL;
531 NPT_String content_type;
532
533 NPT_String method = request.GetMethod();
534 NPT_String uri = request.GetUrl().GetPath();
535
536 // NPT_LOG_FINE_3("CtrlPoint received %s request from %s:%d\r\n",
537 // request.GetMethod(),
538 // client_info.remote_address.GetIpAddress(),
539 // client_info.remote_address.GetPort());
540 //
541 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, &request);
542
543 const NPT_String* sid = PLT_UPnPMessageHelper::GetSID(request);
544 const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request);
545 const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request);
546 PLT_HttpHelper::GetContentType(request, content_type);
547
548 {
549 NPT_AutoLock lock_subs(m_Subscribers);
550
551 // look for the subscriber with that subscription url
552 if (!sid || NPT_FAILED(NPT_ContainerFind(m_Subscribers,
553 PLT_EventSubscriberFinderBySID(*sid),
554 sub))) {
555 NPT_LOG_FINE_1("Subscriber %s not found\n", (const char*)sid);
556 goto bad_request;
557 }
558
559 // verify the request is syntactically correct
560 service = sub->GetService();
561 device = service->GetDevice();
562
563 uuid = device->GetUUID();
564 service_id = service->GetServiceID();
565
566 // callback uri for this sub
567 callback_uri = "/" + uuid + "/" + service_id;
568
569 if (uri.Compare(callback_uri, true) ||
570 !nt || nt->Compare("upnp:event", true) ||
571 !nts || nts->Compare("upnp:propchange", true)) {
572 goto bad_request;
573 }
574
575 // if the sequence number is less than our current one, we got it out of order
576 // so we disregard it
577 PLT_UPnPMessageHelper::GetSeq(request, seq);
578 if (sub->GetEventKey() && seq <= sub->GetEventKey()) {
579 goto bad_request;
580 }
581
582 // parse body
583 if (NPT_FAILED(PLT_HttpHelper::ParseBody(request, xml))) {
584 goto bad_request;
585 }
586
587 // check envelope
588 if (xml->GetTag().Compare("propertyset", true))
589 goto bad_request;
590
591 // check namespace
592 // xml.GetAttrValue("xmlns:e", str);
593 // if (str.Compare("urn:schemas-upnp-org:event-1-0"))
594 // goto bad_request;
595
596 // check property set
597 // keep a vector of the state variables that changed
598 NPT_XmlElementNode* property;
599 PLT_StateVariable* var;
600 for (NPT_List<NPT_XmlNode*>::Iterator children = xml->GetChildren().GetFirstItem(); children; children++) {
601 NPT_XmlElementNode* child = (*children)->AsElementNode();
602 if (!child) continue;
603
604 // check property
605 if (child->GetTag().Compare("property", true))
606 goto bad_request;
607
608 if (NPT_FAILED(PLT_XmlHelper::GetChild(child, property))) {
609 goto bad_request;
610 }
611
612 var = service->FindStateVariable(property->GetTag());
613 if (var == NULL) {
614 goto bad_request;
615 }
616
617 if (NPT_FAILED(var->SetValue(property->GetText()?*property->GetText():""))) {
618 goto bad_request;
619 }
620 vars.Add(var);
621 }
622
623 // update sequence
624 sub->SetEventKey(seq);
625 }
626
627 // notify listener we got an update
628 if (vars.GetItemCount()) {
629 NPT_AutoLock lock(m_ListenerList);
630 m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
631 }
632
633 delete xml;
634 return NPT_SUCCESS;
635
636 bad_request:
637 NPT_LOG_SEVERE("CtrlPoint received bad request\r\n");
638 response.SetStatus(412, "Precondition Failed");
639 delete xml;
640 return NPT_SUCCESS;
641 }
642
643 /*----------------------------------------------------------------------
644 | PLT_CtrlPoint::ProcessSsdpSearchResponse
645 +---------------------------------------------------------------------*/
646 NPT_Result
647 PLT_CtrlPoint::ProcessSsdpSearchResponse(NPT_Result res,
648 const NPT_HttpRequestContext& context,
649 NPT_HttpResponse* response)
650 {
651 NPT_CHECK_SEVERE(res);
652 NPT_CHECK_POINTER_SEVERE(response);
653
654 NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
655 NPT_String protocol = response->GetProtocol();
656
657 NPT_LOG_FINE_2("CtrlPoint received SSDP search response from %s:%d",
658 (const char*)context.GetRemoteAddress().GetIpAddress().ToString() ,
659 context.GetRemoteAddress().GetPort());
660 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response);
661
662 if (response->GetStatusCode() == 200) {
663 const NPT_String* st = response->GetHeaders().GetHeaderValue("st");
664 const NPT_String* usn = response->GetHeaders().GetHeaderValue("usn");
665 const NPT_String* ext = response->GetHeaders().GetHeaderValue("ext");
666 NPT_CHECK_POINTER_SEVERE(st);
667 NPT_CHECK_POINTER_SEVERE(usn);
668 NPT_CHECK_POINTER_SEVERE(ext);
669
670 NPT_String uuid;
671 // if we get an advertisement other than uuid
672 // verify it's formatted properly
673 if (usn != st) {
674 char tmp_uuid[200];
675 char tmp_st[200];
676 int ret;
677 // FIXME: We can't use sscanf directly!
678 ret = sscanf(((const char*)*usn)+5, "%[^::]::%s",
679 tmp_uuid,
680 tmp_st);
681 if (ret != 2)
682 return NPT_FAILURE;
683
684 if (st->Compare(tmp_st, true))
685 return NPT_FAILURE;
686
687 uuid = tmp_uuid;
688 } else {
689 uuid = ((const char*)*usn)+5;
690 }
691
692 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
693 NPT_LOG_FINE_1("CtrlPoint received a search response from ourselves (%s)\n", (const char*)uuid);
694 return NPT_SUCCESS;
695 }
696
697 return ProcessSsdpMessage(response, context, uuid);
698 }
699
700 return NPT_FAILURE;
701 }
702
703 /*----------------------------------------------------------------------
704 | PLT_CtrlPoint::OnSsdpPacket
705 +---------------------------------------------------------------------*/
706 NPT_Result
707 PLT_CtrlPoint::OnSsdpPacket(NPT_HttpRequest& request,
708 const NPT_HttpRequestContext& context)
709 {
710 return ProcessSsdpNotify(request, context);
711 }
712
713 /*----------------------------------------------------------------------
714 | PLT_CtrlPoint::ProcessSsdpNotify
715 +---------------------------------------------------------------------*/
716 NPT_Result
717 PLT_CtrlPoint::ProcessSsdpNotify(NPT_HttpRequest& request,
718 const NPT_HttpRequestContext& context)
719 {
720 // get the address of who sent us some data back
721 NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
722 NPT_String method = request.GetMethod();
723 NPT_String uri = (const char*)request.GetUrl().GetPath();
724 NPT_String protocol = request.GetProtocol();
725
726 if (method.Compare("NOTIFY") == 0) {
727 NPT_LOG_INFO_2("Received SSDP NOTIFY from %s:%d",
728 context.GetRemoteAddress().GetIpAddress().ToString().GetChars(),
729 context.GetRemoteAddress().GetPort());
730 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request);
731
732 if ((uri.Compare("*") != 0) || (protocol.Compare("HTTP/1.1") != 0))
733 return NPT_FAILURE;
734
735 const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request);
736 const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request);
737 const NPT_String* usn = PLT_UPnPMessageHelper::GetUSN(request);
738 NPT_CHECK_POINTER_SEVERE(nts);
739 NPT_CHECK_POINTER_SEVERE(nt);
740 NPT_CHECK_POINTER_SEVERE(usn);
741
742 NPT_String uuid;
743 // if we get an advertisement other than uuid
744 // verify it's formatted properly
745 if (*usn != *nt) {
746 char tmp_uuid[200];
747 char tmp_nt[200];
748 int ret;
749 //FIXME: no sscanf!
750 ret = sscanf(((const char*)*usn)+5, "%[^::]::%s",
751 tmp_uuid,
752 tmp_nt);
753 if (ret != 2)
754 return NPT_FAILURE;
755
756 if (nt->Compare(tmp_nt, true))
757 return NPT_FAILURE;
758
759 uuid = tmp_uuid;
760 } else {
761 uuid = ((const char*)*usn)+5;
762 }
763
764 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
765 NPT_LOG_FINE_1("Received a NOTIFY request from ourselves (%s)\n", (const char*)uuid);
766 return NPT_SUCCESS;
767 }
768
769 // if it's a byebye, remove the device and return right away
770 if (nts->Compare("ssdp:byebye", true) == 0) {
771 NPT_LOG_INFO_1("Received a byebye NOTIFY request from %s\n", (const char*)uuid);
772
773 PLT_DeviceDataReference data;
774 if (NPT_SUCCEEDED(FindDevice(uuid, data))) {
775 NPT_AutoLock lock_devices(m_Devices);
776 RemoveDevice(data);
777 }
778
779 return NPT_SUCCESS;
780 }
781
782 return ProcessSsdpMessage(&request, context, uuid);
783 }
784
785 return NPT_FAILURE;
786 }
787
788 /*----------------------------------------------------------------------
789 | PLT_CtrlPoint::RemoveDevice
790 +---------------------------------------------------------------------*/
791 NPT_Result
792 PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference& data)
793 {
794 NPT_LOG_INFO_1("Removing %s from device list\n", (const char*)data->GetUUID());
795
796 /* recursively remove embedded devices */
797 NPT_Array<PLT_DeviceDataReference> embedded_devices =
798 data->GetEmbeddedDevices();
799 for(NPT_Cardinal i=0;i>embedded_devices.GetItemCount();i++) {
800 RemoveDevice(embedded_devices[i]);
801 }
802
803 /* remove from list */
804 m_Devices.Remove(data);
805
806 /* unsubscribe from services */
807 data->m_Services.Apply(PLT_EventSubscriberRemoverIterator(this));
808
809 /* notify listeners */
810 {
811 NPT_AutoLock lock(m_ListenerList);
812 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceRemovedIterator(data));
813 }
814
815 return NPT_SUCCESS;
816 }
817
818 /*----------------------------------------------------------------------
819 | PLT_CtrlPoint::ProcessSsdpMessage
820 +---------------------------------------------------------------------*/
821 NPT_Result
822 PLT_CtrlPoint::ProcessSsdpMessage(NPT_HttpMessage* message,
823 const NPT_HttpRequestContext& context,
824 NPT_String& uuid)
825 {
826 NPT_COMPILER_UNUSED(context);
827 NPT_CHECK_POINTER_SEVERE(message);
828
829 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) return NPT_SUCCESS;
830
831 const NPT_String* location = PLT_UPnPMessageHelper::GetLocation(*message);
832 NPT_CHECK_POINTER_SEVERE(location);
833
834 // be nice and assume a default lease time if not found
835 NPT_Timeout leasetime;
836 if (NPT_FAILED(PLT_UPnPMessageHelper::GetLeaseTime(*message, leasetime))) {
837 leasetime = 1800;
838 }
839
840 return InspectDevice(*location, uuid, leasetime);
841 }
842
843 /*----------------------------------------------------------------------
844 | PLT_CtrlPoint::InspectDevice
845 +---------------------------------------------------------------------*/
846 NPT_Result
847 PLT_CtrlPoint::InspectDevice(const char* location,
848 const char* uuid,
849 NPT_Timeout leasetime)
850 {
851 NPT_HttpUrl url(location);
852 if (!url.IsValid()) return NPT_FAILURE;
853
854 // is it a new device?
855 PLT_DeviceDataReference data;
856 if (NPT_FAILED(FindDevice(uuid, data))) {
857 NPT_AutoLock lock(m_Devices);
858
859 NPT_LOG_INFO_2("New device \"%s\" detected @ %s", uuid, location);
860
861 data = new PLT_DeviceData(url, uuid, NPT_TimeInterval(leasetime, 0));
862 m_Devices.Add(data);
863
864 // Start a task to retrieve the description
865 PLT_CtrlPointGetDescriptionTask* task = new PLT_CtrlPointGetDescriptionTask(
866 url,
867 this,
868 data);
869 // Add a delay, some devices need it (aka Rhapsody)
870 NPT_TimeInterval delay(0.2f);
871 m_TaskManager.StartTask(task, &delay);
872
873 return NPT_SUCCESS;
874 }
875
876 // in case we missed the byebye and the device description has changed (ip or port)
877 // reset base and assumes device is the same (same number of services and SCPDs)
878 // FIXME: The right way is to remove the device and rescan it though
879 PLT_DeviceReadyIterator device_tester;
880 if (NPT_SUCCEEDED(device_tester(data)) &&
881 data->GetDescriptionUrl().Compare(location, true)) {
882 NPT_LOG_INFO_2("Old device \"%s\" detected @ new location %s",
883 (const char*)data->GetFriendlyName(),
884 location);
885 data->SetURLBase(url);
886 }
887
888 // renew expiration time
889 data->SetLeaseTime(NPT_TimeInterval(leasetime, 0));
890 NPT_LOG_FINE_1("Device (%s) expiration time renewed..", (const char*)uuid);
891 return NPT_SUCCESS;
892 }
893
894 /*----------------------------------------------------------------------
895 | PLT_CtrlPoint::ProcessGetDescriptionResponse
896 +---------------------------------------------------------------------*/
897 NPT_Result
898 PLT_CtrlPoint::ProcessGetDescriptionResponse(NPT_Result res,
899 const NPT_HttpRequestContext& context,
900 NPT_HttpResponse* response,
901 PLT_DeviceDataReference& device)
902 {
903 NPT_LOG_INFO_2("Received device description for %s (result = %d)",
904 (const char*)device->GetUUID(),
905 res);
906
907 NPT_CHECK_FATAL(res);
908 NPT_CHECK_POINTER_FATAL(response);
909
910 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response);
911
912 // make sure we have seen this device before
913 PLT_DeviceDataReference root_device;
914 res = FindDevice(device->GetUUID(), root_device);
915 NPT_CHECK_WARNING(res);
916
917 // get body
918 NPT_String desc;
919 res = PLT_HttpHelper::GetBody(*response, desc);
920 NPT_CHECK_LABEL_SEVERE(res, bad_response);
921
922 // set the device description
923 res = root_device->SetDescription(desc, context.GetLocalAddress().GetIpAddress());
924 NPT_CHECK_LABEL_SEVERE(res, bad_response);
925
926 NPT_LOG_INFO_2("Device \"%s\" is now known as \"%s\"",
927 (const char*)device->GetUUID(),
928 (const char*)device->GetFriendlyName());
929
930 // add embedded devices to list of devices
931 // and fetch their services scpd
932 for (NPT_Cardinal i = 0;
933 i<root_device->m_EmbeddedDevices.GetItemCount();
934 i++) {
935
936 PLT_DeviceDataReference embedded_device = root_device->m_EmbeddedDevices[i];
937 PLT_DeviceDataReference data;
938 if (NPT_FAILED(FindDevice(embedded_device->GetUUID(), data))) {
939 NPT_AutoLock lock(m_Devices);
940 m_Devices.Add(embedded_device);
941 }
942
943 res = embedded_device->m_Services.Apply(PLT_AddGetSCPDRequestIterator(
944 &m_TaskManager,
945 this,
946 embedded_device));
947 NPT_CHECK_LABEL_SEVERE(res, bad_response);
948 }
949
950 // Get SCPD of root device services now
951 res = root_device->m_Services.Apply(PLT_AddGetSCPDRequestIterator(
952 &m_TaskManager,
953 this,
954 root_device));
955 NPT_CHECK_LABEL_SEVERE(res, bad_response);
956
957 return NPT_SUCCESS;
958
959 bad_response:
960 NPT_LOG_SEVERE_2("Bad Description response for device \"%s\": %s",
961 (const char*)device->GetUUID(),
962 (const char*)desc);
963
964 if (!root_device.IsNull()) {
965 NPT_AutoLock lock(m_Devices);
966 RemoveDevice(root_device);
967 }
968 return res;
969 }
970
971 /*----------------------------------------------------------------------
972 | PLT_CtrlPoint::ProcessGetSCPDResponse
973 +---------------------------------------------------------------------*/
974 NPT_Result
975 PLT_CtrlPoint::ProcessGetSCPDResponse(NPT_Result res,
976 NPT_HttpRequest* request,
977 NPT_HttpResponse* response,
978 PLT_DeviceDataReference& device)
979 {
980 PLT_DeviceReadyIterator device_tester;
981 PLT_Service* service = NULL;
982
983 NPT_LOG_INFO_2("Received SCPD response for %s (result = %d)",
984 (const char*)device->GetUUID(),
985 res);
986
987 NPT_CHECK_FATAL(res);
988 NPT_CHECK_POINTER_FATAL(request);
989 NPT_CHECK_POINTER_FATAL(response);
990
991 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response);
992
993 PLT_DeviceDataReference data;
994 NPT_CHECK_WARNING(FindDevice(device->GetUUID(), data));
995
996 // get body
997 NPT_String scpd;
998 res = PLT_HttpHelper::GetBody(*response, scpd);
999 NPT_CHECK_LABEL_FATAL(res, bad_response);
1000
1001 // look for the service based on the SCPD uri
1002 res = data->FindServiceByDescriptionURI(request->GetUrl().GetPath(), service);
1003 NPT_CHECK_LABEL_FATAL(res, bad_response);
1004
1005 {
1006 // lock using listener list before testing
1007 // to make sure an scpd is not getting set while we test
1008 NPT_AutoLock lock(m_ListenerList);
1009
1010 // set the service scpd
1011 res = service->SetSCPDXML(scpd);
1012 NPT_CHECK_LABEL_FATAL(res, bad_response);
1013
1014 if (NPT_SUCCEEDED(device_tester(data))) {
1015 // notify that the device is ready to use
1016 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(data));
1017 }
1018
1019 // if device is not root, notify listeners now if parent is ready
1020 if (!data->GetParentUUID().IsEmpty()) {
1021 PLT_DeviceDataReference parent;
1022 NPT_CHECK_WARNING(FindDevice(data->GetParentUUID(), parent));
1023
1024 // lock using listener list before testing
1025 // to make sure an scpd is not getting set while we test
1026 if (NPT_SUCCEEDED(device_tester(parent))) {
1027 // notify that the root device is ready to use
1028 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(parent));
1029 }
1030 }
1031 }
1032
1033 return NPT_SUCCESS;
1034
1035 bad_response:
1036 NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s",
1037 (const char*)device->GetFriendlyName(),
1038 (const char*)scpd);
1039
1040 if (!data.IsNull()) {
1041 NPT_AutoLock lock(m_Devices);
1042 RemoveDevice(data);
1043 }
1044 return res;
1045 }
1046
1047 /*----------------------------------------------------------------------
1048 | PLT_CtrlPoint::Subscribe
1049 +---------------------------------------------------------------------*/
1050 NPT_Result
1051 PLT_CtrlPoint::Subscribe(PLT_Service* service, bool cancel, void* userdata)
1052 {
1053 NPT_AutoLock lock(m_Subscribers);
1054
1055 if (!service->IsSubscribable()) return NPT_FAILURE;
1056
1057 // look for the host and port of the device
1058 PLT_DeviceData* device = service->GetDevice();
1059
1060 // get the relative event subscription url
1061 // if URL starts with '/', it's not to be appended to base URL
1062 NPT_String event_sub_url = service->GetEventSubURL();
1063 if (!event_sub_url.StartsWith("/")) {
1064 event_sub_url = device->GetURLBase().GetPath() + event_sub_url;
1065 }
1066
1067 NPT_HttpUrl url(device->GetURLBase().GetHost(),
1068 device->GetURLBase().GetPort(),
1069 event_sub_url);
1070
1071 // look for the subscriber with that service to decide if it's a renewal or not
1072 PLT_EventSubscriber* sub = NULL;
1073 NPT_ContainerFind(m_Subscribers,
1074 PLT_EventSubscriberFinderByService(service),
1075 sub);
1076
1077 // create the request
1078 NPT_HttpRequest* request = NULL;
1079
1080 if (cancel == false) {
1081 // renewal?
1082 if (sub) {
1083 NPT_LOG_FINE_3("Renewing subscriber \"%s\" for service \"%s\" of device \"%s\"",
1084 (const char*)sub->GetSID(),
1085 (const char*)service->GetServiceID(),
1086 (const char*)device->GetFriendlyName());
1087
1088 // create the request
1089 request = new NPT_HttpRequest(url, "SUBSCRIBE");
1090
1091 PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID());
1092 PLT_UPnPMessageHelper::SetTimeOut(*request, 1800);
1093 } else {
1094 NPT_LOG_INFO_2("Subscribing to service \"%s\" of device \"%s\"",
1095 (const char*)service->GetServiceID(),
1096 (const char*)service->GetDevice()->GetFriendlyName());
1097
1098 // prepare the callback url
1099 NPT_String uuid = device->GetUUID();
1100 NPT_String service_id = service->GetServiceID();
1101 NPT_String callback_uri = "/" + uuid + "/" + service_id;
1102
1103 // create the request
1104 request = new NPT_HttpRequest(url, "SUBSCRIBE");
1105 // specify callback url using ip of interface used when
1106 // retrieving device description
1107 NPT_HttpUrl callbackUrl(device->m_LocalIfaceIp.ToString(),
1108 m_EventHttpServer->GetPort(),
1109 callback_uri);
1110
1111 // set the required headers for a new subscription
1112 PLT_UPnPMessageHelper::SetNT(*request, "upnp:event");
1113 PLT_UPnPMessageHelper::SetCallbacks(*request,
1114 "<" + callbackUrl.ToString() + ">");
1115 PLT_UPnPMessageHelper::SetTimeOut(*request, 1800);
1116 }
1117 } else {
1118 NPT_LOG_INFO_3("Unsubscribing subscriber \"%s\" for service \"%s\" of device \"%s\"",
1119 (const char*)(sub?sub->GetSID().GetChars():"unknown"),
1120 (const char*)service->GetServiceID(),
1121 (const char*)service->GetDevice()->GetFriendlyName());
1122
1123 // cancellation
1124 if (!sub)return NPT_FAILURE;
1125
1126 // create the request
1127 request = new NPT_HttpRequest(url, "UNSUBSCRIBE");
1128 PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID());
1129
1130 // remove from list now
1131 m_Subscribers.Remove(sub, true);
1132 delete sub;
1133 }
1134
1135 NPT_CHECK_POINTER_FATAL(request);
1136
1137 // Prepare the request
1138 // create a task to post the request
1139 PLT_ThreadTask* task = new PLT_CtrlPointSubscribeEventTask(
1140 request,
1141 this,
1142 service,
1143 userdata);
1144 m_TaskManager.StartTask(task);
1145
1146 return NPT_SUCCESS;
1147 }
1148
1149 /*----------------------------------------------------------------------
1150 | PLT_CtrlPoint::ProcessSubscribeResponse
1151 +---------------------------------------------------------------------*/
1152 NPT_Result
1153 PLT_CtrlPoint::ProcessSubscribeResponse(NPT_Result res,
1154 NPT_HttpResponse* response,
1155 PLT_Service* service,
1156 void* /* userdata */)
1157 {
1158 const NPT_String* sid = NULL;
1159 NPT_Int32 timeout;
1160 PLT_EventSubscriber* sub = NULL;
1161
1162 NPT_AutoLock lock(m_Subscribers);
1163
1164 NPT_LOG_INFO_2("Received subscription response for service \"%s\" (result = %d)",
1165 (const char*)service->GetServiceID(),
1166 res);
1167 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response);
1168
1169 // if there's a failure or it's a response to a cancellation
1170 // we get out
1171 if (NPT_FAILED(res) || response == NULL || response->GetStatusCode() != 200) {
1172 NPT_CHECK_LABEL_SEVERE(NPT_FAILED(res)?res:NPT_FAILURE, failure);
1173 }
1174
1175 if (!(sid = PLT_UPnPMessageHelper::GetSID(*response)) ||
1176 NPT_FAILED(PLT_UPnPMessageHelper::GetTimeOut(*response, timeout))) {
1177 NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
1178 }
1179
1180 // look for the subscriber with that sid
1181 if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
1182 PLT_EventSubscriberFinderBySID(*sid),
1183 sub))) {
1184 NPT_LOG_INFO_3("Creating new subscriber \"%s\" for service \"%s\" of device \"%s\"",
1185 (const char*)*sid,
1186 (const char*)service->GetServiceID(),
1187 (const char*)service->GetDevice()->GetFriendlyName());
1188
1189 sub = new PLT_EventSubscriber(&m_TaskManager, service, *sid);
1190 m_Subscribers.Add(sub);
1191 }
1192
1193 sub->SetTimeout(timeout);
1194 return NPT_SUCCESS;
1195
1196 failure:
1197 NPT_LOG_SEVERE_3("(un)subscription failed of sub \"%s\" for service \"%s\" of device \"%s\"",
1198 (const char*)(sid?*sid:"?"),
1199 (const char*)service->GetServiceID(),
1200 (const char*)service->GetDevice()->GetFriendlyName());
1201
1202 // in case it was a renewal look for the subscriber with that service and remove it from the list
1203 if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers,
1204 PLT_EventSubscriberFinderByService(service),
1205 sub))) {
1206 m_Subscribers.Remove(sub);
1207 delete sub;
1208 }
1209
1210 return NPT_FAILURE;
1211 }
1212
1213 /*----------------------------------------------------------------------
1214 | PLT_CtrlPoint::InvokeAction
1215 +---------------------------------------------------------------------*/
1216 NPT_Result
1217 PLT_CtrlPoint::InvokeAction(PLT_ActionReference& action,
1218 void* userdata)
1219 {
1220 PLT_Service* service = action->GetActionDesc()->GetService();
1221 PLT_DeviceData* device = service->GetDevice();
1222
1223 // look for the service control url
1224 NPT_String control_url = service->GetControlURL();
1225
1226 // if URL starts with a "/", it's not to be appended to base URL
1227 if (!control_url.StartsWith("/")) {
1228 control_url = device->GetURLBase().GetPath() + control_url;
1229 }
1230
1231 // create the request
1232 // FIXME: hack use HTTP/1.0 for now because of WMC that returning 100 Continue when using HTTP/1.1
1233 // and this screws up the http processing right now
1234 NPT_HttpUrl url(device->GetURLBase().GetHost(), device->GetURLBase().GetPort(), control_url);
1235 NPT_HttpRequest* request = new NPT_HttpRequest(url, "POST", NPT_HTTP_PROTOCOL_1_0);
1236
1237 // create a memory stream for our request body
1238 NPT_MemoryStreamReference stream(new NPT_MemoryStream);
1239 action->FormatSoapRequest(*stream);
1240
1241 // set the request body
1242 NPT_InputStreamReference input = stream;
1243 PLT_HttpHelper::SetBody(*request, input);
1244
1245 PLT_HttpHelper::SetContentType(*request, "text/xml; charset=\"utf-8\"");
1246 NPT_String service_type = service->GetServiceType();
1247 NPT_String action_name = action->GetActionDesc()->GetName();
1248 request->GetHeaders().SetHeader("SOAPAction", "\"" + service_type + "#" + action_name + "\"");
1249
1250 // create a task to post the request
1251 PLT_CtrlPointInvokeActionTask* task = new PLT_CtrlPointInvokeActionTask(
1252 request,
1253 this,
1254 action,
1255 userdata);
1256
1257 // queue the request
1258 m_TaskManager.StartTask(task);
1259
1260 return NPT_SUCCESS;
1261 }
1262
1263 /*----------------------------------------------------------------------
1264 | PLT_CtrlPoint::ProcessActionResponse
1265 +---------------------------------------------------------------------*/
1266 NPT_Result
1267 PLT_CtrlPoint::ProcessActionResponse(NPT_Result res,
1268 NPT_HttpResponse* response,
1269 PLT_ActionReference& action,
1270 void* userdata)
1271 {
1272 NPT_String service_type;
1273 NPT_String str;
1274 NPT_XmlElementNode* xml = NULL;
1275 NPT_String name;
1276 NPT_String soap_action_name;
1277 NPT_XmlElementNode* soap_action_response;
1278 NPT_XmlElementNode* soap_body;
1279 NPT_XmlElementNode* fault;
1280 const NPT_String* attr = NULL;
1281 PLT_ActionDesc* action_desc = action->GetActionDesc();
1282
1283 // reset the error code and desc
1284 action->SetError(0, "");
1285
1286 // check context validity
1287 if (NPT_FAILED(res) || response == NULL) {
1288 goto failure;
1289 }
1290
1291 NPT_LOG_FINE("Received Action Response:");
1292 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response);
1293
1294 NPT_LOG_FINER("Reading/Parsing Action Response Body...");
1295 if (NPT_FAILED(PLT_HttpHelper::ParseBody(*response, xml))) {
1296 goto failure;
1297 }
1298
1299 NPT_LOG_FINER("Analyzing Action Response Body...");
1300
1301 // read envelope
1302 if (xml->GetTag().Compare("Envelope", true))
1303 goto failure;
1304
1305 // check namespace
1306 if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/"))
1307 goto failure;
1308
1309 // check encoding
1310 attr = xml->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/");
1311 if (!attr || attr->Compare("http://schemas.xmlsoap.org/soap/encoding/"))
1312 goto failure;
1313
1314 // read action
1315 soap_body = PLT_XmlHelper::GetChild(xml, "Body");
1316 if (soap_body == NULL)
1317 goto failure;
1318
1319 // check if an error occurred
1320 fault = PLT_XmlHelper::GetChild(soap_body, "Fault");
1321 if (fault != NULL) {
1322 // we have an error
1323 ParseFault(action, fault);
1324 goto failure;
1325 }
1326
1327 if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body, soap_action_response)))
1328 goto failure;
1329
1330 // verify action name is identical to SOAPACTION header
1331 if (soap_action_response->GetTag().Compare(action_desc->GetName() + "Response", true))
1332 goto failure;
1333
1334 // verify namespace
1335 if (!soap_action_response->GetNamespace() ||
1336 soap_action_response->GetNamespace()->Compare(action_desc->GetService()->GetServiceType()))
1337 goto failure;
1338
1339 // read all the arguments if any
1340 for (NPT_List<NPT_XmlNode*>::Iterator args = soap_action_response->GetChildren().GetFirstItem(); args; args++) {
1341 NPT_XmlElementNode* child = (*args)->AsElementNode();
1342 if (!child) continue;
1343
1344 action->SetArgumentValue(child->GetTag(), child->GetText()?*child->GetText():"");
1345 if (NPT_FAILED(res)) goto failure;
1346 }
1347
1348 // create a buffer for our response body and call the service
1349 res = action->VerifyArguments(false);
1350 if (NPT_FAILED(res)) goto failure;
1351
1352 goto cleanup;
1353
1354 failure:
1355 // override res with failure if necessary
1356 if (NPT_SUCCEEDED(res)) res = NPT_ERROR_INVALID_FORMAT;
1357 // fallthrough
1358
1359 cleanup:
1360 {
1361 NPT_AutoLock lock(m_ListenerList);
1362 m_ListenerList.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res, action, userdata));
1363 }
1364
1365 delete xml;
1366 return res;
1367 }
1368
1369 /*----------------------------------------------------------------------
1370 | PLT_CtrlPoint::ParseFault
1371 +---------------------------------------------------------------------*/
1372 NPT_Result
1373 PLT_CtrlPoint::ParseFault(PLT_ActionReference& action,
1374 NPT_XmlElementNode* fault)
1375 {
1376 NPT_XmlElementNode* detail = fault->GetChild("detail");
1377 if (detail == NULL) return NPT_FAILURE;
1378
1379 NPT_XmlElementNode *upnp_error, *error_code, *error_desc;
1380 upnp_error = detail->GetChild("upnp_error");
1381 if (upnp_error == NULL) return NPT_FAILURE;
1382
1383 error_code = upnp_error->GetChild("errorCode");
1384 error_desc = upnp_error->GetChild("errorDescription");
1385 NPT_Int32 code = 501;
1386 NPT_String desc;
1387 if (error_code && error_code->GetText()) {
1388 NPT_String value = *error_code->GetText();
1389 value.ToInteger(code);
1390 }
1391 if (error_desc && error_desc->GetText()) {
1392 desc = *error_desc->GetText();
1393 }
1394 action->SetError(code, desc);
1395 return NPT_SUCCESS;
1396 }