7f8ed230ae02c18c1104f67847fc3f8fa71b2304
[vlc.git] / modules / services_discovery / upnp.cpp
1 /*****************************************************************************
2  * upnp.cpp :  UPnP discovery module (libupnp)
3  *****************************************************************************
4  * Copyright (C) 2004-2018 VLC authors and VideoLAN
5  *
6  * Authors: Rémi Denis-Courmont <rem # videolan.org> (original plugin)
7  *          Christian Henz <henz # c-lab.de>
8  *          Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
9  *          Hugo Beauzée-Luyssen <hugo@beauzee.fr>
10  *          Shaleen Jain <shaleen@jain.sh>
11  *
12  * This program is free software; you can redistribute it and/or modify it
13  * under the terms of the GNU Lesser General Public License as published by
14  * the Free Software Foundation; either version 2.1 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  * GNU Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public License
23  * along with this program; if not, write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 #include "upnp.hpp"
28
29 #include <vlc_access.h>
30 #include <vlc_plugin.h>
31 #include <vlc_interrupt.h>
32 #include <vlc_services_discovery.h>
33 #include <vlc_charset.h>
34
35 #include <assert.h>
36 #include <limits.h>
37 #include <algorithm>
38 #include <set>
39 #include <string>
40
41 #if UPNP_VERSION < 10623
42 /*
43  * Compat functions and typedefs for libupnp prior to 1.8
44  */
45
46 typedef Upnp_Discovery UpnpDiscovery;
47 typedef Upnp_Action_Complete UpnpActionComplete;
48
49 static const char* UpnpDiscovery_get_Location_cstr( const UpnpDiscovery* p_discovery )
50 {
51   return p_discovery->Location;
52 }
53
54 static const char* UpnpDiscovery_get_DeviceID_cstr( const UpnpDiscovery* p_discovery )
55 {
56   return p_discovery->DeviceId;
57 }
58
59 static IXML_Document* UpnpActionComplete_get_ActionResult( const UpnpActionComplete* p_result )
60 {
61   return p_result->ActionResult;
62 }
63 #endif
64
65 /*
66  * Constants
67 */
68 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
69 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
70 const char* SATIP_SERVER_DEVICE_TYPE = "urn:ses-com:device:SatIPServer:1";
71
72 #define SATIP_CHANNEL_LIST N_("SAT>IP channel list")
73 #define SATIP_CHANNEL_LIST_URL N_("Custom SAT>IP channel list URL")
74 static const char *const ppsz_satip_channel_lists[] = {
75     "ASTRA_19_2E", "ASTRA_28_2E", "ASTRA_23_5E", "MasterList", "ServerList", "CustomList"
76 };
77 static const char *const ppsz_readible_satip_channel_lists[] = {
78     "Astra 19.2°E", "Astra 28.2°E", "Astra 23.5°E", N_("Master List"), N_("Server List"), N_("Custom List")
79 };
80
81 /*
82  * VLC handle
83  */
84 struct services_discovery_sys_t
85 {
86     UpnpInstanceWrapper* p_upnp;
87     std::shared_ptr<SD::MediaServerList> p_server_list;
88     vlc_thread_t         thread;
89 };
90
91 struct access_sys_t
92 {
93     UpnpInstanceWrapper* p_upnp;
94 };
95
96 UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
97 UpnpInstanceWrapper::Listeners UpnpInstanceWrapper::s_listeners;
98 vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
99
100 /*
101  * VLC callback prototypes
102  */
103 namespace SD
104 {
105     static int Open( vlc_object_t* );
106     static void Close( vlc_object_t* );
107 }
108
109 namespace Access
110 {
111     static int Open( vlc_object_t* );
112     static void Close( vlc_object_t* );
113 }
114
115 VLC_SD_PROBE_HELPER( "upnp", N_("Universal Plug'n'Play"), SD_CAT_LAN )
116
117 /*
118  * Module descriptor
119  */
120 vlc_module_begin()
121     set_shortname( "UPnP" );
122     set_description( N_( "Universal Plug'n'Play" ) );
123     set_category( CAT_PLAYLIST );
124     set_subcategory( SUBCAT_PLAYLIST_SD );
125     set_capability( "services_discovery", 0 );
126     set_callbacks( SD::Open, SD::Close );
127
128     add_string( "satip-channelist", "ASTRA_19_2E", SATIP_CHANNEL_LIST,
129                 SATIP_CHANNEL_LIST, false )
130     change_string_list( ppsz_satip_channel_lists, ppsz_readible_satip_channel_lists )
131     add_string( "satip-channellist-url", NULL, SATIP_CHANNEL_LIST_URL,
132                 SATIP_CHANNEL_LIST_URL, false )
133
134     add_submodule()
135         set_category( CAT_INPUT )
136         set_subcategory( SUBCAT_INPUT_ACCESS )
137         set_callbacks( Access::Open, Access::Close )
138         set_capability( "access", 0 )
139
140     VLC_SD_PROBE_SUBMODULE
141 vlc_module_end()
142
143
144 /*
145  * Returns the value of a child element, or NULL on error
146  */
147 const char* xml_getChildElementValue( IXML_Element* p_parent,
148                                       const char*   psz_tag_name )
149 {
150     assert( p_parent );
151     assert( psz_tag_name );
152
153     IXML_NodeList* p_node_list;
154     p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
155     if ( !p_node_list ) return NULL;
156
157     IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
158     ixmlNodeList_free( p_node_list );
159     if ( !p_element )   return NULL;
160
161     IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
162     if ( !p_text_node ) return NULL;
163
164     return ixmlNode_getNodeValue( p_text_node );
165 }
166
167 /*
168  * Extracts the result document from a SOAP response
169  */
170 IXML_Document* parseBrowseResult( IXML_Document* p_doc )
171 {
172     assert( p_doc );
173
174     // ixml*_getElementsByTagName will ultimately only case the pointer to a Node
175     // pointer, and pass it to a private function. Don't bother have a IXML_Document
176     // version of getChildElementValue
177     const char* psz_raw_didl = xml_getChildElementValue( (IXML_Element*)p_doc, "Result" );
178
179     if( !psz_raw_didl )
180         return NULL;
181
182     /* First, try parsing the buffer as is */
183     IXML_Document* p_result_doc = ixmlParseBuffer( psz_raw_didl );
184     if( !p_result_doc ) {
185         /* Missing namespaces confuse the ixml parser. This is a very ugly
186          * hack but it is needeed until devices start sending valid XML.
187          *
188          * It works that way:
189          *
190          * The DIDL document is extracted from the Result tag, then wrapped into
191          * a valid XML header and a new root tag which contains missing namespace
192          * definitions so the ixml parser understands it.
193          *
194          * If you know of a better workaround, please oh please fix it */
195         const char* psz_xml_result_fmt = "<?xml version=\"1.0\" ?>"
196             "<Result xmlns:sec=\"urn:samsung:metadata:2009\">%s</Result>";
197
198         char* psz_xml_result_string = NULL;
199         if( -1 == asprintf( &psz_xml_result_string,
200                              psz_xml_result_fmt,
201                              psz_raw_didl) )
202             return NULL;
203
204         p_result_doc = ixmlParseBuffer( psz_xml_result_string );
205         free( psz_xml_result_string );
206     }
207
208     if( !p_result_doc )
209         return NULL;
210
211     IXML_NodeList *p_elems = ixmlDocument_getElementsByTagName( p_result_doc,
212                                                                 "DIDL-Lite" );
213
214     IXML_Node *p_node = ixmlNodeList_item( p_elems, 0 );
215     ixmlNodeList_free( p_elems );
216
217     return (IXML_Document*)p_node;
218 }
219
220 namespace SD
221 {
222
223 static void *
224 SearchThread( void *p_data )
225 {
226     services_discovery_t *p_sd = ( services_discovery_t* )p_data;
227     services_discovery_sys_t *p_sys = reinterpret_cast<services_discovery_sys_t *>( p_sd->p_sys );
228
229     /* Search for media servers */
230     int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
231             MEDIA_SERVER_DEVICE_TYPE, MEDIA_SERVER_DEVICE_TYPE );
232     if( i_res != UPNP_E_SUCCESS )
233     {
234         msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
235         return NULL;
236     }
237
238     /* Search for Sat Ip servers*/
239     i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
240             SATIP_SERVER_DEVICE_TYPE, MEDIA_SERVER_DEVICE_TYPE );
241     if( i_res != UPNP_E_SUCCESS )
242         msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
243     return NULL;
244 }
245
246 /*
247  * Initializes UPNP instance.
248  */
249 static int Open( vlc_object_t *p_this )
250 {
251     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
252     services_discovery_sys_t *p_sys  = ( services_discovery_sys_t * )
253             calloc( 1, sizeof( services_discovery_sys_t ) );
254
255     if( !( p_sd->p_sys = p_sys ) )
256         return VLC_ENOMEM;
257
258     p_sd->description = _("Universal Plug'n'Play");
259
260     p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
261     if ( !p_sys->p_upnp )
262     {
263         free(p_sys);
264         return VLC_EGENERIC;
265     }
266
267     try
268     {
269         p_sys->p_server_list = std::make_shared<SD::MediaServerList>( p_sd );
270     }
271     catch ( const std::bad_alloc& )
272     {
273         msg_Err( p_sd, "Failed to create a MediaServerList");
274         p_sys->p_upnp->release();
275         free(p_sys);
276         return VLC_EGENERIC;
277     }
278     p_sys->p_upnp->addListener( p_sys->p_server_list );
279
280     /* XXX: Contrary to what the libupnp doc states, UpnpSearchAsync is
281      * blocking (select() and send() are called). Therefore, Call
282      * UpnpSearchAsync from an other thread. */
283     if ( vlc_clone( &p_sys->thread, SearchThread, p_this,
284                     VLC_THREAD_PRIORITY_LOW ) )
285     {
286         p_sys->p_upnp->removeListener( p_sys->p_server_list );
287         p_sys->p_upnp->release();
288         free(p_sys);
289         return VLC_EGENERIC;
290     }
291
292     return VLC_SUCCESS;
293 }
294
295 /*
296  * Releases resources.
297  */
298 static void Close( vlc_object_t *p_this )
299 {
300     services_discovery_t *p_sd = ( services_discovery_t* )p_this;
301     services_discovery_sys_t *p_sys = reinterpret_cast<services_discovery_sys_t *>( p_sd->p_sys );
302
303     vlc_join( p_sys->thread, NULL );
304     p_sys->p_upnp->removeListener( p_sys->p_server_list );
305     p_sys->p_upnp->release();
306     free( p_sys );
307 }
308
309 MediaServerDesc::MediaServerDesc( const std::string& udn, const std::string& fName,
310                                   const std::string& loc, const std::string& iconUrl )
311     : UDN( udn )
312     , friendlyName( fName )
313     , location( loc )
314     , iconUrl( iconUrl )
315     , inputItem( NULL )
316     , isSatIp( false )
317 {
318 }
319
320 MediaServerDesc::~MediaServerDesc()
321 {
322     if (inputItem)
323         input_item_Release( inputItem );
324 }
325
326 /*
327  * MediaServerList class
328  */
329 MediaServerList::MediaServerList( services_discovery_t* p_sd )
330     : m_sd( p_sd )
331 {
332 }
333
334 MediaServerList::~MediaServerList()
335 {
336     vlc_delete_all(m_list);
337 }
338
339 bool MediaServerList::addServer( MediaServerDesc* desc )
340 {
341     input_item_t* p_input_item = NULL;
342     if ( getServer( desc->UDN ) )
343         return false;
344
345     msg_Dbg( m_sd, "Adding server '%s' with uuid '%s'", desc->friendlyName.c_str(), desc->UDN.c_str() );
346
347     if ( desc->isSatIp )
348     {
349         p_input_item = input_item_NewDirectory( desc->location.c_str(),
350                                                 desc->friendlyName.c_str(),
351                                                 ITEM_NET );
352         if ( !p_input_item )
353             return false;
354
355         input_item_SetSetting( p_input_item, SATIP_SERVER_DEVICE_TYPE );
356
357         char *psz_playlist_option;
358
359         if (asprintf( &psz_playlist_option, "satip-host=%s",
360                      desc->satIpHost.c_str() ) >= 0 ) {
361             input_item_AddOption( p_input_item, psz_playlist_option, 0 );
362             free( psz_playlist_option );
363         }
364     } else {
365         char* psz_mrl;
366         // We might already have some options specified in the location.
367         char opt_delim = desc->location.find( '?' ) == 0 ? '?' : '&';
368         if( asprintf( &psz_mrl, "upnp://%s%cObjectID=0", desc->location.c_str(), opt_delim ) < 0 )
369             return false;
370
371         p_input_item = input_item_NewDirectory( psz_mrl,
372                                                 desc->friendlyName.c_str(),
373                                                 ITEM_NET );
374         free( psz_mrl );
375
376         if ( !p_input_item )
377             return false;
378
379         input_item_SetSetting( p_input_item, MEDIA_SERVER_DEVICE_TYPE );
380     }
381
382     if ( desc->iconUrl.empty() == false )
383         input_item_SetArtworkURL( p_input_item, desc->iconUrl.c_str() );
384     desc->inputItem = p_input_item;
385     input_item_SetDescription( p_input_item, desc->UDN.c_str() );
386     services_discovery_AddItem( m_sd, p_input_item );
387     m_list.push_back( desc );
388
389     return true;
390 }
391
392 MediaServerDesc* MediaServerList::getServer( const std::string& udn )
393 {
394     std::vector<MediaServerDesc*>::const_iterator it = m_list.begin();
395     std::vector<MediaServerDesc*>::const_iterator ite = m_list.end();
396
397     for ( ; it != ite; ++it )
398     {
399         if( udn == (*it)->UDN )
400         {
401             return *it;
402         }
403     }
404     return NULL;
405 }
406
407 void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
408 {
409     if ( !doc )
410     {
411         msg_Err( m_sd, "Null IXML_Document" );
412         return;
413     }
414
415     if ( location.empty() )
416     {
417         msg_Err( m_sd, "Empty location" );
418         return;
419     }
420
421     const char* psz_base_url = location.c_str();
422
423     /* Try to extract baseURL */
424     IXML_NodeList* p_url_list = ixmlDocument_getElementsByTagName( doc, "URLBase" );
425     if ( p_url_list )
426     {
427         if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
428         {
429             IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
430             if ( p_text_node )
431                 psz_base_url = ixmlNode_getNodeValue( p_text_node );
432         }
433         ixmlNodeList_free( p_url_list );
434     }
435
436     /* Get devices */
437     IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( doc, "device" );
438
439     if ( !p_device_list )
440         return;
441
442     for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_list ); i++ )
443     {
444         IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
445
446         if( !p_device_element )
447             continue;
448
449         const char* psz_device_type = xml_getChildElementValue( p_device_element, "deviceType" );
450
451         if ( !psz_device_type )
452         {
453             msg_Warn( m_sd, "No deviceType found!" );
454             continue;
455         }
456
457         if ( strncmp( MEDIA_SERVER_DEVICE_TYPE, psz_device_type,
458                 strlen( MEDIA_SERVER_DEVICE_TYPE ) - 1 )
459                 && strncmp( SATIP_SERVER_DEVICE_TYPE, psz_device_type,
460                         strlen( SATIP_SERVER_DEVICE_TYPE ) - 1 ) )
461             continue;
462
463         const char* psz_udn = xml_getChildElementValue( p_device_element,
464                                                         "UDN" );
465         if ( !psz_udn )
466         {
467             msg_Warn( m_sd, "No UDN!" );
468             continue;
469         }
470
471         /* Check if server is already added */
472         if ( getServer( psz_udn ) )
473         {
474             msg_Warn( m_sd, "Server with uuid '%s' already exists.", psz_udn );
475             continue;
476         }
477
478         const char* psz_friendly_name =
479                    xml_getChildElementValue( p_device_element,
480                                              "friendlyName" );
481
482         if ( !psz_friendly_name )
483         {
484             msg_Dbg( m_sd, "No friendlyName!" );
485             continue;
486         }
487
488         std::string iconUrl = getIconURL( p_device_element, psz_base_url );
489
490         // We now have basic info, we need to get the content browsing url
491         // so the access module can browse without fetching the manifest again
492
493         if ( !strncmp( SATIP_SERVER_DEVICE_TYPE, psz_device_type,
494                 strlen( SATIP_SERVER_DEVICE_TYPE ) - 1 ) )
495         {
496             SD::MediaServerDesc* p_server = NULL;
497
498             vlc_url_t url;
499             vlc_UrlParse( &url, psz_base_url );
500
501             char *psz_satip_channellist = config_GetPsz("satip-channelist");
502             if( !psz_satip_channellist ) {
503                 break;
504             }
505
506             /* a user may have provided a custom playlist url */
507             if (strncmp(psz_satip_channellist, "CustomList", 10) == 0) {
508                 char *psz_satip_playlist_url = config_GetPsz( "satip-channellist-url" );
509                 if ( psz_satip_playlist_url ) {
510                     p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_satip_playlist_url, iconUrl );
511
512                     if( likely( p_server ) ) {
513                         p_server->satIpHost = url.psz_host;
514                         p_server->isSatIp = true;
515                         if( !addServer( p_server ) ) {
516                             delete p_server;
517                         }
518                     }
519
520                     /* to comply with the SAT>IP specification, we don't fall back on another channel list if this path failed */
521                     free( psz_satip_playlist_url );
522                     vlc_UrlClean( &url );
523                     continue;
524                 }
525             }
526
527             /* If requested by the user, check for a SAT>IP m3u list, which may be provided by some rare devices */
528             if (strncmp(psz_satip_channellist, "ServerList", 10) == 0) {
529                 const char* psz_m3u_url = xml_getChildElementValue( p_device_element, "satip:X_SATIPM3U" );
530                 if ( psz_m3u_url ) {
531                     if ( strncmp( "http", psz_m3u_url, 4) )
532                     {
533                         char* psz_url = NULL;
534                         if ( UpnpResolveURL2( psz_base_url, psz_m3u_url, &psz_url ) == UPNP_E_SUCCESS )
535                         {
536                             p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_url, iconUrl );
537                             free(psz_url);
538                         }
539                     } else {
540                         p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_m3u_url, iconUrl );
541                     }
542
543                     if ( unlikely( !p_server ) )
544                     {
545                         free( psz_satip_channellist );
546                         break;
547                     }
548
549                     p_server->satIpHost = url.psz_host;
550                     p_server->isSatIp = true;
551                     if ( !addServer( p_server ) )
552                         delete p_server;
553                 } else {
554                     msg_Warn( m_sd, "SAT>IP server '%s' did not provide a playlist", url.psz_host);
555                 }
556
557                 /* to comply with the SAT>IP specifications, we don't fallback on another channel list if this path failed */
558                 free(psz_satip_channellist);
559                 vlc_UrlClean( &url );
560                 continue;
561             }
562
563             /* Normally, fetch a playlist from the web,
564              * which will be processed by a lua script a bit later */
565             char *psz_url;
566             if (asprintf( &psz_url, "http://www.satip.info/Playlists/%s.m3u",
567                          psz_satip_channellist ) < 0 ) {
568                 vlc_UrlClean( &url );
569                 free( psz_satip_channellist );
570                 continue;
571             }
572
573             p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
574                                                              psz_friendly_name, psz_url, iconUrl );
575
576             if( likely( p_server ) ) {
577                 p_server->satIpHost = url.psz_host;
578                 p_server->isSatIp = true;
579                 if( !addServer( p_server ) ) {
580                     delete p_server;
581                 }
582             }
583             free( psz_url );
584             free( psz_satip_channellist );
585             vlc_UrlClean( &url );
586
587             continue;
588         }
589
590         /* Check for ContentDirectory service. */
591         IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
592         if ( !p_service_list )
593             continue;
594         for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
595         {
596             IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
597
598             const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
599             if ( !psz_service_type )
600             {
601                 msg_Warn( m_sd, "No service type found." );
602                 continue;
603             }
604
605             int k = strlen( CONTENT_DIRECTORY_SERVICE_TYPE ) - 1;
606             if ( strncmp( CONTENT_DIRECTORY_SERVICE_TYPE,
607                         psz_service_type, k ) )
608                 continue;
609
610             const char* psz_control_url = xml_getChildElementValue( p_service_element,
611                                           "controlURL" );
612             if ( !psz_control_url )
613             {
614                 msg_Warn( m_sd, "No control url found." );
615                 continue;
616             }
617
618             /* Try to browse content directory. */
619             char* psz_url = ( char* ) malloc( strlen( psz_base_url ) + strlen( psz_control_url ) + 1 );
620             if ( psz_url )
621             {
622                 if ( UpnpResolveURL( psz_base_url, psz_control_url, psz_url ) == UPNP_E_SUCCESS )
623                 {
624                     SD::MediaServerDesc* p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
625                             psz_friendly_name, psz_url, iconUrl );
626                     free( psz_url );
627                     if ( unlikely( !p_server ) )
628                         break;
629
630                     if ( !addServer( p_server ) )
631                     {
632                         delete p_server;
633                         continue;
634                     }
635                 }
636                 else
637                     free( psz_url );
638             }
639         }
640         ixmlNodeList_free( p_service_list );
641     }
642     ixmlNodeList_free( p_device_list );
643 }
644
645 std::string MediaServerList::getIconURL( IXML_Element* p_device_elem, const char* psz_base_url )
646 {
647     std::string res;
648     IXML_NodeList* p_icon_lists = ixmlElement_getElementsByTagName( p_device_elem, "iconList" );
649     if ( p_icon_lists == NULL )
650         return res;
651     IXML_Element* p_icon_list = (IXML_Element*)ixmlNodeList_item( p_icon_lists, 0 );
652     if ( p_icon_list != NULL )
653     {
654         IXML_NodeList* p_icons = ixmlElement_getElementsByTagName( p_icon_list, "icon" );
655         if ( p_icons != NULL )
656         {
657             unsigned int maxWidth = 0;
658             unsigned int maxHeight = 0;
659             for ( unsigned int i = 0; i < ixmlNodeList_length( p_icons ); ++i )
660             {
661                 IXML_Element* p_icon = (IXML_Element*)ixmlNodeList_item( p_icons, i );
662                 const char* widthStr = xml_getChildElementValue( p_icon, "width" );
663                 const char* heightStr = xml_getChildElementValue( p_icon, "height" );
664                 if ( widthStr == NULL || heightStr == NULL )
665                     continue;
666                 unsigned int width = atoi( widthStr );
667                 unsigned int height = atoi( heightStr );
668                 if ( width <= maxWidth || height <= maxHeight )
669                     continue;
670                 const char* iconUrl = xml_getChildElementValue( p_icon, "url" );
671                 if ( iconUrl == NULL )
672                     continue;
673                 maxWidth = width;
674                 maxHeight = height;
675                 res = iconUrl;
676             }
677             ixmlNodeList_free( p_icons );
678         }
679     }
680     ixmlNodeList_free( p_icon_lists );
681
682     if ( res.empty() == false )
683     {
684         vlc_url_t url;
685         vlc_UrlParse( &url, psz_base_url );
686         char* psz_url;
687         if ( asprintf( &psz_url, "%s://%s:%u%s", url.psz_protocol, url.psz_host, url.i_port, res.c_str() ) < 0 )
688             res.clear();
689         else
690         {
691             res = psz_url;
692             free( psz_url );
693         }
694         vlc_UrlClean( &url );
695     }
696     return res;
697 }
698
699 void MediaServerList::removeServer( const std::string& udn )
700 {
701     MediaServerDesc* p_server = getServer( udn );
702     if ( !p_server )
703         return;
704
705     msg_Dbg( m_sd, "Removing server '%s'", p_server->friendlyName.c_str() );
706
707     assert(p_server->inputItem);
708     services_discovery_RemoveItem( m_sd, p_server->inputItem );
709
710     std::vector<MediaServerDesc*>::iterator it = std::find(m_list.begin(), m_list.end(), p_server);
711     if (it != m_list.end())
712     {
713         m_list.erase( it );
714     }
715     delete p_server;
716 }
717
718 /*
719  * Handles servers listing UPnP events
720  */
721 int MediaServerList::onEvent( Upnp_EventType event_type, UpnpEventPtr p_event, void* p_user_data )
722 {
723     if (p_user_data != MEDIA_SERVER_DEVICE_TYPE)
724         return 0;
725
726     switch( event_type )
727     {
728     case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
729     case UPNP_DISCOVERY_SEARCH_RESULT:
730     {
731         const UpnpDiscovery* p_discovery = ( const UpnpDiscovery* )p_event;
732
733         IXML_Document *p_description_doc = NULL;
734
735         int i_res;
736         i_res = UpnpDownloadXmlDoc( UpnpDiscovery_get_Location_cstr( p_discovery ), &p_description_doc );
737
738         if ( i_res != UPNP_E_SUCCESS )
739         {
740             msg_Warn( m_sd, "Could not download device description! "
741                             "Fetching data from %s failed: %s",
742                             UpnpDiscovery_get_Location_cstr( p_discovery ), UpnpGetErrorMessage( i_res ) );
743             return i_res;
744         }
745         parseNewServer( p_description_doc, UpnpDiscovery_get_Location_cstr( p_discovery ) );
746         ixmlDocument_free( p_description_doc );
747     }
748     break;
749
750     case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
751     {
752         const UpnpDiscovery* p_discovery = ( const UpnpDiscovery* )p_event;
753         removeServer( UpnpDiscovery_get_DeviceID_cstr( p_discovery ) );
754     }
755     break;
756
757     case UPNP_EVENT_SUBSCRIBE_COMPLETE:
758     {
759         msg_Warn( m_sd, "subscription complete" );
760     }
761         break;
762
763     case UPNP_DISCOVERY_SEARCH_TIMEOUT:
764     {
765         msg_Warn( m_sd, "search timeout" );
766     }
767         break;
768
769     case UPNP_EVENT_RECEIVED:
770     case UPNP_EVENT_AUTORENEWAL_FAILED:
771     case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
772         // Those are for the access part
773         break;
774
775     default:
776     {
777         msg_Err( m_sd, "Unhandled event, please report ( type=%d )", event_type );
778     }
779         break;
780     }
781
782     return UPNP_E_SUCCESS;
783 }
784
785 }
786
787 namespace Access
788 {
789
790 namespace
791 {
792     class ItemDescriptionHolder
793     {
794     private:
795         struct Slave : std::string
796         {
797             slave_type type;
798
799             Slave(std::string const &url, slave_type type) :
800                 std::string(url), type(type)
801             {
802             }
803         };
804
805         std::set<Slave> slaves;
806
807         const char* objectID,
808             * title,
809             * psz_artist,
810             * psz_genre,
811             * psz_album,
812             * psz_date,
813             * psz_orig_track_nb,
814             * psz_album_artist,
815             * psz_albumArt;
816
817     public:
818         enum MEDIA_TYPE
819             {
820                 VIDEO = 0,
821                 AUDIO,
822                 IMAGE,
823                 CONTAINER
824             };
825
826         MEDIA_TYPE media_type;
827
828         ItemDescriptionHolder()
829         {
830         }
831
832         bool init(IXML_Element *itemElement)
833         {
834             objectID = ixmlElement_getAttribute( itemElement, "id" );
835             if ( !objectID )
836                 return false;
837             title = xml_getChildElementValue( itemElement, "dc:title" );
838             if ( !title )
839                 return false;
840             const char *psz_subtitles = xml_getChildElementValue( itemElement, "sec:CaptionInfo" );
841             if ( !psz_subtitles &&
842                  !(psz_subtitles = xml_getChildElementValue( itemElement, "sec:CaptionInfoEx" )) )
843                 psz_subtitles = xml_getChildElementValue( itemElement, "pv:subtitlefile" );
844             addSlave(psz_subtitles, SLAVE_TYPE_SPU);
845             psz_artist = xml_getChildElementValue( itemElement, "upnp:artist" );
846             psz_genre = xml_getChildElementValue( itemElement, "upnp:genre" );
847             psz_album = xml_getChildElementValue( itemElement, "upnp:album" );
848             psz_date = xml_getChildElementValue( itemElement, "dc:date" );
849             psz_orig_track_nb = xml_getChildElementValue( itemElement, "upnp:originalTrackNumber" );
850             psz_album_artist = xml_getChildElementValue( itemElement, "upnp:albumArtist" );
851             psz_albumArt = xml_getChildElementValue( itemElement, "upnp:albumArtURI" );
852             const char *psz_media_type = xml_getChildElementValue( itemElement, "upnp:class" );
853             if (strncmp(psz_media_type, "object.item.videoItem", 21) == 0)
854                 media_type = VIDEO;
855             else if (strncmp(psz_media_type, "object.item.audioItem", 21) == 0)
856                 media_type = AUDIO;
857             else if (strncmp(psz_media_type, "object.item.imageItem", 21) == 0)
858                 media_type = IMAGE;
859             else if (strncmp(psz_media_type, "object.container", 16 ) == 0)
860                 media_type = CONTAINER;
861             else
862                 return false;
863             return true;
864         }
865
866         void addSlave(const char *psz_slave, slave_type type)
867         {
868             if (psz_slave)
869                 slaves.insert(Slave(psz_slave, type));
870         }
871
872         void addSubtitleSlave(IXML_Element* p_resource)
873         {
874             if (slaves.empty())
875                 addSlave(ixmlElement_getAttribute( p_resource, "pv:subtitleFileUri" ),
876                          SLAVE_TYPE_SPU);
877         }
878
879         void setArtworkURL(IXML_Element* p_resource)
880         {
881             psz_albumArt = xml_getChildElementValue( p_resource, "res" );
882         }
883
884         void apply(input_item_t *p_item)
885         {
886             if ( psz_artist != NULL )
887                 input_item_SetArtist( p_item, psz_artist );
888             if ( psz_genre != NULL )
889                 input_item_SetGenre( p_item, psz_genre );
890             if ( psz_album != NULL )
891                 input_item_SetAlbum( p_item, psz_album );
892             if ( psz_date != NULL )
893                 input_item_SetDate( p_item, psz_date );
894             if ( psz_orig_track_nb != NULL )
895                 input_item_SetTrackNumber( p_item, psz_orig_track_nb );
896             if ( psz_album_artist != NULL )
897                 input_item_SetAlbumArtist( p_item, psz_album_artist );
898             if ( psz_albumArt != NULL )
899                 input_item_SetArtworkURL( p_item, psz_albumArt );
900             for (std::set<Slave>::iterator it = slaves.begin(); it != slaves.end(); ++it)
901             {
902                 input_item_slave *p_slave = input_item_slave_New( it->c_str(), it->type,
903                                                                   SLAVE_PRIORITY_MATCH_ALL );
904                 if ( p_slave )
905                     input_item_AddSlave( p_item, p_slave );
906             }
907         }
908
909         input_item_t *createNewItem(IXML_Element *p_resource)
910         {
911             vlc_tick_t i_duration = -1;
912             const char* psz_resource_url = xml_getChildElementValue( p_resource, "res" );
913             if( !psz_resource_url )
914                 return NULL;
915             const char* psz_duration = ixmlElement_getAttribute( p_resource, "duration" );
916             if ( psz_duration )
917             {
918                 int i_hours, i_minutes, i_seconds;
919                 if( sscanf( psz_duration, "%d:%02d:%02d", &i_hours, &i_minutes, &i_seconds ) )
920                     i_duration = CLOCK_FREQ * ( i_hours * 3600 + i_minutes * 60 +
921                                                 i_seconds );
922             }
923             return input_item_NewExt( psz_resource_url, title, i_duration,
924                                       ITEM_TYPE_FILE, ITEM_NET );
925         }
926
927         input_item_t *createNewContainerItem( const char* psz_root )
928         {
929             if ( objectID == NULL || title == NULL )
930                 return NULL;
931
932             char* psz_url;
933             if( asprintf( &psz_url, "upnp://%s?ObjectID=%s", psz_root, objectID ) < 0 )
934                 return NULL;
935
936             input_item_t* p_item = input_item_NewDirectory( psz_url, title, ITEM_NET );
937             free( psz_url);
938             return p_item;
939         }
940     };
941 }
942
943 Upnp_i11e_cb::Upnp_i11e_cb( Upnp_FunPtr callback, void *cookie )
944     : m_refCount( 2 ) /* 2: owned by the caller, and the Upnp Async function */
945     , m_callback( callback )
946     , m_cookie( cookie )
947
948 {
949     vlc_mutex_init( &m_lock );
950     vlc_sem_init( &m_sem, 0 );
951 }
952
953 Upnp_i11e_cb::~Upnp_i11e_cb()
954 {
955     vlc_mutex_destroy( &m_lock );
956     vlc_sem_destroy( &m_sem );
957 }
958
959 void Upnp_i11e_cb::waitAndRelease( void )
960 {
961     vlc_sem_wait_i11e( &m_sem );
962
963     vlc_mutex_lock( &m_lock );
964     if ( --m_refCount == 0 )
965     {
966         /* The run callback is processed, we can destroy this object */
967         vlc_mutex_unlock( &m_lock );
968         delete this;
969     } else
970     {
971         /* Interrupted, let the run callback destroy this object */
972         vlc_mutex_unlock( &m_lock );
973     }
974 }
975
976 int Upnp_i11e_cb::run( Upnp_EventType eventType, UpnpEventPtr p_event, void *p_cookie )
977 {
978     Upnp_i11e_cb *self = static_cast<Upnp_i11e_cb*>( p_cookie );
979
980     vlc_mutex_lock( &self->m_lock );
981     if ( --self->m_refCount == 0 )
982     {
983         /* Interrupted, we can destroy self */
984         vlc_mutex_unlock( &self->m_lock );
985         delete self;
986         return 0;
987     }
988     /* Process the user callback_ */
989     self->m_callback( eventType, p_event, self->m_cookie);
990     vlc_mutex_unlock( &self->m_lock );
991
992     /* Signal that the callback is processed */
993     vlc_sem_post( &self->m_sem );
994     return 0;
995 }
996
997 MediaServer::MediaServer( stream_t *p_access, input_item_node_t *node )
998     : m_psz_objectId( NULL )
999     , m_access( p_access )
1000     , m_node( node )
1001
1002 {
1003     m_psz_root = strdup( p_access->psz_location );
1004     char* psz_objectid = strstr( m_psz_root, "ObjectID=" );
1005     if ( psz_objectid != NULL )
1006     {
1007         // Remove this parameter from the URL, since it might cause some servers to fail
1008         // Keep in mind that we added a '&' or a '?' to the URL, so remove it as well
1009         *( psz_objectid - 1) = 0;
1010         m_psz_objectId = &psz_objectid[strlen( "ObjectID=" )];
1011     }
1012 }
1013
1014 MediaServer::~MediaServer()
1015 {
1016     free( m_psz_root );
1017 }
1018
1019 bool MediaServer::addContainer( IXML_Element* containerElement )
1020 {
1021     ItemDescriptionHolder holder;
1022
1023     if ( holder.init( containerElement ) == false )
1024         return false;
1025
1026     input_item_t* p_item = holder.createNewContainerItem( m_psz_root );
1027     if ( !p_item )
1028         return false;
1029     holder.apply( p_item );
1030     input_item_CopyOptions( p_item, m_node->p_item );
1031     input_item_node_AppendItem( m_node, p_item );
1032     input_item_Release( p_item );
1033     return true;
1034 }
1035
1036 bool MediaServer::addItem( IXML_Element* itemElement )
1037 {
1038     ItemDescriptionHolder holder;
1039
1040     if (!holder.init(itemElement))
1041         return false;
1042     /* Try to extract all resources in DIDL */
1043     IXML_NodeList* p_resource_list = ixmlDocument_getElementsByTagName( (IXML_Document*) itemElement, "res" );
1044     if ( !p_resource_list)
1045         return false;
1046     int list_lenght = ixmlNodeList_length( p_resource_list );
1047     if (list_lenght <= 0 ) {
1048         ixmlNodeList_free( p_resource_list );
1049         return false;
1050     }
1051     input_item_t *p_item = NULL;
1052
1053     for (int index = 0; index < list_lenght; index++)
1054     {
1055         IXML_Element* p_resource = ( IXML_Element* ) ixmlNodeList_item( p_resource_list, index );
1056         const char* rez_type = ixmlElement_getAttribute( p_resource, "protocolInfo" );
1057
1058         if (strncmp(rez_type, "http-get:*:video/", 17) == 0 && holder.media_type == ItemDescriptionHolder::VIDEO)
1059         {
1060             if (!p_item)
1061                 p_item = holder.createNewItem(p_resource);
1062             holder.addSubtitleSlave(p_resource);
1063         }
1064         else if (strncmp(rez_type, "http-get:*:image/", 17) == 0)
1065             switch (holder.media_type)
1066             {
1067             case ItemDescriptionHolder::IMAGE:
1068                 if (!p_item) {
1069                     p_item = holder.createNewItem(p_resource);
1070                     break;
1071                 }
1072             case ItemDescriptionHolder::VIDEO:
1073             case ItemDescriptionHolder::AUDIO:
1074                 holder.setArtworkURL(p_resource);
1075                 break;
1076             case ItemDescriptionHolder::CONTAINER:
1077                 msg_Warn( m_access, "Unexpected object.container in item enumeration" );
1078                 continue;
1079             }
1080         else if (strncmp(rez_type, "http-get:*:text/", 16) == 0)
1081             holder.addSlave(xml_getChildElementValue( p_resource, "res" ), SLAVE_TYPE_SPU);
1082         else if (strncmp(rez_type, "http-get:*:audio/", 17) == 0)
1083         {
1084             if (holder.media_type == ItemDescriptionHolder::AUDIO)
1085             {
1086                 if (!p_item)
1087                     p_item = holder.createNewItem(p_resource);
1088             }
1089             else
1090                 holder.addSlave(xml_getChildElementValue( p_resource, "res" ),
1091                                 SLAVE_TYPE_AUDIO);
1092         }
1093     }
1094     ixmlNodeList_free( p_resource_list );
1095     if (!p_item)
1096         return false;
1097     holder.apply(p_item);
1098     input_item_CopyOptions( p_item, m_node->p_item );
1099     input_item_node_AppendItem( m_node, p_item );
1100     input_item_Release( p_item );
1101     return true;
1102 }
1103
1104 int MediaServer::sendActionCb( Upnp_EventType eventType,
1105                                UpnpEventPtr p_event, void *p_cookie )
1106 {
1107     if( eventType != UPNP_CONTROL_ACTION_COMPLETE )
1108         return 0;
1109     IXML_Document** pp_sendActionResult = (IXML_Document** )p_cookie;
1110     const UpnpActionComplete *p_result = (const UpnpActionComplete *)p_event;
1111
1112     /* The only way to dup the result is to print it and parse it again */
1113     DOMString tmpStr = ixmlPrintNode( ( IXML_Node * ) UpnpActionComplete_get_ActionResult( p_result ) );
1114     if (tmpStr == NULL)
1115         return 0;
1116
1117     *pp_sendActionResult = ixmlParseBuffer( tmpStr );
1118     ixmlFreeDOMString( tmpStr );
1119     return 0;
1120 }
1121
1122 /* Access part */
1123 IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
1124                                            const char* psz_browser_flag_,
1125                                            const char* psz_filter_,
1126                                            const char* psz_requested_count_,
1127                                            const char* psz_sort_criteria_ )
1128 {
1129     IXML_Document* p_action = NULL;
1130     IXML_Document* p_response = NULL;
1131     Upnp_i11e_cb *i11eCb = NULL;
1132     access_sys_t *sys = (access_sys_t *)m_access->p_sys;
1133
1134     int i_res;
1135
1136     if ( vlc_killed() )
1137         return NULL;
1138
1139     i_res = UpnpAddToAction( &p_action, "Browse",
1140             CONTENT_DIRECTORY_SERVICE_TYPE, "ObjectID", psz_object_id_ ? psz_object_id_ : "0" );
1141
1142     if ( i_res != UPNP_E_SUCCESS )
1143     {
1144         msg_Dbg( m_access, "AddToAction 'ObjectID' failed: %s",
1145                 UpnpGetErrorMessage( i_res ) );
1146         goto browseActionCleanup;
1147     }
1148
1149     i_res = UpnpAddToAction( &p_action, "Browse",
1150             CONTENT_DIRECTORY_SERVICE_TYPE, "BrowseFlag", psz_browser_flag_ );
1151
1152     if ( i_res != UPNP_E_SUCCESS )
1153     {
1154         msg_Dbg( m_access, "AddToAction 'BrowseFlag' failed: %s",
1155                 UpnpGetErrorMessage( i_res ) );
1156         goto browseActionCleanup;
1157     }
1158
1159     i_res = UpnpAddToAction( &p_action, "Browse",
1160             CONTENT_DIRECTORY_SERVICE_TYPE, "Filter", psz_filter_ );
1161
1162     if ( i_res != UPNP_E_SUCCESS )
1163     {
1164         msg_Dbg( m_access, "AddToAction 'Filter' failed: %s",
1165                 UpnpGetErrorMessage( i_res ) );
1166         goto browseActionCleanup;
1167     }
1168
1169     i_res = UpnpAddToAction( &p_action, "Browse",
1170             CONTENT_DIRECTORY_SERVICE_TYPE, "StartingIndex", "0" );
1171     if ( i_res != UPNP_E_SUCCESS )
1172     {
1173         msg_Dbg( m_access, "AddToAction 'StartingIndex' failed: %s",
1174                 UpnpGetErrorMessage( i_res ) );
1175         goto browseActionCleanup;
1176     }
1177
1178     i_res = UpnpAddToAction( &p_action, "Browse",
1179             CONTENT_DIRECTORY_SERVICE_TYPE, "RequestedCount", psz_requested_count_ );
1180
1181     if ( i_res != UPNP_E_SUCCESS )
1182     {
1183         msg_Dbg( m_access, "AddToAction 'RequestedCount' failed: %s",
1184                 UpnpGetErrorMessage( i_res ) );
1185         goto browseActionCleanup;
1186     }
1187
1188     i_res = UpnpAddToAction( &p_action, "Browse",
1189             CONTENT_DIRECTORY_SERVICE_TYPE, "SortCriteria", psz_sort_criteria_ );
1190
1191     if ( i_res != UPNP_E_SUCCESS )
1192     {
1193         msg_Dbg( m_access, "AddToAction 'SortCriteria' failed: %s",
1194                 UpnpGetErrorMessage( i_res ) );
1195         goto browseActionCleanup;
1196     }
1197
1198     /* Setup an interruptible callback that will call sendActionCb if not
1199      * interrupted by vlc_interrupt_kill */
1200     i11eCb = new Upnp_i11e_cb( sendActionCb, &p_response );
1201     i_res = UpnpSendActionAsync( sys->p_upnp->handle(),
1202               m_psz_root,
1203               CONTENT_DIRECTORY_SERVICE_TYPE,
1204               NULL, /* ignored in SDK, must be NULL */
1205               p_action,
1206               Upnp_i11e_cb::run, i11eCb );
1207
1208     if ( i_res != UPNP_E_SUCCESS )
1209     {
1210         msg_Err( m_access, "%s when trying the send() action with URL: %s",
1211                 UpnpGetErrorMessage( i_res ), m_access->psz_location );
1212     }
1213     /* Wait for the callback to fill p_response or wait for an interrupt */
1214     i11eCb->waitAndRelease();
1215
1216 browseActionCleanup:
1217     ixmlDocument_free( p_action );
1218     return p_response;
1219 }
1220
1221 /*
1222  * Fetches and parses the UPNP response
1223  */
1224 bool MediaServer::fetchContents()
1225 {
1226     IXML_Document* p_response = _browseAction( m_psz_objectId,
1227                                       "BrowseDirectChildren",
1228                                       "*",
1229                                       // Some servers don't understand "0" as "no-limit"
1230                                       "5000", /* RequestedCount */
1231                                       "" /* SortCriteria */
1232                                       );
1233     if ( !p_response )
1234     {
1235         msg_Err( m_access, "No response from browse() action" );
1236         return false;
1237     }
1238
1239     IXML_Document* p_result = parseBrowseResult( p_response );
1240
1241     ixmlDocument_free( p_response );
1242
1243     if ( !p_result )
1244     {
1245         msg_Err( m_access, "browse() response parsing failed" );
1246         return false;
1247     }
1248
1249 #ifndef NDEBUG
1250     msg_Dbg( m_access, "Got DIDL document: %s", ixmlPrintDocument( p_result ) );
1251 #endif
1252
1253     IXML_NodeList* containerNodeList =
1254                 ixmlDocument_getElementsByTagName( p_result, "container" );
1255
1256     if ( containerNodeList )
1257     {
1258         for ( unsigned int i = 0; i < ixmlNodeList_length( containerNodeList ); i++ )
1259             addContainer( (IXML_Element*)ixmlNodeList_item( containerNodeList, i ) );
1260         ixmlNodeList_free( containerNodeList );
1261     }
1262
1263     IXML_NodeList* itemNodeList = ixmlDocument_getElementsByTagName( p_result,
1264                                                                      "item" );
1265     if ( itemNodeList )
1266     {
1267         for ( unsigned int i = 0; i < ixmlNodeList_length( itemNodeList ); i++ )
1268             addItem( (IXML_Element*)ixmlNodeList_item( itemNodeList, i ) );
1269         ixmlNodeList_free( itemNodeList );
1270     }
1271
1272     ixmlDocument_free( p_result );
1273     return true;
1274 }
1275
1276 static int ReadDirectory( stream_t *p_access, input_item_node_t* p_node )
1277 {
1278     MediaServer server( p_access, p_node );
1279
1280     if ( !server.fetchContents() )
1281         return VLC_EGENERIC;
1282     return VLC_SUCCESS;
1283 }
1284
1285 static int Open( vlc_object_t *p_this )
1286 {
1287     stream_t* p_access = (stream_t*)p_this;
1288     access_sys_t* p_sys = new(std::nothrow) access_sys_t;
1289     if ( unlikely( !p_sys ) )
1290         return VLC_ENOMEM;
1291
1292     p_access->p_sys = p_sys;
1293     p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
1294     if ( !p_sys->p_upnp )
1295     {
1296         delete p_sys;
1297         return VLC_EGENERIC;
1298     }
1299
1300     p_access->pf_readdir = ReadDirectory;
1301     p_access->pf_control = access_vaDirectoryControlHelper;
1302
1303     return VLC_SUCCESS;
1304 }
1305
1306 static void Close( vlc_object_t* p_this )
1307 {
1308     stream_t* p_access = (stream_t*)p_this;
1309     access_sys_t *sys = (access_sys_t *)p_access->p_sys;
1310
1311     sys->p_upnp->release();
1312     delete sys;
1313 }
1314
1315 }
1316
1317 UpnpInstanceWrapper::UpnpInstanceWrapper()
1318     : m_handle( -1 )
1319     , m_refcount( 0 )
1320 {
1321 }
1322
1323 UpnpInstanceWrapper::~UpnpInstanceWrapper()
1324 {
1325     UpnpUnRegisterClient( m_handle );
1326     UpnpFinish();
1327 }
1328
1329 #ifdef _WIN32
1330
1331 static IP_ADAPTER_MULTICAST_ADDRESS* getMulticastAddress(IP_ADAPTER_ADDRESSES* p_adapter)
1332 {
1333     const unsigned long i_broadcast_ip = inet_addr("239.255.255.250");
1334
1335     IP_ADAPTER_MULTICAST_ADDRESS *p_multicast = p_adapter->FirstMulticastAddress;
1336     while (p_multicast != NULL)
1337     {
1338         if (((struct sockaddr_in *)p_multicast->Address.lpSockaddr)->sin_addr.S_un.S_addr == i_broadcast_ip)
1339             return p_multicast;
1340         p_multicast = p_multicast->Next;
1341     }
1342     return NULL;
1343 }
1344
1345 static bool isAdapterSuitable(IP_ADAPTER_ADDRESSES* p_adapter, bool ipv6)
1346 {
1347     if ( p_adapter->OperStatus != IfOperStatusUp )
1348         return false;
1349     if (p_adapter->Length == sizeof(IP_ADAPTER_ADDRESSES_XP))
1350     {
1351         IP_ADAPTER_ADDRESSES_XP* p_adapter_xp = reinterpret_cast<IP_ADAPTER_ADDRESSES_XP*>( p_adapter );
1352         // On Windows Server 2003 and Windows XP, this member is zero if IPv4 is not available on the interface.
1353         if (ipv6)
1354             return p_adapter_xp->Ipv6IfIndex != 0;
1355         return p_adapter_xp->IfIndex != 0;
1356     }
1357     IP_ADAPTER_ADDRESSES_LH* p_adapter_lh = reinterpret_cast<IP_ADAPTER_ADDRESSES_LH*>( p_adapter );
1358     if (p_adapter_lh->FirstGatewayAddress == NULL)
1359         return false;
1360     if (ipv6)
1361         return p_adapter_lh->Ipv6Enabled;
1362     return p_adapter_lh->Ipv4Enabled;
1363 }
1364
1365 static IP_ADAPTER_ADDRESSES* ListAdapters()
1366 {
1367     ULONG addrSize;
1368     const ULONG queryFlags = GAA_FLAG_INCLUDE_GATEWAYS|GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_DNS_SERVER;
1369     IP_ADAPTER_ADDRESSES* addresses = NULL;
1370     HRESULT hr;
1371
1372     /**
1373      * https://msdn.microsoft.com/en-us/library/aa365915.aspx
1374      *
1375      * The recommended method of calling the GetAdaptersAddresses function is to pre-allocate a
1376      * 15KB working buffer pointed to by the AdapterAddresses parameter. On typical computers,
1377      * this dramatically reduces the chances that the GetAdaptersAddresses function returns
1378      * ERROR_BUFFER_OVERFLOW, which would require calling GetAdaptersAddresses function multiple
1379      * times. The example code illustrates this method of use.
1380      */
1381     addrSize = 15 * 1024;
1382     do
1383     {
1384         free(addresses);
1385         addresses = (IP_ADAPTER_ADDRESSES*)malloc( addrSize );
1386         if (addresses == NULL)
1387             return NULL;
1388         hr = GetAdaptersAddresses(AF_UNSPEC, queryFlags, NULL, addresses, &addrSize);
1389     } while (hr == ERROR_BUFFER_OVERFLOW);
1390     if (hr != NO_ERROR) {
1391         free(addresses);
1392         return NULL;
1393     }
1394     return addresses;
1395 }
1396
1397 #ifdef UPNP_ENABLE_IPV6
1398
1399 static char* getPreferedAdapter()
1400 {
1401     IP_ADAPTER_ADDRESSES *p_adapter, *addresses;
1402
1403     addresses = ListAdapters();
1404     if (addresses == NULL)
1405         return NULL;
1406
1407     /* find one with multicast capabilities */
1408     p_adapter = addresses;
1409     while (p_adapter != NULL)
1410     {
1411         if (isAdapterSuitable( p_adapter, true ))
1412         {
1413             /* make sure it supports 239.255.255.250 */
1414             IP_ADAPTER_MULTICAST_ADDRESS *p_multicast = getMulticastAddress( p_adapter );
1415             if (p_multicast != NULL)
1416             {
1417                 char* res = FromWide( p_adapter->FriendlyName );
1418                 free( addresses );
1419                 return res;
1420             }
1421         }
1422         p_adapter = p_adapter->Next;
1423     }
1424     free(addresses);
1425     return NULL;
1426 }
1427
1428 #else
1429
1430 static char *getIpv4ForMulticast()
1431 {
1432     IP_ADAPTER_UNICAST_ADDRESS *p_best_ip = NULL;
1433     wchar_t psz_uri[32];
1434     DWORD strSize;
1435     IP_ADAPTER_ADDRESSES *p_adapter, *addresses;
1436
1437     addresses = ListAdapters();
1438     if (addresses == NULL)
1439         return NULL;
1440
1441     /* find one with multicast capabilities */
1442     p_adapter = addresses;
1443     while (p_adapter != NULL)
1444     {
1445         if (isAdapterSuitable( p_adapter, false ))
1446         {
1447             /* make sure it supports 239.255.255.250 */
1448             IP_ADAPTER_MULTICAST_ADDRESS *p_multicast = getMulticastAddress( p_adapter );
1449             if (p_multicast != NULL)
1450             {
1451                 /* get an IPv4 address */
1452                 IP_ADAPTER_UNICAST_ADDRESS *p_unicast = p_adapter->FirstUnicastAddress;
1453                 while (p_unicast != NULL)
1454                 {
1455                     strSize = sizeof( psz_uri ) / sizeof( wchar_t );
1456                     if( WSAAddressToString( p_unicast->Address.lpSockaddr,
1457                                             p_unicast->Address.iSockaddrLength,
1458                                             NULL, psz_uri, &strSize ) == 0 )
1459                     {
1460                         if ( p_best_ip == NULL ||
1461                              p_best_ip->ValidLifetime > p_unicast->ValidLifetime )
1462                         {
1463                             p_best_ip = p_unicast;
1464                         }
1465                     }
1466                     p_unicast = p_unicast->Next;
1467                 }
1468             }
1469         }
1470         p_adapter = p_adapter->Next;
1471     }
1472
1473     if ( p_best_ip != NULL )
1474         goto done;
1475
1476     /* find any with IPv4 */
1477     p_adapter = addresses;
1478     while (p_adapter != NULL)
1479     {
1480         if (isAdapterSuitable(p_adapter, false))
1481         {
1482             /* get an IPv4 address */
1483             IP_ADAPTER_UNICAST_ADDRESS *p_unicast = p_adapter->FirstUnicastAddress;
1484             while (p_unicast != NULL)
1485             {
1486                 strSize = sizeof( psz_uri ) / sizeof( wchar_t );
1487                 if( WSAAddressToString( p_unicast->Address.lpSockaddr,
1488                                         p_unicast->Address.iSockaddrLength,
1489                                         NULL, psz_uri, &strSize ) == 0 )
1490                 {
1491                     if ( p_best_ip == NULL ||
1492                          p_best_ip->ValidLifetime > p_unicast->ValidLifetime )
1493                     {
1494                         p_best_ip = p_unicast;
1495                     }
1496                 }
1497                 p_unicast = p_unicast->Next;
1498             }
1499         }
1500         p_adapter = p_adapter->Next;
1501     }
1502
1503 done:
1504     if (p_best_ip != NULL)
1505     {
1506         strSize = sizeof( psz_uri ) / sizeof( wchar_t );
1507         WSAAddressToString( p_best_ip->Address.lpSockaddr,
1508                             p_best_ip->Address.iSockaddrLength,
1509                             NULL, psz_uri, &strSize );
1510         free(addresses);
1511         return FromWide( psz_uri );
1512     }
1513     free(addresses);
1514     return NULL;
1515 }
1516 #endif /* UPNP_ENABLE_IPV6 */
1517 #else /* _WIN32 */
1518
1519 #ifdef UPNP_ENABLE_IPV6
1520
1521 static char *getPreferedAdapter()
1522 {
1523     return NULL;
1524 }
1525
1526 #else
1527
1528 static char *getIpv4ForMulticast()
1529 {
1530     return NULL;
1531 }
1532
1533 #endif
1534
1535 #endif /* _WIN32 */
1536
1537 UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj)
1538 {
1539     vlc_mutex_locker lock( &s_lock );
1540     if ( s_instance == NULL )
1541     {
1542         UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper;
1543         if ( unlikely( !instance ) )
1544         {
1545             return NULL;
1546         }
1547
1548     #ifdef UPNP_ENABLE_IPV6
1549         char* psz_miface = var_InheritString( p_obj, "miface" );
1550         if (psz_miface == NULL)
1551             psz_miface = getPreferedAdapter();
1552         msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface ? psz_miface : "default" );
1553         int i_res = UpnpInit2( psz_miface, 0 );
1554         free( psz_miface );
1555     #else
1556         /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */
1557         char *psz_hostip = getIpv4ForMulticast();
1558         int i_res = UpnpInit( psz_hostip, 0 );
1559         free(psz_hostip);
1560     #endif /* UPNP_ENABLE_IPV6 */
1561         if( i_res != UPNP_E_SUCCESS )
1562         {
1563             msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) );
1564             delete instance;
1565             return NULL;
1566         }
1567
1568         ixmlRelaxParser( 1 );
1569
1570         /* Register a control point */
1571         i_res = UpnpRegisterClient( Callback, instance, &instance->m_handle );
1572         if( i_res != UPNP_E_SUCCESS )
1573         {
1574             msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
1575             delete instance;
1576             return NULL;
1577         }
1578
1579         /* libupnp does not treat a maximum content length of 0 as unlimited
1580          * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate
1581          * between versions */
1582         if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS )
1583         {
1584             msg_Err( p_obj, "Failed to set maximum content length: %s",
1585                     UpnpGetErrorMessage( i_res ));
1586             delete instance;
1587             return NULL;
1588         }
1589         s_instance = instance;
1590     }
1591     s_instance->m_refcount++;
1592     return s_instance;
1593 }
1594
1595 void UpnpInstanceWrapper::release()
1596 {
1597     UpnpInstanceWrapper *p_delete = NULL;
1598     vlc_mutex_lock( &s_lock );
1599     if (--s_instance->m_refcount == 0)
1600     {
1601         p_delete = s_instance;
1602         s_instance = NULL;
1603     }
1604     vlc_mutex_unlock( &s_lock );
1605     delete p_delete;
1606 }
1607
1608 UpnpClient_Handle UpnpInstanceWrapper::handle() const
1609 {
1610     return m_handle;
1611 }
1612
1613 int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, UpnpEventPtr p_event, void *p_user_data)
1614 {
1615     for (Listeners::iterator iter = s_listeners.begin(); iter != s_listeners.end(); ++iter)
1616     {
1617         (*iter)->onEvent(event_type, p_event, p_user_data);
1618     }
1619
1620     return 0;
1621 }
1622
1623 void UpnpInstanceWrapper::addListener(ListenerPtr listener)
1624 {
1625     vlc_mutex_lock( &s_lock );
1626     if ( std::find( s_listeners.begin(), s_listeners.end(), listener) != s_listeners.end() )
1627         return;
1628     s_listeners.push_back( std::move(listener) );
1629     vlc_mutex_unlock( &s_lock );
1630 }
1631
1632 void UpnpInstanceWrapper::removeListener(ListenerPtr listener)
1633 {
1634     vlc_mutex_lock( &s_lock );
1635     Listeners::iterator iter = std::find( s_listeners.begin(), s_listeners.end(), listener );
1636     if ( iter == s_listeners.end() )
1637         return;
1638
1639     s_listeners.erase( iter );
1640     vlc_mutex_unlock( &s_lock );
1641 }