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