* modules/access/dvb: Full support for DVB MMI menus via an optional HTTP
authorChristophe Massiot <massiot@videolan.org>
Tue, 27 Dec 2005 18:05:47 +0000 (18:05 +0000)
committerChristophe Massiot <massiot@videolan.org>
Tue, 27 Dec 2005 18:05:47 +0000 (18:05 +0000)
   server.

modules/access/dvb/Modules.am
modules/access/dvb/access.c
modules/access/dvb/dvb.h
modules/access/dvb/en50221.c
modules/access/dvb/http.c [new file with mode: 0644]
modules/access/dvb/linux_dvb.c

index af40f7c..1217318 100644 (file)
@@ -2,5 +2,6 @@ SOURCES_dvb = \
        access.c \
        linux_dvb.c \
        en50221.c \
+       http.c \
        dvb.h \
        $(NULL)
index 4d82ebb..973d11e 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * access.c: DVB card input v4l2 only
  *****************************************************************************
- * Copyright (C) 1998-2004 the VideoLAN team
+ * Copyright (C) 1998-2005 the VideoLAN team
  *
  * Authors: Johan Bilien <jobi@via.ecp.fr>
  *          Jean-Paul Saman <jpsaman@wxs.nl>
 #   include "psi.h"
 #endif
 
+#ifdef ENABLE_HTTPD
+#   include "vlc_httpd.h"
+#endif
+
 #include "dvb.h"
 
 /*****************************************************************************
@@ -139,6 +143,40 @@ static void Close( vlc_object_t *p_this );
 #define HIERARCHY_TEXT N_("Terrestrial hierarchy mode")
 #define HIERARCHY_LONGTEXT ""
 
+#define HOST_TEXT N_( "HTTP Host address" )
+#define HOST_LONGTEXT N_( \
+    "To enable the internal HTTP server, set its address and port here." )
+
+#define USER_TEXT N_( "HTTP user name" )
+#define USER_LONGTEXT N_( \
+    "You can set the user name the administrator will use to log into " \
+    "the internal HTTP server." )
+
+#define PASSWORD_TEXT N_( "HTTP password" )
+#define PASSWORD_LONGTEXT N_( \
+    "You can set the password the administrator will use to log into " \
+    "the internal HTTP server." )
+
+#define ACL_TEXT N_( "HTTP ACL" )
+#define ACL_LONGTEXT N_( \
+    "You can set the access control list (equivalent to .hosts) file path, " \
+    "which will limit the range of IPs entitled to log into the internal " \
+    "HTTP server." )
+
+#define CERT_TEXT N_( "Certificate file" )
+#define CERT_LONGTEXT N_( "HTTP interface x509 PEM certificate file " \
+                          "(enables SSL)" )
+
+#define KEY_TEXT N_( "Private key file" )
+#define KEY_LONGTEXT N_( "HTTP interface x509 PEM private key file" )
+
+#define CA_TEXT N_( "Root CA file" )
+#define CA_LONGTEXT N_( "HTTP interface x509 PEM trusted root CA " \
+                        "certificates file" )
+
+#define CRL_TEXT N_( "CRL file" )
+#define CRL_LONGTEXT N_( "HTTP interface Certificates Revocation List file" )
+
 vlc_module_begin();
     set_shortname( _("DVB") );
     set_description( N_("DVB input with v4l2 support") );
@@ -191,6 +229,26 @@ vlc_module_begin();
                  TRANSMISSION_LONGTEXT, VLC_TRUE );
     add_integer( "dvb-hierarchy", 0, NULL, HIERARCHY_TEXT, HIERARCHY_LONGTEXT,
                  VLC_TRUE );
+#ifdef ENABLE_HTTPD
+    /* MMI HTTP interface */
+    set_section( N_("HTTP server" ), 0 );
+    add_string( "dvb-http-host", NULL, NULL, HOST_TEXT, HOST_LONGTEXT,
+                VLC_TRUE );
+    add_string( "dvb-http-user", NULL, NULL, USER_TEXT, USER_LONGTEXT,
+                VLC_TRUE );
+    add_string( "dvb-http-password", NULL, NULL, PASSWORD_TEXT,
+                PASSWORD_LONGTEXT, VLC_TRUE );
+    add_string( "dvb-http-acl", NULL, NULL, ACL_TEXT, ACL_LONGTEXT,
+                VLC_TRUE );
+    add_string( "dvb-http-intf-cert", NULL, NULL, CERT_TEXT, CERT_LONGTEXT,
+                VLC_TRUE );
+    add_string( "dvb-http-intf-key",  NULL, NULL, KEY_TEXT,  KEY_LONGTEXT,
+                VLC_TRUE );
+    add_string( "dvb-http-intf-ca",   NULL, NULL, CA_TEXT,   CA_LONGTEXT,
+                VLC_TRUE );
+    add_string( "dvb-http-intf-crl",  NULL, NULL, CRL_TEXT,  CRL_LONGTEXT,
+                VLC_TRUE );
+#endif
 
     set_capability( "access2", 0 );
     add_shortcut( "dvb" );
@@ -303,6 +361,10 @@ static int Open( vlc_object_t *p_this )
     else
         p_sys->i_read_once = DVB_READ_ONCE_START;
 
+#ifdef ENABLE_HTTPD
+    E_(HTTPOpen)( p_access );
+#endif
+
     return VLC_SUCCESS;
 }
 
@@ -320,6 +382,10 @@ static void Close( vlc_object_t *p_this )
     E_(FrontendClose)( p_access );
     E_(CAMClose)( p_access );
 
+#ifdef ENABLE_HTTPD
+    E_(HTTPClose)( p_access );
+#endif
+
     free( p_sys );
 }
 
@@ -376,6 +442,37 @@ static block_t *Block( access_t *p_access )
             E_(FrontendPoll)( p_access );
         }
 
+#ifdef ENABLE_HTTPD
+        if ( p_sys->i_httpd_timeout && mdate() > p_sys->i_httpd_timeout )
+        {
+            vlc_mutex_lock( &p_sys->httpd_mutex );
+            if ( p_sys->b_request_frontend_info )
+            {
+                msg_Warn( p_access, "frontend timeout for HTTP interface" );
+                p_sys->b_request_frontend_info = VLC_FALSE;
+                p_sys->psz_frontend_info = strdup( "Timeout getting info\n" );
+            }
+            if ( p_sys->b_request_mmi_info )
+            {
+                msg_Warn( p_access, "MMI timeout for HTTP interface" );
+                p_sys->b_request_mmi_info = VLC_FALSE;
+                p_sys->psz_mmi_info = strdup( "Timeout getting info\n" );
+            }
+            vlc_cond_signal( &p_sys->httpd_cond );
+            vlc_mutex_unlock( &p_sys->httpd_mutex );
+        }
+
+        if ( p_sys->b_request_frontend_info )
+        {
+            E_(FrontendStatus)( p_access );
+        }
+
+        if ( p_sys->b_request_mmi_info )
+        {
+            E_(CAMStatus)( p_access );
+        }
+#endif
+
         if ( p_sys->i_frontend_timeout && mdate() > p_sys->i_frontend_timeout )
         {
             msg_Warn( p_access, "no lock, tuning again" );
@@ -577,6 +674,17 @@ static void VarInit( access_t *p_access )
     var_Create( p_access, "dvb-transmission", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
     var_Create( p_access, "dvb-guard", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
     var_Create( p_access, "dvb-hierarchy", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
+
+#ifdef ENABLE_HTTPD
+    var_Create( p_access, "dvb-http-host", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-user", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-password", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-acl", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-intf-cert", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-intf-key", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-intf-ca", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Create( p_access, "dvb-http-intf-crl", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+#endif
 }
 
 /* */
index bcf1f92..f8e089f 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * dvb.h : functions to control a DVB card under Linux with v4l2
  *****************************************************************************
- * Copyright (C) 1998-2004 the VideoLAN team
+ * Copyright (C) 1998-2005 the VideoLAN team
  *
  * Authors: Johan Bilien <jobi@via.ecp.fr>
  *          Jean-Paul Saman <jpsaman@saman>
@@ -35,7 +35,7 @@
 /*****************************************************************************
  * Local structures
  *****************************************************************************/
-typedef struct
+typedef struct demux_handle_t
 {
     int i_pid;
     int i_handle;
@@ -44,7 +44,7 @@ typedef struct
 
 typedef struct frontend_t frontend_t;
 
-typedef struct
+typedef struct en50221_session_t
 {
     int i_slot;
     int i_resource_id;
@@ -54,6 +54,84 @@ typedef struct
     void *p_sys;
 } en50221_session_t;
 
+#define EN50221_MMI_NONE 0
+#define EN50221_MMI_ENQ 1
+#define EN50221_MMI_ANSW 2
+#define EN50221_MMI_MENU 3
+#define EN50221_MMI_MENU_ANSW 4
+#define EN50221_MMI_LIST 5
+
+typedef struct en50221_mmi_object_t
+{
+    int i_object_type;
+
+    union
+    {
+        struct
+        {
+            vlc_bool_t b_blind;
+            char *psz_text;
+        } enq;
+
+        struct
+        {
+            vlc_bool_t b_ok;
+            char *psz_answ;
+        } answ;
+
+        struct
+        {
+            char *psz_title, *psz_subtitle, *psz_bottom;
+            char **ppsz_choices;
+            int i_choices;
+        } menu; /* menu and list are the same */
+
+        struct
+        {
+            int i_choice;
+        } menu_answ;
+    } u;
+} en50221_mmi_object_t;
+
+static __inline__ void en50221_MMIFree( en50221_mmi_object_t *p_object )
+{
+    int i;
+
+#define FREE( x )                                                           \
+    if ( x != NULL )                                                        \
+        free( x );
+
+    switch ( p_object->i_object_type )
+    {
+    case EN50221_MMI_ENQ:
+        FREE( p_object->u.enq.psz_text );
+        break;
+
+    case EN50221_MMI_ANSW:
+        if ( p_object->u.answ.b_ok )
+        {
+            FREE( p_object->u.answ.psz_answ );
+        }
+        break;
+
+    case EN50221_MMI_MENU:
+    case EN50221_MMI_LIST:
+        FREE( p_object->u.menu.psz_title );
+        FREE( p_object->u.menu.psz_subtitle );
+        FREE( p_object->u.menu.psz_bottom );
+        for ( i = 0; i < p_object->u.menu.i_choices; i++ )
+        {
+            FREE( p_object->u.menu.ppsz_choices[i] );
+        }
+        FREE( p_object->u.menu.ppsz_choices );
+        break;
+
+    default:
+        break;
+    }
+#undef FREE
+}
+
 #define MAX_DEMUX 256
 #define MAX_CI_SLOTS 16
 #define MAX_SESSIONS 32
@@ -72,6 +150,8 @@ struct access_sys_t
     int i_nb_slots;
     vlc_bool_t pb_active_slot[MAX_CI_SLOTS];
     vlc_bool_t pb_tc_has_data[MAX_CI_SLOTS];
+    vlc_bool_t pb_slot_mmi_expected[MAX_CI_SLOTS];
+    vlc_bool_t pb_slot_mmi_undisplayed[MAX_CI_SLOTS];
     en50221_session_t p_sessions[MAX_SESSIONS];
     mtime_t i_ca_timeout, i_ca_next_event, i_frontend_timeout;
     dvbpsi_pmt_t *pp_selected_programs[MAX_PROGRAMS];
@@ -79,6 +159,20 @@ struct access_sys_t
 
     /* */
     int i_read_once;
+
+#ifdef ENABLE_HTTPD
+    /* Local HTTP server */
+    httpd_host_t        *p_httpd_host;
+    httpd_file_sys_t    *p_httpd_file;
+    httpd_redirect_t    *p_httpd_redir;
+
+    vlc_mutex_t         httpd_mutex;
+    vlc_cond_t          httpd_cond;
+    mtime_t             i_httpd_timeout;
+    vlc_bool_t          b_request_frontend_info, b_request_mmi_info;
+    char                *psz_frontend_info, *psz_mmi_info;
+    char                *psz_request;
+#endif
 };
 
 #define VIDEO0_TYPE     1
@@ -96,6 +190,9 @@ int  E_(FrontendOpen)( access_t * );
 void E_(FrontendPoll)( access_t *p_access );
 int  E_(FrontendSet)( access_t * );
 void E_(FrontendClose)( access_t * );
+#ifdef ENABLE_HTTPD
+void E_(FrontendStatus)( access_t * );
+#endif
 
 int E_(DMXSetFilter)( access_t *, int i_pid, int * pi_fd, int i_type );
 int E_(DMXUnsetFilter)( access_t *, int i_fd );
@@ -107,9 +204,25 @@ int  E_(CAMOpen)( access_t * );
 int  E_(CAMPoll)( access_t * );
 int  E_(CAMSet)( access_t *, dvbpsi_pmt_t * );
 void E_(CAMClose)( access_t * );
+#ifdef ENABLE_HTTPD
+void E_(CAMStatus)( access_t * );
+#endif
 
 int E_(en50221_Init)( access_t * );
 int E_(en50221_Poll)( access_t * );
 int E_(en50221_SetCAPMT)( access_t *, dvbpsi_pmt_t * );
+int E_(en50221_OpenMMI)( access_t * p_access, int i_slot );
+int E_(en50221_CloseMMI)( access_t * p_access, int i_slot );
+en50221_mmi_object_t *E_(en50221_GetMMIObject)( access_t * p_access,
+                                                int i_slot );
+void E_(en50221_SendMMIObject)( access_t * p_access, int i_slot,
+                                en50221_mmi_object_t *p_object );
 void E_(en50221_End)( access_t * );
 
+#ifdef ENABLE_HTTPD
+int E_(HTTPOpen)( access_t *p_access );
+void E_(HTTPClose)( access_t *p_access );
+char *E_(HTTPExtractValue)( char *psz_uri, const char *psz_name,
+                            char *psz_value, int i_value_max );
+#endif
+
index fa4d61b..40ff0a8 100644 (file)
@@ -2,7 +2,7 @@
  * en50221.c : implementation of the transport, session and applications
  * layers of EN 50 221
  *****************************************************************************
- * Copyright (C) 2004 the VideoLAN team
+ * Copyright (C) 2004-2005 the VideoLAN team
  *
  * Authors: Christophe Massiot <massiot@via.ecp.fr>
  * Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger
 #   include "psi.h"
 #endif
 
+#ifdef ENABLE_HTTPD
+#   include "vlc_httpd.h"
+#endif
+
 #include "dvb.h"
 
 #undef DEBUG_TPDU
@@ -471,6 +475,99 @@ static void SessionOpen( access_t * p_access, uint8_t i_slot,
     }
 }
 
+#if 0
+/* unused code for the moment - commented out to keep gcc happy */
+/*****************************************************************************
+ * SessionCreate
+ *****************************************************************************/
+static void SessionCreate( access_t * p_access, int i_slot, int i_resource_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    uint8_t p_response[16];
+    uint8_t i_tag;
+    int i_session_id;
+
+    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+    {
+        if ( !p_sys->p_sessions[i_session_id - 1].i_resource_id )
+            break;
+    }
+    if ( i_session_id == MAX_SESSIONS )
+    {
+        msg_Err( p_access, "too many sessions !" );
+        return;
+    }
+    p_sys->p_sessions[i_session_id - 1].i_slot = i_slot;
+    p_sys->p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
+    p_sys->p_sessions[i_session_id - 1].pf_close = NULL;
+    p_sys->p_sessions[i_session_id - 1].pf_manage = NULL;
+    p_sys->p_sessions[i_session_id - 1].p_sys = NULL;
+
+    p_response[0] = ST_CREATE_SESSION;
+    p_response[1] = 0x6;
+    p_response[2] = i_resource_id >> 24;
+    p_response[3] = (i_resource_id >> 16) & 0xff;
+    p_response[4] = (i_resource_id >> 8) & 0xff;
+    p_response[5] = i_resource_id & 0xff;
+    p_response[6] = i_session_id >> 8;
+    p_response[7] = i_session_id & 0xff;
+
+    if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 4 ) !=
+            VLC_SUCCESS )
+    {
+        msg_Err( p_access,
+                 "SessionCreate: couldn't send TPDU on slot %d", i_slot );
+        return;
+    }
+    if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != VLC_SUCCESS )
+    {
+        msg_Err( p_access,
+                 "SessionCreate: couldn't recv TPDU on slot %d", i_slot );
+        return;
+    }
+}
+#endif
+
+/*****************************************************************************
+ * SessionCreateResponse
+ *****************************************************************************/
+static void SessionCreateResponse( access_t * p_access, uint8_t i_slot,
+                                   uint8_t *p_spdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_status = p_spdu[2];
+    int i_resource_id = ResourceIdToInt( &p_spdu[3] );
+    int i_session_id = ((int)p_spdu[7] << 8) | p_spdu[8];
+
+    if ( i_status != SS_OK )
+    {
+        msg_Err( p_access, "SessionCreateResponse: failed to open session %d"
+                 " resource=0x%x status=0x%x", i_session_id, i_resource_id,
+                 i_status );
+        p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
+        return;
+    }
+
+    switch ( i_resource_id )
+    {
+    case RI_RESOURCE_MANAGER:
+        ResourceManagerOpen( p_access, i_session_id ); break; 
+    case RI_APPLICATION_INFORMATION:
+        ApplicationInformationOpen( p_access, i_session_id ); break; 
+    case RI_CONDITIONAL_ACCESS_SUPPORT:
+        ConditionalAccessOpen( p_access, i_session_id ); break; 
+    case RI_DATE_TIME:
+        DateTimeOpen( p_access, i_session_id ); break; 
+    case RI_MMI:
+        MMIOpen( p_access, i_session_id ); break; 
+
+    case RI_HOST_CONTROL:
+    default:
+        msg_Err( p_access, "unknown resource id (0x%x)", i_resource_id );
+        p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
+    }
+}
+
 /*****************************************************************************
  * SessionSendClose
  *****************************************************************************/
@@ -561,20 +658,39 @@ static void SPDUHandle( access_t * p_access, uint8_t i_slot,
         SessionOpen( p_access, i_slot, p_spdu, i_size );
         break;
 
+    case ST_CREATE_SESSION_RESPONSE:
+        if ( i_size != 9 || p_spdu[1] != 0x7 )
+            return;
+        SessionCreateResponse( p_access, i_slot, p_spdu, i_size );
+        break;
+
     case ST_CLOSE_SESSION_REQUEST:
+        if ( i_size != 4 || p_spdu[1] != 0x2 )
+            return;
         i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
         SessionClose( p_access, i_session_id );
         break;
 
     case ST_CLOSE_SESSION_RESPONSE:
-        i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
-        if ( p_sys->p_sessions[i_session_id - 1].pf_close != NULL )
-            p_sys->p_sessions[i_session_id - 1].pf_close( p_access,
-                                                          i_session_id );
-        p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
+        if ( i_size != 5 || p_spdu[1] != 0x3 )
+            return;
+        i_session_id = ((int)p_spdu[3] << 8) | p_spdu[4];
+        if ( p_spdu[2] )
+        {
+            msg_Err( p_access, "closing a session which is not allocated (%d)",
+                     i_session_id );
+        }
+        else
+        {
+            if ( p_sys->p_sessions[i_session_id - 1].pf_close != NULL )
+                p_sys->p_sessions[i_session_id - 1].pf_close( p_access,
+                                                              i_session_id );
+            p_sys->p_sessions[i_session_id - 1].i_resource_id = 0;
+        }
         break;
 
     default:
+        msg_Err( p_access, "unexpected tag in SPDUHandle (%x)", p_spdu[0] );
         break;
     }
 }
@@ -675,13 +791,13 @@ static int APDUSend( access_t * p_access, int i_session_id, int i_tag,
     p = SetLength( p, i_size );
     if ( i_size )
         memcpy( p, p_data, i_size );
-    if( p_sys->i_ca_type == CA_CI_LINK )
+    if ( p_sys->i_ca_type == CA_CI_LINK )
     {
         i_ret = SPDUSend( p_access, i_session_id, p_apdu, i_size + p - p_apdu );
     }
     else
     {
-        if( i_size + p - p_apdu >256 )
+        if ( i_size + p - p_apdu > 256 )
         {
             msg_Err( p_access, "CAM: apdu overflow" );
             i_ret = VLC_EGENERIC;
@@ -690,11 +806,11 @@ static int APDUSend( access_t * p_access, int i_session_id, int i_tag,
         {
             char *psz_hex;
             ca_msg.length = i_size + p - p_apdu;
-            if( i_size == 0 ) ca_msg.length=3;
+            if ( i_size == 0 ) ca_msg.length=3;
             psz_hex = (char*)malloc( ca_msg.length*3 + 1);
             memcpy( ca_msg.msg, p_apdu, i_size + p - p_apdu );
             i_ret = ioctl(p_sys->i_ca_handle, CA_SEND_MSG, &ca_msg );
-            if( i_ret < 0 )
+            if ( i_ret < 0 )
             {
                 msg_Err( p_access, "Error sending to CAM: %s", strerror(errno) );
                 i_ret = VLC_EGENERIC;
@@ -759,6 +875,20 @@ static void ResourceManagerOpen( access_t * p_access, int i_session_id )
  * Application Information
  */
 
+/*****************************************************************************
+ * ApplicationInformationEnterMenu
+ *****************************************************************************/
+static void ApplicationInformationEnterMenu( access_t * p_access,
+                                             int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+
+    msg_Dbg( p_access, "Entering MMI menus on session %d", i_session_id );
+    APDUSend( p_access, i_session_id, AOT_ENTER_MENU, NULL, 0 );
+    p_sys->pb_slot_mmi_expected[i_slot] = VLC_TRUE;
+}
+
 /*****************************************************************************
  * ApplicationInformationHandle
  *****************************************************************************/
@@ -1190,6 +1320,18 @@ static void ConditionalAccessHandle( access_t * p_access, int i_session_id,
     }
 }
 
+/*****************************************************************************
+ * ConditionalAccessClose
+ *****************************************************************************/
+static void ConditionalAccessClose( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "closing ConditionalAccess session (%d)", i_session_id );
+
+    free( p_sys->p_sessions[i_session_id - 1].p_sys );
+}
+
 /*****************************************************************************
  * ConditionalAccessOpen
  *****************************************************************************/
@@ -1200,6 +1342,7 @@ static void ConditionalAccessOpen( access_t * p_access, int i_session_id )
     msg_Dbg( p_access, "opening ConditionalAccess session (%d)", i_session_id );
 
     p_sys->p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle;
+    p_sys->p_sessions[i_session_id - 1].pf_close = ConditionalAccessClose;
     p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(system_ids_t));
     memset( p_sys->p_sessions[i_session_id - 1].p_sys, 0,
             sizeof(system_ids_t) );
@@ -1308,6 +1451,18 @@ static void DateTimeManage( access_t * p_access, int i_session_id )
     }
 }
 
+/*****************************************************************************
+ * DateTimeClose
+ *****************************************************************************/
+static void DateTimeClose( access_t * p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    msg_Dbg( p_access, "closing DateTime session (%d)", i_session_id );
+
+    free( p_sys->p_sessions[i_session_id - 1].p_sys );
+}
+
 /*****************************************************************************
  * DateTimeOpen
  *****************************************************************************/
@@ -1319,6 +1474,7 @@ static void DateTimeOpen( access_t * p_access, int i_session_id )
 
     p_sys->p_sessions[i_session_id - 1].pf_handle = DateTimeHandle;
     p_sys->p_sessions[i_session_id - 1].pf_manage = DateTimeManage;
+    p_sys->p_sessions[i_session_id - 1].pf_close = DateTimeClose;
     p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(date_time_t));
     memset( p_sys->p_sessions[i_session_id - 1].p_sys, 0, sizeof(date_time_t) );
 
@@ -1363,6 +1519,63 @@ static void DateTimeOpen( access_t * p_access, int i_session_id )
 #define AI_CANCEL  0x00
 #define AI_ANSWER  0x01
 
+typedef struct
+{
+    en50221_mmi_object_t last_object;
+} mmi_t;
+
+/*****************************************************************************
+ * MMISendObject
+ *****************************************************************************/
+static void MMISendObject( access_t *p_access, int i_session_id,
+                           en50221_mmi_object_t *p_object )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+    uint8_t *p_data;
+    int i_size, i_tag;
+
+    switch ( p_object->i_object_type )
+    {
+    case EN50221_MMI_ANSW:
+        i_tag = AOT_ANSW;
+        i_size = 1 + strlen( p_object->u.answ.psz_answ );
+        p_data = malloc( i_size );
+        p_data[0] = (p_object->u.answ.b_ok == VLC_TRUE) ? 0x1 : 0x0;
+        strncpy( &p_data[1], p_object->u.answ.psz_answ, i_size - 1 );
+        break;
+
+    case EN50221_MMI_MENU_ANSW:
+        i_tag = AOT_MENU_ANSW;
+        i_size = 1;
+        p_data = malloc( i_size );
+        p_data[0] = p_object->u.menu_answ.i_choice;
+        break;
+
+    default:
+        msg_Err( p_access, "unknown MMI object %d", p_object->i_object_type );
+        return;
+    }
+
+    APDUSend( p_access, i_session_id, i_tag, p_data, i_size );
+    free( p_data );
+
+    p_sys->pb_slot_mmi_expected[i_slot] = VLC_TRUE;
+}
+
+/*****************************************************************************
+ * MMISendClose
+ *****************************************************************************/
+static void MMISendClose( access_t *p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+
+    APDUSend( p_access, i_session_id, AOT_CLOSE_MMI, NULL, 0 );
+
+    p_sys->pb_slot_mmi_expected[i_slot] = VLC_TRUE;
+}
+
 /*****************************************************************************
  * MMIDisplayReply
  *****************************************************************************/
@@ -1381,22 +1594,22 @@ static void MMIDisplayReply( access_t *p_access, int i_session_id )
 /*****************************************************************************
  * MMIGetText
  *****************************************************************************/
-static char *MMIGetText( access_t *p_access, char *psz_text,
-                         uint8_t **pp_apdu, int *pi_size )
+static char *MMIGetText( access_t *p_access, uint8_t **pp_apdu, int *pi_size )
 {
     int i_tag = APDUGetTag( *pp_apdu, *pi_size );
+    char *psz_text;
     int l;
     uint8_t *d;
 
     if ( i_tag != AOT_TEXT_LAST )
     {
         msg_Err( p_access, "unexpected text tag: %06x", i_tag );
-        psz_text[0] = '\0';
         *pi_size = 0;
-        return psz_text;
+        return strdup( "" );
     }
 
     d = APDUGetLength( *pp_apdu, &l );
+    psz_text = malloc( l + 1 );
     strncpy( psz_text, (char *)d, l );
     psz_text[l] = '\0';
 
@@ -1405,6 +1618,82 @@ static char *MMIGetText( access_t *p_access, char *psz_text,
     return psz_text;
 }
 
+/*****************************************************************************
+ * MMIHandleEnq
+ *****************************************************************************/
+static void MMIHandleEnq( access_t *p_access, int i_session_id,
+                          uint8_t *p_apdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    mmi_t *p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+    int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+    int l;
+    uint8_t *d = APDUGetLength( p_apdu, &l );
+
+    en50221_MMIFree( &p_mmi->last_object );
+    p_mmi->last_object.i_object_type = EN50221_MMI_ENQ;
+    p_mmi->last_object.u.enq.b_blind = (*d & 0x1) ? VLC_TRUE : VLC_FALSE;
+    d += 2; /* skip answer_text_length because it is not mandatory */
+    l -= 2;
+    p_mmi->last_object.u.enq.psz_text = malloc( l + 1 );
+    strncpy( p_mmi->last_object.u.enq.psz_text, (char *)d, l );
+    p_mmi->last_object.u.enq.psz_text[l] = '\0';
+
+    msg_Dbg( p_access, "MMI enq: %s%s", p_mmi->last_object.u.enq.psz_text,
+             p_mmi->last_object.u.enq.b_blind == VLC_TRUE ? " (blind)" : "" );
+    p_sys->pb_slot_mmi_expected[i_slot] = VLC_FALSE;
+    p_sys->pb_slot_mmi_undisplayed[i_slot] = VLC_TRUE;
+}
+
+/*****************************************************************************
+ * MMIHandleMenu
+ *****************************************************************************/
+static void MMIHandleMenu( access_t *p_access, int i_session_id, int i_tag,
+                           uint8_t *p_apdu, int i_size )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    mmi_t *p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+    int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+    int l;
+    uint8_t *d = APDUGetLength( p_apdu, &l );
+
+    en50221_MMIFree( &p_mmi->last_object );
+    p_mmi->last_object.i_object_type = (i_tag == AOT_MENU_LAST) ?
+                                       EN50221_MMI_MENU : EN50221_MMI_LIST;
+    p_mmi->last_object.u.menu.i_choices = 0;
+    p_mmi->last_object.u.menu.ppsz_choices = NULL;
+
+    if ( l > 0 )
+    {
+        l--; d++; /* choice_nb */
+
+#define GET_FIELD( x )                                                      \
+        if ( l > 0 )                                                        \
+        {                                                                   \
+            p_mmi->last_object.u.menu.psz_##x                               \
+                            = MMIGetText( p_access, &d, &l );               \
+            msg_Dbg( p_access, "MMI " STRINGIFY( x ) ": %s",                \
+                     p_mmi->last_object.u.menu.psz_##x );                   \
+        }
+
+        GET_FIELD( title );
+        GET_FIELD( subtitle );
+        GET_FIELD( bottom );
+#undef GET_FIELD
+
+        while ( l > 0 )
+        {
+            char *psz_text = MMIGetText( p_access, &d, &l );
+            TAB_APPEND( p_mmi->last_object.u.menu.i_choices,
+                        p_mmi->last_object.u.menu.ppsz_choices,
+                        psz_text );
+            msg_Dbg( p_access, "MMI choice: %s", psz_text );
+        }
+    }
+    p_sys->pb_slot_mmi_expected[i_slot] = VLC_FALSE;
+    p_sys->pb_slot_mmi_undisplayed[i_slot] = VLC_TRUE;
+}
+
 /*****************************************************************************
  * MMIHandle
  *****************************************************************************/
@@ -1440,38 +1729,17 @@ static void MMIHandle( access_t *p_access, int i_session_id,
         break;
     }
 
+    case AOT_ENQ:
+        MMIHandleEnq( p_access, i_session_id, p_apdu, i_size );
+        break;
+
     case AOT_LIST_LAST:
     case AOT_MENU_LAST:
-    {
-        int l;
-        uint8_t *d = APDUGetLength( p_apdu, &l );
-        char psz_text[255];
-
-        if ( l > 0 )
-        {
-            l--; d++; /* choice_nb */
-
-            if ( l > 0 )
-                msg_Info( p_access, "MMI title: %s",
-                          MMIGetText( p_access, psz_text, &d, &l ) );
-            if ( l > 0 )
-                msg_Info( p_access, "MMI subtitle: %s",
-                          MMIGetText( p_access, psz_text, &d, &l ) );
-            if ( l > 0 )
-                msg_Info( p_access, "MMI bottom: %s",
-                          MMIGetText( p_access, psz_text, &d, &l ) );
-            while ( l > 0 )
-            {
-                msg_Info( p_access, "MMI: %s",
-                          MMIGetText( p_access, psz_text, &d, &l ) );
-            }
-        }
+        MMIHandleMenu( p_access, i_session_id, i_tag, p_apdu, i_size );
         break;
-    }
 
     case AOT_CLOSE_MMI:
         SessionSendClose( p_access, i_session_id );
-        msg_Dbg( p_access, "closing MMI session (%d)", i_session_id );
         break;
 
     default:
@@ -1479,16 +1747,38 @@ static void MMIHandle( access_t *p_access, int i_session_id,
     }
 }
 
+/*****************************************************************************
+ * MMIClose
+ *****************************************************************************/
+static void MMIClose( access_t *p_access, int i_session_id )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_slot = p_sys->p_sessions[i_session_id - 1].i_slot;
+    mmi_t *p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+
+    en50221_MMIFree( &p_mmi->last_object );
+    free( p_sys->p_sessions[i_session_id - 1].p_sys );
+
+    msg_Dbg( p_access, "closing MMI session (%d)", i_session_id );
+    p_sys->pb_slot_mmi_expected[i_slot] = VLC_FALSE;
+    p_sys->pb_slot_mmi_undisplayed[i_slot] = VLC_TRUE;
+}
+
 /*****************************************************************************
  * MMIOpen
  *****************************************************************************/
 static void MMIOpen( access_t *p_access, int i_session_id )
 {
     access_sys_t *p_sys = p_access->p_sys;
+    mmi_t *p_mmi;
 
     msg_Dbg( p_access, "opening MMI session (%d)", i_session_id );
 
     p_sys->p_sessions[i_session_id - 1].pf_handle = MMIHandle;
+    p_sys->p_sessions[i_session_id - 1].pf_close = MMIClose;
+    p_sys->p_sessions[i_session_id - 1].p_sys = malloc(sizeof(mmi_t));
+    p_mmi = (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+    p_mmi->last_object.i_object_type = EN50221_MMI_NONE;
 }
 
 
@@ -1824,13 +2114,138 @@ int E_(en50221_SetCAPMT)( access_t * p_access, dvbpsi_pmt_t *p_pmt )
     return VLC_SUCCESS;
 }
 
+/*****************************************************************************
+ * en50221_OpenMMI :
+ *****************************************************************************/
+int E_(en50221_OpenMMI)( access_t * p_access, int i_slot )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    if( p_sys->i_ca_type & CA_CI_LINK )
+    {
+        int i_session_id;
+        for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+        {
+            if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI
+                  && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot )
+            {
+                msg_Dbg( p_access,
+                         "MMI menu is already opened on slot %d (session=%d)",
+                         i_slot, i_session_id );
+                return VLC_SUCCESS;
+            }
+        }
+
+        for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+        {
+            if ( p_sys->p_sessions[i_session_id - 1].i_resource_id
+                    == RI_APPLICATION_INFORMATION
+                  && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot )
+            {
+                ApplicationInformationEnterMenu( p_access, i_session_id );
+                return VLC_SUCCESS;
+            }
+        }
+
+        msg_Err( p_access, "no application information on slot %d", i_slot );
+        return VLC_EGENERIC;
+    }
+    else
+    {
+        msg_Err( p_access, "MMI menu not supported" );
+        return VLC_EGENERIC;
+    }
+}
+
+/*****************************************************************************
+ * en50221_CloseMMI :
+ *****************************************************************************/
+int E_(en50221_CloseMMI)( access_t * p_access, int i_slot )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    if( p_sys->i_ca_type & CA_CI_LINK )
+    {
+        int i_session_id;
+        for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+        {
+            if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI
+                  && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot )
+            {
+                MMISendClose( p_access, i_session_id );
+                return VLC_SUCCESS;
+            }
+        }
+
+        msg_Warn( p_access, "closing a non-existing MMI session on slot %d",
+                  i_slot );
+        return VLC_EGENERIC;
+    }
+    else
+    {
+        msg_Err( p_access, "MMI menu not supported" );
+        return VLC_EGENERIC;
+    }
+}
+
+/*****************************************************************************
+ * en50221_GetMMIObject :
+ *****************************************************************************/
+en50221_mmi_object_t *E_(en50221_GetMMIObject)( access_t * p_access,
+                                                int i_slot )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_session_id;
+
+    if ( p_sys->pb_slot_mmi_expected[i_slot] == VLC_TRUE )
+        return NULL; /* should not happen */
+
+    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+    {
+        if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI
+              && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot )
+        {
+            mmi_t *p_mmi =
+                (mmi_t *)p_sys->p_sessions[i_session_id - 1].p_sys;
+            if ( p_mmi == NULL )
+                return NULL; /* should not happen */
+            return &p_mmi->last_object;
+        }
+    }
+
+    return NULL;
+}
+
+
+/*****************************************************************************
+ * en50221_SendMMIObject :
+ *****************************************************************************/
+void E_(en50221_SendMMIObject)( access_t * p_access, int i_slot,
+                                en50221_mmi_object_t *p_object )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    int i_session_id;
+
+    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+    {
+        if ( p_sys->p_sessions[i_session_id - 1].i_resource_id == RI_MMI
+              && p_sys->p_sessions[i_session_id - 1].i_slot == i_slot )
+        {
+            MMISendObject( p_access, i_session_id, p_object );
+            return;
+        }
+    }
+
+    msg_Err( p_access, "SendMMIObject when no MMI session is opened !" );
+}
+
 /*****************************************************************************
  * en50221_End :
  *****************************************************************************/
 void E_(en50221_End)( access_t * p_access )
 {
     access_sys_t *p_sys = p_access->p_sys;
-    int i;
+    int i_session_id, i;
 
     for ( i = 0; i < MAX_PROGRAMS; i++ )
     {
@@ -1840,5 +2255,17 @@ void E_(en50221_End)( access_t * p_access )
         }
     }
 
-    /* TODO */
+    for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
+    {
+        if ( p_sys->p_sessions[i_session_id - 1].i_resource_id
+              && p_sys->p_sessions[i_session_id - 1].pf_close != NULL )
+        {
+            p_sys->p_sessions[i_session_id - 1].pf_close( p_access,
+                                                          i_session_id );
+        }
+    }
+
+    /* Leave the CAM configured, so that it can be reused in another
+     * program. */
 }
+
diff --git a/modules/access/dvb/http.c b/modules/access/dvb/http.c
new file mode 100644 (file)
index 0000000..15fb92f
--- /dev/null
@@ -0,0 +1,366 @@
+/*****************************************************************************
+ * http.c: HTTP interface
+ *****************************************************************************
+ * Copyright (C) 2005 the VideoLAN team
+ *
+ * Authors: Christophe Massiot <massiot@via.ecp.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+#include <vlc/vlc.h>
+#include <vlc/input.h>
+
+#ifdef HAVE_UNISTD_H
+#   include <unistd.h>
+#endif
+
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include <errno.h>
+
+/* Include dvbpsi headers */
+#ifdef HAVE_DVBPSI_DR_H
+#   include <dvbpsi/dvbpsi.h>
+#   include <dvbpsi/descriptor.h>
+#   include <dvbpsi/pat.h>
+#   include <dvbpsi/pmt.h>
+#   include <dvbpsi/dr.h>
+#   include <dvbpsi/psi.h>
+#else
+#   include "dvbpsi.h"
+#   include "descriptor.h"
+#   include "tables/pat.h"
+#   include "tables/pmt.h"
+#   include "descriptors/dr.h"
+#   include "psi.h"
+#endif
+
+#ifdef ENABLE_HTTPD
+#   include "vlc_httpd.h"
+#   include "vlc_acl.h"
+#endif
+
+#include "dvb.h"
+
+#ifdef ENABLE_HTTPD
+struct httpd_file_sys_t
+{
+    access_t         *p_access;
+    httpd_file_t     *p_file;
+};
+
+static int HttpCallback( httpd_file_sys_t *p_args,
+                         httpd_file_t *p_file,
+                         uint8_t *_p_request,
+                         uint8_t **_pp_data, int *pi_data );
+
+/*****************************************************************************
+ * HTTPOpen: Start the internal HTTP server
+ *****************************************************************************/
+int E_(HTTPOpen)( access_t *p_access )
+{
+#define FREE( x )                                                           \
+    if ( (x) != NULL )                                                      \
+        free( x );
+
+    access_sys_t *p_sys = p_access->p_sys;
+    char          *psz_address, *psz_cert = NULL, *psz_key = NULL,
+                  *psz_ca = NULL, *psz_crl = NULL, *psz_user = NULL,
+                  *psz_password = NULL, *psz_acl = NULL;
+    int           i_port       = 0;
+    char          psz_tmp[10];
+    vlc_acl_t     *p_acl = NULL;
+    httpd_file_sys_t *f;
+
+    vlc_mutex_init( p_access, &p_sys->httpd_mutex );
+    vlc_cond_init( p_access, &p_sys->httpd_cond );
+    p_sys->b_request_frontend_info = p_sys->b_request_mmi_info = VLC_FALSE;
+    p_sys->i_httpd_timeout = 0;
+
+    psz_address = var_GetString( p_access, "dvb-http-host" );
+    if( psz_address != NULL && *psz_address )
+    {
+        char *psz_parser = strchr( psz_address, ':' );
+        if( psz_parser )
+        {
+            *psz_parser++ = '\0';
+            i_port = atoi( psz_parser );
+        }
+    }
+    else
+    {
+        if ( psz_address != NULL ) free( psz_address );
+        return VLC_SUCCESS;
+    }
+
+    /* determine SSL configuration */
+    psz_cert = var_GetString( p_access, "dvb-http-intf-cert" );
+    if ( psz_cert != NULL && *psz_cert )
+    {
+        msg_Dbg( p_access, "enabling TLS for HTTP interface (cert file: %s)",
+                 psz_cert );
+        psz_key = config_GetPsz( p_access, "dvb-http-intf-key" );
+        psz_ca = config_GetPsz( p_access, "dvb-http-intf-ca" );
+        psz_crl = config_GetPsz( p_access, "dvb-http-intf-crl" );
+
+        if ( i_port <= 0 )
+            i_port = 8443;
+    }
+    else
+    {
+        if ( !*psz_cert )
+        {
+            free( psz_cert );
+            psz_cert = NULL;
+        }
+        if ( i_port <= 0 )
+            i_port= 8082;
+    }
+
+    /* Ugly hack to allow to run several HTTP servers on different ports. */
+    sprintf( psz_tmp, ":%d", i_port + 1 );
+    config_PutPsz( p_access, "dvb-http-host", psz_tmp );
+
+    msg_Dbg( p_access, "base %s:%d", psz_address, i_port );
+
+    p_sys->p_httpd_host = httpd_TLSHostNew( VLC_OBJECT(p_access), psz_address,
+                                            i_port, psz_cert, psz_key, psz_ca,
+                                            psz_crl );
+    FREE( psz_cert );
+    FREE( psz_key );
+    FREE( psz_ca );
+    FREE( psz_crl );
+
+    if ( p_sys->p_httpd_host == NULL )
+    {
+        msg_Err( p_access, "cannot listen on %s:%d", psz_address, i_port );
+        free( psz_address );
+        return VLC_EGENERIC;
+    }
+    free( psz_address );
+
+    psz_user = var_GetString( p_access, "dvb-http-user" );
+    psz_password = var_GetString( p_access, "dvb-http-password" );
+    psz_acl = var_GetString( p_access, "dvb-http-acl" );
+
+    if ( psz_acl != NULL )
+    {
+        p_acl = ACL_Create( p_access, VLC_FALSE );
+        if( ACL_LoadFile( p_acl, psz_acl ) )
+        {
+            ACL_Destroy( p_acl );
+            p_acl = NULL;
+        }
+    }
+
+    /* Declare an index.html file. */
+    f = malloc( sizeof(httpd_file_sys_t) );
+    f->p_access = p_access;
+    f->p_file = httpd_FileNew( p_sys->p_httpd_host, "/index.html",
+                               "text/html; charset=UTF-8",
+                               psz_user, psz_password, p_acl,
+                               HttpCallback, f );
+
+    FREE( psz_user );
+    FREE( psz_password );
+    FREE( psz_acl );
+    if ( p_acl != NULL )
+        ACL_Destroy( p_acl );
+
+    if ( f->p_file == NULL )
+    {
+        free( f );
+        p_sys->p_httpd_file = NULL;
+        return VLC_EGENERIC;
+    }
+
+    p_sys->p_httpd_file = f;
+    p_sys->p_httpd_redir = httpd_RedirectNew( p_sys->p_httpd_host,
+                                              "/index.html", "/" );
+
+#undef FREE
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * HTTPClose: Stop the internal HTTP server
+ *****************************************************************************/
+void E_(HTTPClose)( access_t *p_access )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+
+    if ( p_sys->p_httpd_host != NULL )
+    {
+        if ( p_sys->p_httpd_file != NULL )
+        {
+            /* Unlock the thread if it is stuck in HttpCallback */
+            vlc_mutex_lock( &p_sys->httpd_mutex );
+            if ( p_sys->b_request_frontend_info == VLC_TRUE )
+            {
+                p_sys->b_request_frontend_info = VLC_FALSE;
+                p_sys->psz_frontend_info = strdup("");
+            }
+            if ( p_sys->b_request_mmi_info == VLC_TRUE )
+            {
+                p_sys->b_request_mmi_info = VLC_FALSE;
+                p_sys->psz_mmi_info = strdup("");
+            }
+            vlc_cond_signal( &p_sys->httpd_cond );
+            vlc_mutex_unlock( &p_sys->httpd_mutex );
+
+            httpd_FileDelete( p_sys->p_httpd_file->p_file );
+            httpd_RedirectDelete( p_sys->p_httpd_redir );
+        }
+
+        httpd_HostDelete( p_sys->p_httpd_host );
+    }
+
+    vlc_mutex_destroy( &p_sys->httpd_mutex );
+    vlc_cond_destroy( &p_sys->httpd_cond );
+}
+
+
+static const char *psz_constant_header =
+    "<html>\n"
+    "<head><title>VLC DVB monitoring interface</title></head>\n"
+    "<body><a href=\"index.html\">Reload this page</a>\n"
+    "<h1>CAM info</h1>\n";
+
+static const char *psz_constant_middle =
+    "<hr><h1>Frontend Info</h1>\n";
+
+static const char *psz_constant_footer =
+    "</body></html>\n";
+
+/****************************************************************************
+ * HttpCallback: Return the index.html file
+ ****************************************************************************/
+static int HttpCallback( httpd_file_sys_t *p_args,
+                         httpd_file_t *p_file,
+                         uint8_t *_psz_request,
+                         uint8_t **_pp_data, int *pi_data )
+{
+    access_sys_t *p_sys = p_args->p_access->p_sys;
+    char *psz_request = (char *)_psz_request;
+    char **pp_data = (char **)_pp_data;
+
+    vlc_mutex_lock( &p_sys->httpd_mutex );
+
+    p_sys->i_httpd_timeout = mdate() + I64C(3000000); /* 3 s */
+    p_sys->psz_request = psz_request;
+    p_sys->b_request_frontend_info = VLC_TRUE;
+    if ( p_sys->i_ca_handle )
+    {
+        p_sys->b_request_mmi_info = VLC_TRUE;
+    }
+    else
+    {
+        p_sys->psz_mmi_info = strdup( "No available CAM interface\n" );
+    }
+
+    do
+    {
+        vlc_cond_wait( &p_sys->httpd_cond, &p_sys->httpd_mutex );
+    }
+    while ( p_sys->b_request_frontend_info || p_sys->b_request_mmi_info );
+
+    p_sys->i_httpd_timeout = 0;
+    vlc_mutex_unlock( &p_sys->httpd_mutex );
+
+    *pi_data = strlen( psz_constant_header )
+                + strlen( p_sys->psz_mmi_info )
+                + strlen( psz_constant_middle )
+                + strlen( p_sys->psz_frontend_info )
+                + strlen( psz_constant_footer ) + 1;
+    *pp_data = malloc( *pi_data );
+
+    sprintf( *pp_data, "%s%s%s%s%s", psz_constant_header,
+             p_sys->psz_mmi_info, psz_constant_middle,
+             p_sys->psz_frontend_info, psz_constant_footer );
+    free( p_sys->psz_frontend_info );
+    free( p_sys->psz_mmi_info );
+
+    return VLC_SUCCESS;
+}
+
+/****************************************************************************
+ * HTTPExtractValue: Extract a GET variable from psz_request
+ ****************************************************************************/
+char *E_(HTTPExtractValue)( char *psz_uri, const char *psz_name,
+                            char *psz_value, int i_value_max )
+{
+    char *p = psz_uri;
+
+    while( (p = strstr( p, psz_name )) )
+    {
+        /* Verify that we are dealing with a post/get argument */
+        if( (p == psz_uri || *(p - 1) == '&' || *(p - 1) == '\n')
+              && p[strlen(psz_name)] == '=' )
+            break;
+        p++;
+    }
+
+    if( p )
+    {
+        int i_len;
+
+        p += strlen( psz_name );
+        if( *p == '=' ) p++;
+
+        if( strchr( p, '&' ) )
+        {
+            i_len = strchr( p, '&' ) - p;
+        }
+        else
+        {
+            /* for POST method */
+            if( strchr( p, '\n' ) )
+            {
+                i_len = strchr( p, '\n' ) - p;
+                if( i_len && *(p+i_len-1) == '\r' ) i_len--;
+            }
+            else
+            {
+                i_len = strlen( p );
+            }
+        }
+        i_len = __MIN( i_value_max - 1, i_len );
+        if( i_len > 0 )
+        {
+            strncpy( psz_value, p, i_len );
+            psz_value[i_len] = '\0';
+        }
+        else
+        {
+            strncpy( psz_value, "", i_value_max );
+        }
+        p += i_len;
+    }
+    else
+    {
+        strncpy( psz_value, "", i_value_max );
+    }
+
+    return p;
+}
+
+#endif /* ENABLE_HTTPD */
index 2a1b376..a002564 100644 (file)
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * linux_dvb.c : functions to control a DVB card under Linux with v4l2
  *****************************************************************************
- * Copyright (C) 1998-2004 the VideoLAN team
+ * Copyright (C) 1998-2005 the VideoLAN team
  *
  * Authors: Damien Lucas <nitrox@via.ecp.fr>
  *          Johan Bilien <jobi@via.ecp.fr>
 #   include "psi.h"
 #endif
 
+#ifdef ENABLE_HTTPD
+#   include "vlc_httpd.h"
+#endif
+
 #include "dvb.h"
 
 /*
@@ -268,24 +272,24 @@ void E_(FrontendPoll)( access_t *p_access )
         if( i_ret < 0 )
         {
             if( errno == EWOULDBLOCK )
-                return;
+                return; /* no more events */
 
-            msg_Err( p_access, "reading frontend status failed (%d) %s",
+            msg_Err( p_access, "reading frontend event failed (%d) %s",
                      i_ret, strerror(errno) );
-            continue;
+            return;
         }
 
         i_status = event.status;
         i_diff = i_status ^ p_frontend->i_last_status;
         p_frontend->i_last_status = i_status;
 
+        {
 #define IF_UP( x )                                                          \
-    }                                                                       \
-    if ( i_diff & (x) )                                                     \
-    {                                                                       \
-        if ( i_status & (x) )
+        }                                                                   \
+        if ( i_diff & (x) )                                                 \
+        {                                                                   \
+            if ( i_status & (x) )
 
-        {
             IF_UP( FE_HAS_SIGNAL )
                 msg_Dbg( p_access, "frontend has acquired signal" );
             else
@@ -333,8 +337,153 @@ void E_(FrontendPoll)( access_t *p_access )
                 E_(FrontendSet)( p_access );
             }
         }
+#undef IF_UP
     }
 }
+
+#ifdef ENABLE_HTTPD
+/*****************************************************************************
+ * FrontendStatus : Read frontend status
+ *****************************************************************************/
+void E_(FrontendStatus)( access_t *p_access )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    frontend_t *p_frontend = p_sys->p_frontend;
+    char *p = p_sys->psz_frontend_info = malloc( 10000 );
+    fe_status_t i_status;
+    int i_ret;
+
+    /* Determine type of frontend */
+    if( (i_ret = ioctl( p_sys->i_frontend_handle, FE_GET_INFO,
+                        &p_frontend->info )) < 0 )
+    {
+        p += sprintf( p, "ioctl FE_GET_INFO failed (%d) %s\n", i_ret,
+                      strerror(errno) );
+        goto out;
+    }
+
+    /* Print out frontend capabilities. */
+    p += sprintf( p, "<table border=1><tr><th>name</th><td>%s</td></tr>\n",
+                  p_frontend->info.name );
+    switch( p_frontend->info.type )
+    {
+        case FE_QPSK:
+            p += sprintf( p, "<tr><th>type</th><td>QPSK (DVB-S)</td></tr>\n" );
+            break;
+        case FE_QAM:
+            p += sprintf( p, "<tr><th>type</th><td>QAM (DVB-C)</td></tr>\n" );
+            break;
+        case FE_OFDM:
+            p += sprintf( p, "<tr><th>type</th><td>OFDM (DVB-T)</td></tr>\n" );
+            break;
+#if 0 /* DVB_API_VERSION == 3 */
+        case FE_MEMORY:
+            p += sprintf( p, "<tr><th>type</th><td>MEMORY</td></tr>\n" );
+            break;
+        case FE_NET:
+            p += sprintf( p, "<tr><th>type</th><td>NETWORK</td></tr>\n" );
+            break;
+#endif
+        default:
+            p += sprintf( p, "<tr><th>type</th><td>UNKNOWN (%d)</td></tr>\n",
+                          p_frontend->info.type );
+            goto out;
+    }
+#define CHECK_INFO( x )                                                     \
+    p += sprintf( p,                                                        \
+                  "<tr><th>" STRINGIFY(x) "</th><td>%u</td></tr>\n",        \
+                  p_frontend->info.x );
+
+    CHECK_INFO( frequency_min );
+    CHECK_INFO( frequency_max );
+    CHECK_INFO( frequency_stepsize );
+    CHECK_INFO( frequency_tolerance );
+    CHECK_INFO( symbol_rate_min );
+    CHECK_INFO( symbol_rate_max );
+    CHECK_INFO( symbol_rate_tolerance );
+    CHECK_INFO( notifier_delay );
+#undef CHECK_INFO
+
+    p += sprintf( p, "</table><p>Frontend capability list:\n<table border=1>" );
+
+#define CHECK_CAPS( x )                                                     \
+    if ( p_frontend->info.caps & (FE_##x) )                                 \
+        p += sprintf( p, "<tr><td>" STRINGIFY(x) "</td></tr>\n" );
+
+    CHECK_CAPS( IS_STUPID );
+    CHECK_CAPS( CAN_INVERSION_AUTO );
+    CHECK_CAPS( CAN_FEC_1_2 );
+    CHECK_CAPS( CAN_FEC_2_3 );
+    CHECK_CAPS( CAN_FEC_3_4 );
+    CHECK_CAPS( CAN_FEC_4_5 );
+    CHECK_CAPS( CAN_FEC_5_6 );
+    CHECK_CAPS( CAN_FEC_6_7 );
+    CHECK_CAPS( CAN_FEC_7_8 );
+    CHECK_CAPS( CAN_FEC_8_9 );
+    CHECK_CAPS( CAN_FEC_AUTO );
+    CHECK_CAPS( CAN_QPSK );
+    CHECK_CAPS( CAN_QAM_16 );
+    CHECK_CAPS( CAN_QAM_32 );
+    CHECK_CAPS( CAN_QAM_64 );
+    CHECK_CAPS( CAN_QAM_128 );
+    CHECK_CAPS( CAN_QAM_256 );
+    CHECK_CAPS( CAN_QAM_AUTO );
+    CHECK_CAPS( CAN_TRANSMISSION_MODE_AUTO );
+    CHECK_CAPS( CAN_BANDWIDTH_AUTO );
+    CHECK_CAPS( CAN_GUARD_INTERVAL_AUTO );
+    CHECK_CAPS( CAN_HIERARCHY_AUTO );
+    CHECK_CAPS( CAN_MUTE_TS );
+    CHECK_CAPS( CAN_RECOVER );
+    CHECK_CAPS( CAN_CLEAN_SETUP );
+#undef CHECK_CAPS
+
+    p += sprintf( p, "</table><p>Current frontend status:\n<table border=1>" );
+
+    if( (i_ret = ioctl( p_sys->i_frontend_handle, FE_READ_STATUS, &i_status ))
+           < 0 )
+    {
+        p += sprintf( p, "</table>ioctl FE_READ_STATUS failed (%d) %s\n", i_ret,
+                      strerror(errno) );
+        goto out;
+    }
+
+#define CHECK_STATUS( x )                                                   \
+    if ( i_status & (FE_##x) )                                              \
+        p += sprintf( p, "<tr><td>" STRINGIFY(x) "</td></tr>\n" );
+
+    CHECK_STATUS( HAS_SIGNAL );
+    CHECK_STATUS( HAS_CARRIER );
+    CHECK_STATUS( HAS_VITERBI );
+    CHECK_STATUS( HAS_SYNC );
+    CHECK_STATUS( HAS_LOCK );
+    CHECK_STATUS( REINIT );
+#undef CHECK_STATUS
+
+    if ( i_status & FE_HAS_LOCK )
+    {
+        int32_t i_value;
+        p += sprintf( p, "</table><p>Signal status:\n<table border=1>" );
+        if( ioctl( p_sys->i_frontend_handle, FE_READ_BER, &i_value ) >= 0 )
+            p += sprintf( p, "<tr><th>Bit error rate</th><td>%d</td></tr>\n",
+                          i_value );
+        if( ioctl( p_sys->i_frontend_handle, FE_READ_SIGNAL_STRENGTH,
+                   &i_value ) >= 0 )
+            p += sprintf( p, "<tr><th>Signal strength</th><td>%d</td></tr>\n",
+                          i_value );
+        if( ioctl( p_sys->i_frontend_handle, FE_READ_SNR, &i_value ) >= 0 )
+            p += sprintf( p, "<tr><th>SNR</th><td>%d</td></tr>\n",
+                          i_value );
+    }
+    p += sprintf( p, "</table>" );
+
+out:
+    vlc_mutex_lock( &p_sys->httpd_mutex );
+    p_sys->b_request_frontend_info = VLC_FALSE;
+    vlc_cond_signal( &p_sys->httpd_cond );
+    vlc_mutex_unlock( &p_sys->httpd_mutex );
+}
+#endif
+
 /*****************************************************************************
  * FrontendInfo : Return information about given frontend
  *****************************************************************************/
@@ -1242,7 +1391,7 @@ int E_(CAMOpen)( access_t *p_access )
     msg_Dbg( p_access, "CAMInit: CA interface with %d %s", caps.slot_num, 
         caps.slot_num == 1 ? "slot" : "slots" );
     if ( caps.slot_type & CA_CI )
-        msg_Dbg( p_access, "CAMInit: CI high level interface type (not supported)" );
+        msg_Dbg( p_access, "CAMInit: CI high level interface type" );
     if ( caps.slot_type & CA_CI_LINK )
         msg_Dbg( p_access, "CAMInit: CI link layer level interface type" );
     if ( caps.slot_type & CA_CI_PHYS )
@@ -1314,6 +1463,265 @@ int E_(CAMPoll)( access_t * p_access )
     return i_ret;
 }
 
+#ifdef ENABLE_HTTPD
+/*****************************************************************************
+ * CAMStatus :
+ *****************************************************************************/
+void E_(CAMStatus)( access_t * p_access )
+{
+    access_sys_t *p_sys = p_access->p_sys;
+    char *p;
+    ca_caps_t caps;
+    int i_slot, i;
+
+    if ( p_sys->psz_request != NULL && *p_sys->psz_request )
+    {
+        /* Check if we have an undisplayed MMI message : in that case we ignore
+         * the user input to avoid confusing the CAM. */
+        for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
+        {
+            if ( p_sys->pb_slot_mmi_undisplayed[i_slot] == VLC_TRUE )
+            {
+                p_sys->psz_request = NULL;
+                msg_Dbg( p_access,
+                         "ignoring user request because of a new MMI object" );
+                break;
+            }
+        }
+    }
+
+    if ( p_sys->psz_request != NULL && *p_sys->psz_request )
+    {
+        /* We have a mission to accomplish. */
+        en50221_mmi_object_t mmi_object;
+        char *psz_request = p_sys->psz_request;
+        char psz_value[255];
+        int i_slot;
+        vlc_bool_t b_ok = VLC_FALSE;
+
+        p_sys->psz_request = NULL;
+
+        if ( E_(HTTPExtractValue)( psz_request, "slot", psz_value,
+                                   sizeof(psz_value) ) == NULL )
+        {
+            p_sys->psz_mmi_info = strdup( "invalid request parameter\n" );
+            goto out;
+        }
+        i_slot = atoi(psz_value);
+
+        if ( E_(HTTPExtractValue)( psz_request, "open", psz_value,
+                                   sizeof(psz_value) ) != NULL )
+        {
+            E_(en50221_OpenMMI)( p_access, i_slot );
+            return;
+        }
+
+        if ( E_(HTTPExtractValue)( psz_request, "close", psz_value,
+                                   sizeof(psz_value) ) != NULL )
+        {
+            E_(en50221_CloseMMI)( p_access, i_slot );
+            return;
+        }
+
+        if ( E_(HTTPExtractValue)( psz_request, "cancel", psz_value,
+                                   sizeof(psz_value) ) == NULL )
+        {
+            b_ok = VLC_TRUE;
+        }
+
+        if ( E_(HTTPExtractValue)( psz_request, "type", psz_value,
+                                   sizeof(psz_value) ) == NULL )
+        {
+            p_sys->psz_mmi_info = strdup( "invalid request parameter\n" );
+            goto out;
+        }
+
+        if ( !strcmp( psz_value, "enq" ) )
+        {
+            mmi_object.i_object_type = EN50221_MMI_ANSW;
+            mmi_object.u.answ.b_ok = b_ok;
+            if ( b_ok == VLC_FALSE )
+            {
+                mmi_object.u.answ.psz_answ = strdup("");
+            }
+            else
+            {
+                if ( E_(HTTPExtractValue)( psz_request, "answ", psz_value,
+                                           sizeof(psz_value) ) == NULL )
+                {
+                    p_sys->psz_mmi_info = strdup( "invalid request parameter\n" );
+                    goto out;
+                }
+
+                mmi_object.u.answ.psz_answ = strdup(psz_value);
+            }
+        }
+        else
+        {
+            mmi_object.i_object_type = EN50221_MMI_MENU_ANSW;
+            if ( b_ok == VLC_FALSE )
+            {
+                mmi_object.u.menu_answ.i_choice = 0;
+            }
+            else
+            {
+                if ( E_(HTTPExtractValue)( psz_request, "choice", psz_value,
+                                           sizeof(psz_value) ) == NULL )
+                    mmi_object.u.menu_answ.i_choice = 0;
+                else
+                    mmi_object.u.menu_answ.i_choice = atoi(psz_value);
+            }
+        }
+
+        E_(en50221_SendMMIObject)( p_access, i_slot, &mmi_object );
+        return;
+    }
+
+    /* Check that we have all necessary MMI information. */
+    for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
+    {
+        if ( p_sys->pb_slot_mmi_expected[i_slot] == VLC_TRUE )
+            return;
+    }
+
+    p = p_sys->psz_mmi_info = malloc( 10000 );
+
+    if ( ioctl( p_sys->i_ca_handle, CA_GET_CAP, &caps ) != 0 )
+    {
+        p += sprintf( p, "ioctl CA_GET_CAP failed (%s)\n",
+                      strerror(errno) );
+        goto out;
+    }
+
+    /* Output CA capabilities */
+    p += sprintf( p, "CA interface with %d %s, type:\n<table>", caps.slot_num,
+                  caps.slot_num == 1 ? "slot" : "slots" );
+#define CHECK_CAPS( x, s )                                                  \
+    if ( caps.slot_type & (CA_##x) )                                        \
+        p += sprintf( p, "<tr><td>" s "</td></tr>\n" );
+
+    CHECK_CAPS( CI, "CI high level interface" );
+    CHECK_CAPS( CI_LINK, "CI link layer level interface" );
+    CHECK_CAPS( CI_PHYS, "CI physical layer level interface (not supported)" );
+    CHECK_CAPS( DESCR, "built-in descrambler" );
+    CHECK_CAPS( SC, "simple smartcard interface" );
+#undef CHECK_CAPS
+
+    p += sprintf( p, "</table>%d available %s\n<table>", caps.descr_num,
+        caps.descr_num == 1 ? "descrambler (key)" : "descramblers (keys)" );
+#define CHECK_DESC( x )                                                     \
+    if ( caps.descr_type & (CA_##x) )                                       \
+        p += sprintf( p, "<tr><td>" STRINGIFY(x) "</td></tr>\n" );
+
+    CHECK_DESC( ECD );
+    CHECK_DESC( NDS );
+    CHECK_DESC( DSS );
+#undef CHECK_DESC
+
+    p += sprintf( p, "</table>" );
+
+    for ( i_slot = 0; i_slot < p_sys->i_nb_slots; i_slot++ )
+    {
+        ca_slot_info_t sinfo;
+
+        p_sys->pb_slot_mmi_undisplayed[i_slot] = VLC_FALSE;
+        p += sprintf( p, "<p>CA slot #%d: ", i_slot );
+
+        sinfo.num = i_slot;
+        if ( ioctl( p_sys->i_ca_handle, CA_GET_SLOT_INFO, &sinfo ) != 0 )
+        {
+            p += sprintf( p, "ioctl CA_GET_SLOT_INFO failed (%s)<br>\n",
+                          strerror(errno) );
+            continue;
+        }
+
+#define CHECK_TYPE( x, s )                                                  \
+        if ( sinfo.type & (CA_##x) )                                        \
+            p += sprintf( p, s );
+
+        CHECK_TYPE( CI, "high level, " );
+        CHECK_TYPE( CI_LINK, "link layer level, " );
+        CHECK_TYPE( CI_PHYS, "physical layer level, " );
+#undef CHECK_TYPE
+
+        if ( sinfo.flags & CA_CI_MODULE_READY )
+        {
+            en50221_mmi_object_t *p_object = E_(en50221_GetMMIObject)( p_access,
+                                                                       i_slot );
+
+            p += sprintf( p, "module present and ready<p>\n" );
+            p += sprintf( p, "<form action=index.html method=get>\n" );
+            p += sprintf( p, "<input type=hidden name=slot value=\"%d\">\n",
+                          i_slot );
+
+            if ( p_object == NULL )
+            {
+                p += sprintf( p, "<input type=submit name=open value=\"Open session\">\n" );
+            }
+            else
+            {
+                switch ( p_object->i_object_type )
+                {
+                case EN50221_MMI_ENQ:
+                    p += sprintf( p, "<input type=hidden name=type value=enq>\n" );
+                    p += sprintf( p, "<table border=1><tr><th>%s</th></tr>\n",
+                                  p_object->u.enq.psz_text );
+                    if ( p_object->u.enq.b_blind == VLC_FALSE )
+                        p += sprintf( p, "<tr><td><input type=text name=answ></td></tr>\n" );
+                    else
+                        p += sprintf( p, "<tr><td><input type=password name=answ></td></tr>\n" );
+                    break;
+
+                case EN50221_MMI_MENU:
+                    p += sprintf( p, "<input type=hidden name=type value=menu>\n" );
+                    p += sprintf( p, "<table border=1><tr><th>%s</th></tr>\n",
+                                  p_object->u.menu.psz_title );
+                    p += sprintf( p, "<tr><td>%s</td></tr><tr><td>\n",
+                                  p_object->u.menu.psz_subtitle );
+                    for ( i = 0; i < p_object->u.menu.i_choices; i++ )
+                        p += sprintf( p, "<input type=radio name=choice value=\"%d\">%s<br>\n", i + 1, p_object->u.menu.ppsz_choices[i] );
+                    p += sprintf( p, "</td></tr><tr><td>%s</td></tr>\n",
+                                  p_object->u.menu.psz_bottom );
+                    break;
+
+                case EN50221_MMI_LIST:
+                    p += sprintf( p, "<input type=hidden name=type value=menu>\n" );
+                    p += sprintf( p, "<input type=hidden name=choice value=0>\n" );
+                    p += sprintf( p, "<table border=1><tr><th>%s</th></tr>\n",
+                                  p_object->u.menu.psz_title );
+                    p += sprintf( p, "<tr><td>%s</td></tr><tr><td>\n",
+                                  p_object->u.menu.psz_subtitle );
+                    for ( i = 0; i < p_object->u.menu.i_choices; i++ )
+                        p += sprintf( p, "%s<br>\n",
+                                      p_object->u.menu.ppsz_choices[i] );
+                    p += sprintf( p, "</td></tr><tr><td>%s</td></tr>\n",
+                                  p_object->u.menu.psz_bottom );
+                    break;
+
+                default:
+                    p += sprintf( p, "<table><tr><th>Unknown MMI object type</th></tr>\n" );
+                }
+
+                p += sprintf( p, "</table><p><input type=submit name=ok value=\"OK\">\n" );
+                p += sprintf( p, "<input type=submit name=cancel value=\"Cancel\">\n" );
+                p += sprintf( p, "<input type=submit name=close value=\"Close Session\">\n" );
+            }
+            p += sprintf( p, "</form>\n" );
+        }
+        else if ( sinfo.flags & CA_CI_MODULE_PRESENT )
+            p += sprintf( p, "module present, not ready<br>\n" );
+        else
+            p += sprintf( p, "module not present<br>\n" );
+    }
+
+out:
+    vlc_mutex_lock( &p_sys->httpd_mutex );
+    p_sys->b_request_mmi_info = VLC_FALSE;
+    vlc_cond_signal( &p_sys->httpd_cond );
+    vlc_mutex_unlock( &p_sys->httpd_mutex );
+}
+#endif
+
 /*****************************************************************************
  * CAMSet :
  *****************************************************************************/