url: remove vlc_UrlParse() option parameter
[vlc.git] / modules / services_discovery / upnp.cpp
1 /*****************************************************************************
2  * upnp.cpp :  UPnP discovery module (libupnp)
3  *****************************************************************************
4  * Copyright (C) 2004-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
8  *          Christian Henz <henz # c-lab.de>
9  *          Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
10  *          Hugo Beauzée-Luyssen <hugo@beauzee.fr>
11  *
12  * UPnP Plugin using the Intel SDK (libupnp) instead of CyberLink
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  *****************************************************************************/
28
29 #include "upnp.hpp"
30
31 #include <vlc_access.h>
32 #include <vlc_plugin.h>
33 #include <vlc_services_discovery.h>
34 #include <vlc_url.h>
35
36 #include <assert.h>
37 #include <limits.h>
38 #include <algorithm>
39
40 /*
41  * Constants
42 */
43 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
44 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
45
46 /*
47  * VLC handle
48  */
49 struct services_discovery_sys_t
50 {
51     UpnpInstanceWrapper* p_upnp;
52     SD::MediaServerList* p_server_list;
53 };
54
55 struct access_sys_t
56 {
57     UpnpInstanceWrapper* p_upnp;
58     Access::MediaServer* p_server;
59 };
60
61 UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
62 vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
63
64 /*
65  * VLC callback prototypes
66  */
67 namespace SD
68 {
69     static int Open( vlc_object_t* );
70     static void Close( vlc_object_t* );
71 }
72
73 namespace Access
74 {
75     static int Open( vlc_object_t* );
76     static void Close( vlc_object_t* );
77 }
78
79 VLC_SD_PROBE_HELPER( "upnp", "Universal Plug'n'Play", SD_CAT_LAN )
80
81 /*
82  * Module descriptor
83  */
84 vlc_module_begin()
85     set_shortname( "UPnP" );
86     set_description( N_( "Universal Plug'n'Play" ) );
87     set_category( CAT_PLAYLIST );
88     set_subcategory( SUBCAT_PLAYLIST_SD );
89     set_capability( "services_discovery", 0 );
90     set_callbacks( SD::Open, SD::Close );
91
92     add_submodule()
93         set_category( CAT_INPUT )
94         set_subcategory( SUBCAT_INPUT_ACCESS )
95         set_callbacks( Access::Open, Access::Close )
96         set_capability( "access", 0 )
97
98     VLC_SD_PROBE_SUBMODULE
99 vlc_module_end()
100
101
102 /*
103  * Returns the value of a child element, or NULL on error
104  */
105 const char* xml_getChildElementValue( IXML_Element* p_parent,
106                                       const char*   psz_tag_name )
107 {
108     assert( p_parent );
109     assert( psz_tag_name );
110
111     IXML_NodeList* p_node_list;
112     p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
113     if ( !p_node_list ) return NULL;
114
115     IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
116     ixmlNodeList_free( p_node_list );
117     if ( !p_element )   return NULL;
118
119     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
120     if ( !p_text_node ) return NULL;
121
122     return ixmlNode_getNodeValue( p_text_node );
123 }
124
125 /*
126  * Extracts the result document from a SOAP response
127  */
128 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
129 {
130     assert( p_doc );
131
132     // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
133     // pointer, and pass it to a private function. Don't bother have a IXML_Document
134     // version of getChildElementValue
135     const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
136
137     if( !psz_raw_didl )
138         return NULL;
139
140     /* First, try parsing the buffer as is */
141     IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
142     if( !p_result_doc ) {
143         /* Missing namespaces confuse the ixml parser. This is a very ugly
144          * hack but it is needeed until devices start sending valid XML.
145          *
146          * It works that way:
147          *
148          * The DIDL document is extracted from the Result tag, then wrapped into
149          * a valid XML header and a new root tag which contains missing namespace
150          * definitions so the ixml parser understands it.
151          *
152          * If you know of a better workaround, please oh please fix it */
153         const char* psz_xml_result_fmt = "<?xml version=\"1.0\" ?>"
154             "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
155
156         char* psz_xml_result_string = NULL;
157         if( -1 == asprintf( &psz_xml_result_string,
158                              psz_xml_result_fmt,
159                              psz_raw_didl) )
160             return NULL;
161
162         p_result_doc = ixmlParseBuffer( psz_xml_result_string );
163         free( psz_xml_result_string );
164     }
165
166     if( !p_result_doc )
167         return NULL;
168
169     IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
170                                                                 "DIDL-Lite" );
171
172     IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
173     ixmlNodeList_free( p_elems );
174
175     return (IXML_Document*)p_node;
176 }
177
178 namespace SD
179 {
180
181 /*
182  * Initializes UPNP instance.
183  */
184 static int Open( vlc_object_t *p_this )
185 {
186     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
187     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
188             calloc( 1, sizeof( services_discovery_sys_t ) );
189
190     if( !( p_sd->p_sys = p_sys ) )
191         return VLC_ENOMEM;
192
193     p_sys->p_server_list = new(std::nothrow) SD::MediaServerList( p_sd );
194     if ( unlikely( p_sys->p_server_list == NULL ) )
195     {
196         return VLC_ENOMEM;
197     }
198
199     p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, SD::MediaServerList::Callback, p_sys->p_server_list );
200     if ( !p_sys->p_upnp )
201     {
202         Close( p_this );
203         return VLC_EGENERIC;
204     }
205
206     /* Search for media servers */
207     int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
208             MEDIA_SERVER_DEVICE_TYPE, p_sys->p_upnp );
209     if( i_res != UPNP_E_SUCCESS )
210     {
211         msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
212         Close( p_this );
213         return VLC_EGENERIC;
214     }
215
216     return VLC_SUCCESS;
217 }
218
219 /*
220  * Releases resources.
221  */
222 static void Close( vlc_object_t *p_this )
223 {
224     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
225     services_discovery_sys_t *p_sys = p_sd->p_sys;
226
227     if (p_sys->p_upnp)
228         p_sys->p_upnp->release( true );
229     delete p_sys->p_server_list;
230     free( p_sys );
231 }
232
233 MediaServerDesc::MediaServerDesc(const std::string& udn, const std::string& fName, const std::string& loc)
234     : UDN( udn )
235     , friendlyName( fName )
236     , location( loc )
237     , inputItem( NULL )
238 {
239 }
240
241 MediaServerDesc::~MediaServerDesc()
242 {
243     if (inputItem)
244         vlc_gc_decref( inputItem );
245 }
246
247 /*
248  * MediaServerList class
249  */
250 MediaServerList::MediaServerList( services_discovery_t* p_sd )
251     : p_sd_( p_sd )
252 {
253     vlc_mutex_init( &lock_ );
254 }
255
256 MediaServerList::~MediaServerList()
257 {
258     vlc_delete_all(list_);
259     vlc_mutex_destroy( &lock_ );
260 }
261
262 bool MediaServerList::addServer( MediaServerDesc* desc )
263 {
264     vlc_mutex_locker lock( &lock_ );
265     input_item_t* p_input_item = NULL;
266     if ( getServer( desc->UDN ) )
267         return false;
268
269     msg_Dbg( p_sd_, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
270
271     char* psz_mrl;
272     if( asprintf(&psz_mrl, "upnp://%s?ObjectID=0", desc->location.c_str() ) < 0 )
273         return false;
274
275     p_input_item = input_item_NewWithTypeExt( psz_mrl, desc->friendlyName.c_str(), 0,
276                                               NULL, 0, -1, ITEM_TYPE_NODE, 1);
277     free( psz_mrl );
278     if ( !p_input_item )
279         return false;
280     desc->inputItem = p_input_item;
281     input_item_SetDescription( p_input_item, desc->UDN.c_str() );
282     services_discovery_AddItem( p_sd_, p_input_item, NULL );
283     list_.push_back( desc );
284     return true;
285 }
286
287 MediaServerDesc* MediaServerList::getServer( const std::string& udn )
288 {
289     std::vector<MediaServerDesc*>::const_iterator it = list_.begin();
290     std::vector<MediaServerDesc*>::const_iterator ite = list_.end();
291
292     for ( ; it != ite; ++it )
293     {
294         if( udn == (*it)->UDN )
295         {
296             return *it;
297         }
298     }
299     return NULL;
300 }
301
302 void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
303 {
304     if ( !doc )
305     {
306         msg_Err( p_sd_, "Null IXML_Document" );
307         return;
308     }
309
310     if ( location.empty() )
311     {
312         msg_Err( p_sd_, "Empty location" );
313         return;
314     }
315
316     const char* psz_base_url = location.c_str();
317
318     /* Try to extract baseURL */
319     IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
320     if ( p_url_list )
321     {
322         if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
323         {
324             IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
325             if ( p_text_node )
326                 psz_base_url = ixmlNode_getNodeValue( p_text_node );
327         }
328         ixmlNodeList_free( p_url_list );
329     }
330
331     /* Get devices */
332     IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
333
334     if ( !p_device_list )
335         return;
336     for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
337     {
338         IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
339
340         if( !p_device_element )
341             continue;
342
343         const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
344
345         if ( !psz_device_type )
346         {
347             msg_Warn( p_sd_, "No deviceType found!" );
348             continue;
349         }
350
351         if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
352                 strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 ) )
353             continue;
354
355         const char* psz_udn = xml_getChildElementValue( p_device_element,
356                                                         "UDN" );
357         if ( !psz_udn )
358         {
359             msg_Warn( p_sd_, "No UDN!" );
360             continue;
361         }
362
363         /* Check if server is already added */
364         if ( p_sd_->p_sys->p_server_list->getServer( psz_udn ) )
365         {
366             msg_Warn( p_sd_, "Server with uuid '%s' already exists.", psz_udn );
367             continue;
368         }
369
370         const char* psz_friendly_name =
371                    xml_getChildElementValue( p_device_element,
372                                              "friendlyName" );
373
374         if ( !psz_friendly_name )
375         {
376             msg_Dbg( p_sd_, "No friendlyName!" );
377             continue;
378         }
379
380         // We now have basic info, we need to get the content browsing url
381         // so the access module can browse without fetching the manifest again
382
383         /* Check for ContentDirectory service. */
384         IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
385         if ( !p_service_list )
386             continue;
387         for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
388         {
389             IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
390
391             const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
392             if ( !psz_service_type )
393             {
394                 msg_Warn( p_sd_, "No service type found." );
395                 continue;
396             }
397
398             int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
399             if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
400                         psz_service_type, k ) )
401                 continue;
402
403             const char* psz_control_url = xml_getChildElementValue( p_service_element,
404                                           "controlURL" );
405             if ( !psz_control_url )
406             {
407                 msg_Warn( p_sd_, "No control url found." );
408                 continue;
409             }
410
411             /* Try to browse content directory. */
412             char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
413             if ( psz_url )
414             {
415                 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
416                 {
417                     SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
418                             psz_friendly_name, psz_url );
419                     free( psz_url );
420                     if ( unlikely( !p_server ) )
421                         break;
422
423                     if ( !addServer( p_server ) )
424                     {
425                         delete p_server;
426                         continue;
427                     }
428                 }
429                 else
430                     free( psz_url );
431             }
432         }
433         ixmlNodeList_free( p_service_list );
434     }
435     ixmlNodeList_free( p_device_list );
436 }
437
438 void MediaServerList::removeServer( const std::string& udn )
439 {
440     vlc_mutex_locker lock( &lock_ );
441
442     MediaServerDesc* p_server = getServer( udn );
443     if ( !p_server )
444         return;
445
446     msg_Dbg( p_sd_, "Removing server '%s'", p_server->friendlyName.c_str() );
447
448     assert(p_server->inputItem);
449     services_discovery_RemoveItem( p_sd_, p_server->inputItem );
450
451     std::vector<MediaServerDesc*>::iterator it = std::find(list_.begin(), list_.end(), p_server);
452     if (it != list_.end())
453     {
454         list_.erase( it );
455     }
456     delete p_server;
457 }
458
459 /*
460  * Handles servers listing UPnP events
461  */
462 int MediaServerList::Callback( Upnp_EventType event_type, void* p_event, void* p_user_data )
463 {
464     MediaServerList* self = static_cast<MediaServerList*>( p_user_data );
465     services_discovery_t* p_sd = self->p_sd_;
466
467     switch( event_type )
468     {
469     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
470     case UPNP_DISCOVERY_SEARCH_RESULT:
471     {
472         struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
473
474         IXML_Document *p_description_doc = NULL;
475
476         int i_res;
477         i_res = UpnpDownloadXmlDoc( p_discovery->Location, &p_description_doc );
478         if ( i_res != UPNP_E_SUCCESS )
479         {
480             msg_Warn( p_sd, "Could not download device description! "
481                             "Fetching data from %s failed: %s",
482                             p_discovery->Location, UpnpGetErrorMessage( i_res ) );
483             return i_res;
484         }
485         self->parseNewServer( p_description_doc, p_discovery->Location );
486         ixmlDocument_free( p_description_doc );
487     }
488     break;
489
490     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
491     {
492         struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )p_event;
493
494         self->removeServer( p_discovery->DeviceId );
495     }
496     break;
497
498     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
499         msg_Warn( p_sd, "subscription complete" );
500         break;
501
502     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
503         msg_Warn( p_sd, "search timeout" );
504         break;
505
506     case UPNP_EVENT_RECEIVED:
507     case UPNP_EVENT_AUTORENEWAL_FAILED:
508     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
509         // Those are for the access part
510         break;
511
512     default:
513         msg_Err( p_sd, "Unhandled event, please report ( type=%d )", event_type );
514         break;
515     }
516
517     return UPNP_E_SUCCESS;
518 }
519
520 }
521
522 namespace Access
523 {
524
525 MediaServer::MediaServer(const char *psz_url, access_t *p_access)
526     : url_( psz_url )
527     , access_( p_access )
528     , xmlDocument_( NULL )
529     , containerNodeList_( NULL )
530     , containerNodeIndex_( 0 )
531     , itemNodeList_( NULL )
532     , itemNodeIndex_( 0 )
533 {
534 }
535
536 MediaServer::~MediaServer()
537 {
538     ixmlNodeList_free( containerNodeList_ );
539     ixmlNodeList_free( itemNodeList_ );
540     ixmlDocument_free( xmlDocument_ );
541 }
542
543 input_item_t* MediaServer::newItem(const char *objectID, const char *title )
544 {
545     vlc_url_t url;
546     vlc_UrlParse( &url, url_.c_str() );
547     char* psz_url;
548
549     if (asprintf( &psz_url, "upnp://%s://%s:%u%s?ObjectID=%s", url.psz_protocol,
550                   url.psz_host, url.i_port ? url.i_port : 80, url.psz_path, objectID ) < 0 )
551     {
552         vlc_UrlClean( &url );
553         return NULL;
554     }
555     vlc_UrlClean( &url );
556
557     input_item_t* p_item = input_item_NewWithTypeExt( psz_url, title, 0, NULL,
558                                                       0, -1, ITEM_TYPE_DIRECTORY, 1 );
559     free( psz_url);
560     return p_item;
561 }
562
563 input_item_t* MediaServer::newItem(const char* title, const char*, const char*,
564                                    mtime_t duration, const char* psz_url)
565 {
566     return input_item_NewWithTypeExt( psz_url, title, 0, NULL, 0,
567                                       duration, ITEM_TYPE_FILE, 1 );
568 }
569
570 /* Access part */
571 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
572                                            const char* psz_browser_flag_,
573                                            const char* psz_filter_,
574                                            const char* psz_requested_count_,
575                                            const char* psz_sort_criteria_ )
576 {
577     IXML_Document* p_action = NULL;
578     IXML_Document* p_response = NULL;
579     const char* psz_url = url_.c_str();
580
581     if ( url_.empty() )
582     {
583         msg_Dbg( access_, "No subscription url set!" );
584         return NULL;
585     }
586
587     int i_res;
588
589     i_res = UpnpAddToAction( &p_action, "Browse",
590             CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ );
591
592     if ( i_res != UPNP_E_SUCCESS )
593     {
594         msg_Dbg( access_, "AddToAction 'ObjectID' failed: %s",
595                 UpnpGetErrorMessage( i_res ) );
596         goto browseActionCleanup;
597     }
598
599     i_res = UpnpAddToAction( &p_action, "Browse",
600             CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
601     if ( i_res != UPNP_E_SUCCESS )
602     {
603         msg_Dbg( access_, "AddToAction 'StartingIndex' failed: %s",
604                 UpnpGetErrorMessage( i_res ) );
605         goto browseActionCleanup;
606     }
607
608     i_res = UpnpAddToAction( &p_action, "Browse",
609             CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
610
611     if ( i_res != UPNP_E_SUCCESS )
612     {
613         msg_Dbg( access_, "AddToAction 'BrowseFlag' failed: %s",
614                 UpnpGetErrorMessage( i_res ) );
615         goto browseActionCleanup;
616     }
617
618     i_res = UpnpAddToAction( &p_action, "Browse",
619             CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
620
621     if ( i_res != UPNP_E_SUCCESS )
622     {
623         msg_Dbg( access_, "AddToAction 'Filter' failed: %s",
624                 UpnpGetErrorMessage( i_res ) );
625         goto browseActionCleanup;
626     }
627
628     i_res = UpnpAddToAction( &p_action, "Browse",
629             CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
630
631     if ( i_res != UPNP_E_SUCCESS )
632     {
633         msg_Dbg( access_, "AddToAction 'RequestedCount' failed: %s",
634                 UpnpGetErrorMessage( i_res ) );
635         goto browseActionCleanup;
636     }
637
638     i_res = UpnpAddToAction( &p_action, "Browse",
639             CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
640
641     if ( i_res != UPNP_E_SUCCESS )
642     {
643         msg_Dbg( access_, "AddToAction 'SortCriteria' failed: %s",
644                 UpnpGetErrorMessage( i_res ) );
645         goto browseActionCleanup;
646     }
647
648     i_res = UpnpSendAction( access_->p_sys->p_upnp->handle(),
649               psz_url,
650               CONTENT_DIRECTORY_SERVICE_TYPE,
651               NULL, /* ignored in SDK, must be NULL */
652               p_action,
653               &p_response );
654
655     if ( i_res != UPNP_E_SUCCESS )
656     {
657         msg_Err( access_, "%s when trying the send() action with URL: %s",
658                 UpnpGetErrorMessage( i_res ), psz_url );
659
660         ixmlDocument_free( p_response );
661         p_response = NULL;
662     }
663
664 browseActionCleanup:
665     ixmlDocument_free( p_action );
666     return p_response;
667 }
668
669 /*
670  * Fetches and parses the UPNP response
671  */
672 void MediaServer::fetchContents()
673 {
674     const char* objectID = "";
675     vlc_url_t url;
676     vlc_UrlParse( &url, access_->psz_location );
677
678     if ( url.psz_option && !strncmp( url.psz_option, "ObjectID=", strlen( "ObjectID=" ) ) )
679     {
680         objectID = &url.psz_option[strlen( "ObjectID=" )];
681     }
682
683     IXML_Document* p_response = _browseAction( objectID,
684                                       "BrowseDirectChildren",
685                                       "id,dc:title,res," /* Filter */
686                                       "sec:CaptionInfo,sec:CaptionInfoEx,"
687                                       "pv:subtitlefile",
688                                       "0", /* RequestedCount */
689                                       "" /* SortCriteria */
690                                       );
691     vlc_UrlClean( &url );
692     if ( !p_response )
693     {
694         msg_Err( access_, "No response from browse() action" );
695         return;
696     }
697
698     xmlDocument_ = parseBrowseResult( p_response );
699
700     ixmlDocument_free( p_response );
701
702     if ( !xmlDocument_ )
703     {
704         msg_Err( access_, "browse() response parsing failed" );
705         return;
706     }
707
708 #ifndef NDEBUG
709     msg_Dbg( access_, "Got DIDL document: %s", ixmlPrintDocument( xmlDocument_ ) );
710 #endif
711
712     containerNodeList_ = ixmlDocument_getElementsByTagName( xmlDocument_, "container" );
713     itemNodeList_ = ixmlDocument_getElementsByTagName( xmlDocument_, "item" );
714 }
715
716 input_item_t* MediaServer::getNextItem()
717 {
718     input_item_t *p_item = NULL;
719
720     if( !xmlDocument_ )
721     {
722         fetchContents();
723         if( !xmlDocument_ )
724             return NULL;
725     }
726
727     if ( containerNodeList_ )
728     {
729         for ( ; !p_item && containerNodeIndex_ < ixmlNodeList_length( containerNodeList_ )
730               ; containerNodeIndex_++ )
731         {
732             IXML_Element* containerElement = (IXML_Element*)ixmlNodeList_item( containerNodeList_,
733                                                                                 containerNodeIndex_ );
734
735             const char* objectID = ixmlElement_getAttribute( containerElement,
736                                                              "id" );
737             if ( !objectID )
738                 continue;
739
740             const char* title = xml_getChildElementValue( containerElement,
741                                                           "dc:title" );
742             if ( !title )
743                 continue;
744             p_item = newItem(objectID, title);
745         }
746     }
747
748     if( itemNodeList_ )
749     {
750         for ( ; !p_item && itemNodeIndex_ < ixmlNodeList_length( itemNodeList_ )
751               ; itemNodeIndex_++ )
752         {
753             IXML_Element* itemElement =
754                         ( IXML_Element* )ixmlNodeList_item( itemNodeList_,
755                                                             itemNodeIndex_ );
756
757             const char* objectID =
758                         ixmlElement_getAttribute( itemElement, "id" );
759
760             if ( !objectID )
761                 continue;
762
763             const char* title =
764                         xml_getChildElementValue( itemElement, "dc:title" );
765
766             if ( !title )
767                 continue;
768
769             const char* psz_subtitles = xml_getChildElementValue( itemElement,
770                     "sec:CaptionInfo" );
771
772             if ( !psz_subtitles )
773                 psz_subtitles = xml_getChildElementValue( itemElement,
774                         "sec:CaptionInfoEx" );
775
776             if ( !psz_subtitles )
777                 psz_subtitles = xml_getChildElementValue( itemElement,
778                         "pv:subtitlefile" );
779
780             /* Try to extract all resources in DIDL */
781             IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" );
782             if ( p_resource_list && ixmlNodeList_length( p_resource_list ) > 0 )
783             {
784                 mtime_t i_duration = -1;
785                 int i_hours, i_minutes, i_seconds;
786                 IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, 0 );
787                 const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" );
788                 if( !psz_resource_url )
789                     continue;
790                 const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" );
791
792                 if ( psz_duration )
793                 {
794                     if( sscanf( psz_duration, "%d:%02d:%02d",
795                         &i_hours, &i_minutes, &i_seconds ) )
796                         i_duration = INT64_C(1000000) * ( i_hours*3600 +
797                                                           i_minutes*60 +
798                                                           i_seconds );
799                 }
800
801                 p_item = newItem( title, objectID, psz_subtitles, i_duration,
802                                   psz_resource_url );
803             }
804             ixmlNodeList_free( p_resource_list );
805         }
806     }
807
808     return p_item;
809 }
810
811 static input_item_t* ReadDirectory( access_t *p_access )
812 {
813     return p_access->p_sys->p_server->getNextItem();
814 }
815
816 static int Open( vlc_object_t *p_this )
817 {
818     access_t* p_access = (access_t*)p_this;
819     access_sys_t* p_sys = new(std::nothrow) access_sys_t;
820     if ( unlikely( !p_sys ) )
821         return VLC_ENOMEM;
822
823     p_access->p_sys = p_sys;
824     p_sys->p_server = new(std::nothrow) MediaServer( p_access->psz_location,
825                                                      p_access );
826     if ( !p_sys->p_server )
827     {
828         delete p_sys;
829         return VLC_EGENERIC;
830     }
831     p_sys->p_upnp = UpnpInstanceWrapper::get( p_this, NULL, NULL );
832     if ( !p_sys->p_upnp )
833     {
834         delete p_sys->p_server;
835         delete p_sys;
836         return VLC_EGENERIC;
837     }
838
839     p_access->pf_readdir = ReadDirectory;
840     p_access->pf_control = access_vaDirectoryControlHelper;
841     p_access->info.b_dir_sorted = true;
842     p_access->info.b_dir_can_loop = true;
843
844     return VLC_SUCCESS;
845 }
846
847 static void Close( vlc_object_t* p_this )
848 {
849     access_t* p_access = (access_t*)p_this;
850     p_access->p_sys->p_upnp->release( false );
851     delete p_access->p_sys->p_server;
852     delete p_access->p_sys;
853 }
854
855 }
856
857 UpnpInstanceWrapper::UpnpInstanceWrapper()
858     : handle_( -1 )
859     , opaque_( NULL )
860     , callback_( NULL )
861     , refcount_( 0 )
862 {
863 }
864
865 UpnpInstanceWrapper::~UpnpInstanceWrapper()
866 {
867     UpnpUnRegisterClient( handle_ );
868     UpnpFinish();
869 }
870
871 UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj, Upnp_FunPtr callback, SD::MediaServerList *opaque)
872 {
873     vlc_mutex_locker lock( &s_lock );
874     if ( s_instance == NULL )
875     {
876         UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
877         if ( unlikely( !instance ) )
878             return NULL;
879
880     #ifdef UPNP_ENABLE_IPV6
881         char* psz_miface = var_InheritString( p_obj, "miface" );
882         msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface );
883         int i_res = UpnpInit2( psz_miface, 0 );
884         free( psz_miface );
885     #else
886         /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
887         int i_res = UpnpInit( 0, 0 );
888     #endif
889         if( i_res != UPNP_E_SUCCESS )
890         {
891             msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
892             delete instance;
893             return NULL;
894         }
895
896         ixmlRelaxParser( 1 );
897
898         /* Register a control point */
899         i_res = UpnpRegisterClient( Callback, instance, &instance->handle_ );
900         if( i_res != UPNP_E_SUCCESS )
901         {
902             msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
903             delete instance;
904             return NULL;
905         }
906
907         /* libupnp does not treat a maximum content length of 0 as unlimited
908          * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
909          * between versions */
910         if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
911         {
912             msg_Err( p_obj, "Failed to set maximum content length: %s",
913                     UpnpGetErrorMessage( i_res ));
914             delete instance;
915             return NULL;
916         }
917         s_instance = instance;
918     }
919     s_instance->refcount_++;
920     // This assumes a single UPNP SD instance
921     if (callback && opaque)
922     {
923         assert(!s_instance->callback_ && !s_instance->opaque_);
924         s_instance->opaque_ = opaque;
925         s_instance->callback_ = callback;
926     }
927     return s_instance;
928 }
929
930 void UpnpInstanceWrapper::release(bool isSd)
931 {
932     vlc_mutex_locker lock( &s_lock );
933     if ( isSd )
934     {
935         callback_ = NULL;
936         opaque_ = NULL;
937     }
938     if (--s_instance->refcount_ == 0)
939     {
940         delete s_instance;
941         s_instance = NULL;
942     }
943 }
944
945 UpnpClient_Handle UpnpInstanceWrapper::handle() const
946 {
947     return handle_;
948 }
949
950 int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, void *p_event, void *p_user_data)
951 {
952     UpnpInstanceWrapper* self = static_cast<UpnpInstanceWrapper*>( p_user_data );
953     vlc_mutex_locker lock( &self->s_lock );
954     if ( !self->callback_ )
955         return 0;
956     self->callback_( event_type, p_event, self->opaque_ );
957     return 0;
958 }