f5fa8d064ec0cbcf88a58a63f36c65c34be0371c
[vlc.git] / modules / control / http.c
1 /*****************************************************************************
2  * http.c :  http remote control plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: http.c,v 1.10 2003/07/10 18:29:41 zorglub Exp $
6  *
7  * Authors: Gildas Bazin <gbazin@netcourrier.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  * 
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <stdlib.h>                                      /* malloc(), free() */
28 #include <string.h>
29
30 #include <errno.h>                                                 /* ENOMEM */
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <signal.h>
34
35 #include <vlc/vlc.h>
36 #include <vlc/intf.h>
37
38 #ifdef HAVE_UNISTD_H
39 #    include <unistd.h>
40 #endif
41
42 #ifdef HAVE_SYS_TIME_H
43 #    include <sys/time.h>
44 #endif
45 #include <sys/types.h>
46
47 #include "httpd.h"
48
49 /*****************************************************************************
50  * Local prototypes
51  *****************************************************************************/
52 static int  Activate     ( vlc_object_t * );
53 static void Close        ( vlc_object_t * );
54 static void Run          ( intf_thread_t *p_intf );
55
56 static int  httpd_page_interface_get( httpd_file_callback_args_t *p_args,
57                                       uint8_t *p_request, int i_request,
58                                       uint8_t **pp_data, int *pi_data );
59
60 /*****************************************************************************
61  * intf_sys_t: description and status of interface
62  *****************************************************************************/
63 struct intf_sys_t
64 {
65     httpd_t             *p_httpd;
66     httpd_host_t        *p_httpd_host;
67
68     input_thread_t *    p_input;
69 };
70
71 /*****************************************************************************
72  * Module descriptor
73  *****************************************************************************/
74 #define PORT_TEXT N_( "HTTP interface bind port" )
75 #define PORT_LONGTEXT N_( \
76     "You can set the port on which the http interface will accept connections" )
77 #define ADDR_TEXT N_( "HTTP interface bind address" )
78 #define ADDR_LONGTEXT N_( \
79     "You can set the address on which the http interface will bind" )
80 #define USER_TEXT N_( "Username" )
81 #define USER_LONGTEXT N_( \
82     "You can set the username that will be allowed to connect \
83     If you do not set it, everyone is allowed without password" )
84 #define PASS_TEXT N_( "Password" )
85 #define PASS_LONGTEXT N_( \
86     "You can set the password that corresponds to the username \
87     If you do not set it, the password is left blank" )
88
89
90 vlc_module_begin();
91     add_category_hint( N_("HTTP remote control"), NULL, VLC_TRUE );
92     add_string( "http-addr", NULL, NULL, ADDR_TEXT, ADDR_LONGTEXT, VLC_TRUE );
93     add_integer( "http-port", 8080, NULL, PORT_TEXT, PORT_LONGTEXT, VLC_TRUE );
94     add_string( "http-user", NULL, NULL, USER_TEXT, USER_LONGTEXT, VLC_TRUE );
95     add_string( "http-pass", NULL, NULL, PASS_TEXT, PASS_LONGTEXT, VLC_TRUE );
96     set_description( _("HTTP remote control interface") );
97     set_capability( "interface", 10 );
98     set_callbacks( Activate, Close );
99 vlc_module_end();
100
101 /*****************************************************************************
102  * Activate: initialize and create stuff
103  *****************************************************************************/
104 static int Activate( vlc_object_t *p_this )
105 {
106     intf_thread_t *p_intf = (intf_thread_t*)p_this;
107
108     /* Allocate instance and initialize some members */
109     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
110     if( p_intf->p_sys == NULL )
111     {
112         return VLC_EGENERIC;
113     };
114
115     p_intf->p_sys->p_httpd = NULL;
116     p_intf->p_sys->p_httpd_host = NULL;
117
118     p_intf->pf_run = Run;
119
120     CONSOLE_INTRO_MSG;
121
122     return VLC_SUCCESS;
123 }
124
125 /*****************************************************************************
126  * CloseIntf: destroy interface
127  *****************************************************************************/
128 void Close ( vlc_object_t *p_this )
129 {
130     intf_thread_t *p_intf = (intf_thread_t *)p_this;
131
132     if( p_intf->p_sys->p_httpd_host )
133         p_intf->p_sys->p_httpd->pf_unregister_host( p_intf->p_sys->p_httpd,
134                                                  p_intf->p_sys->p_httpd_host );
135
136     if( p_intf->p_sys->p_httpd )
137         httpd_Release( p_intf->p_sys->p_httpd );
138
139     /* Destroy structure */
140     free( p_intf->p_sys );
141 }
142
143 /*****************************************************************************
144  * Run: http interface thread
145  *****************************************************************************/
146 static void Run( intf_thread_t *p_intf )
147 {
148     input_thread_t *p_input = NULL;
149     playlist_t     *p_playlist = NULL;
150     httpd_file_t   *p_page_intf;
151
152 #if 0
153     input_info_category_t * p_category;
154     input_info_t * p_info;
155
156     off_t      i_oldpos = 0;
157     off_t      i_newpos;
158     double     f_ratio = 1.0;
159 #endif
160
161     /* Get bind address and port */
162     char *psz_bind_addr = config_GetPsz( p_intf, "http-addr" );
163     int i_bind_port = config_GetInt( p_intf, "http-port" );
164
165     char *psz_user = config_GetPsz( p_intf, "http-user" );
166     char *psz_pass = config_GetPsz( p_intf, "http-pass" );
167     
168     if( !psz_bind_addr ) psz_bind_addr = strdup( "" );
169
170     p_intf->p_sys->p_httpd = httpd_Find( VLC_OBJECT(p_intf), VLC_TRUE );
171     if( !p_intf->p_sys->p_httpd )
172     {
173         msg_Err( p_intf, "cannot start httpd daemon" );
174         free( p_intf->p_sys );
175         return;
176     }
177
178     p_intf->p_sys->p_httpd_host =
179         p_intf->p_sys->p_httpd->pf_register_host( p_intf->p_sys->p_httpd,
180                                                   psz_bind_addr, i_bind_port );
181     
182     if( !p_intf->p_sys->p_httpd_host )
183     {
184         msg_Err( p_intf, "cannot listen on %s:%d", psz_bind_addr, i_bind_port);
185         httpd_Release( p_intf->p_sys->p_httpd );
186         free( p_intf->p_sys );
187         return;
188     }
189
190     msg_Info( p_intf, "http interface started on %s:%d", 
191         *psz_bind_addr ? psz_bind_addr : "localhost" , i_bind_port );
192
193     /*
194      * Register our interface page with the httpd daemon
195      */
196      if( !psz_user )
197      {
198          psz_pass = NULL; /* No password if no user given */
199      }
200    
201     if( psz_user && !psz_pass)
202     {
203         psz_pass="";
204     } 
205     
206     p_page_intf = p_intf->p_sys->p_httpd->pf_register_file(
207                       p_intf->p_sys->p_httpd, "/", "text/html",
208                       psz_user, psz_pass, httpd_page_interface_get,
209                       httpd_page_interface_get,
210                       (httpd_file_callback_args_t*)p_intf );
211
212     while( !p_intf->b_die )
213     {
214
215         /* Manage the input part */
216         if( p_input == NULL )
217         {
218             if( p_playlist )
219             {
220                 p_input = vlc_object_find( p_playlist, VLC_OBJECT_INPUT,
221                                                        FIND_CHILD );
222             }
223             else
224             {
225                 p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
226                                                    FIND_ANYWHERE );
227                 if( p_input )
228                 {
229                     p_playlist = vlc_object_find( p_input, VLC_OBJECT_PLAYLIST,
230                                                            FIND_PARENT );
231                 }
232             }
233         }
234         else if( p_input->b_dead )
235         {
236             vlc_object_release( p_input );
237             p_input = NULL;
238         }
239
240         if( p_input )
241         {
242             /* Get position */
243             vlc_mutex_lock( &p_input->stream.stream_lock );
244             if( !p_input->b_die && p_input->stream.i_mux_rate )
245             {
246 #if 0
247 #define A p_input->stream.p_selected_area
248                 f_ratio = 1.0 / ( 50 * p_input->stream.i_mux_rate );
249                 i_newpos = A->i_tell * f_ratio;
250
251                 if( i_oldpos != i_newpos )
252                 {
253                     i_oldpos = i_newpos;
254                     printf( "pos: %li s / %li s\n", (long int)i_newpos,
255                             (long int)(f_ratio * A->i_size) );
256                 }
257 #undef S
258 #endif
259             }
260             vlc_mutex_unlock( &p_input->stream.stream_lock );
261         }
262
263         /* Wait a bit */
264         msleep( INTF_IDLE_SLEEP );
265     }
266
267     p_intf->p_sys->p_httpd->pf_unregister_file( p_intf->p_sys->p_httpd,
268                                                 p_page_intf );
269
270     if( p_input )
271     {
272         vlc_object_release( p_input );
273         p_input = NULL;
274     }
275
276     if( p_playlist )
277     {
278         vlc_object_release( p_playlist );
279         p_playlist = NULL;
280     }
281 }
282
283 /*****************************************************************************
284  * Local functions
285  *****************************************************************************/
286 static int httpd_page_interface_update( intf_thread_t *p_intf,
287                                         playlist_t *p_playlist,
288                                         uint8_t **pp_data, int *pi_data, vlc_bool_t b_redirect );
289
290 static void uri_extract_value( char *psz_uri, char *psz_name,
291                                char *psz_value, int i_value_max )
292 {
293     char *p;
294
295     p = strstr( psz_uri, psz_name );
296     if( p )
297     {
298         int i_len;
299
300         p += strlen( psz_name );
301         if( *p == '=' ) p++;
302
303         if( strchr( p, '&' ) )
304         {
305             i_len = strchr( p, '&' ) - p;
306         }
307         else
308         {
309             /* for POST method */
310             if( strchr( p, '\n' ) )
311             {
312                 i_len = strchr( p, '\n' ) - p;
313                 if( i_len && *(p+i_len-1) == '\r' ) i_len--;
314             }
315             else
316             {
317                 i_len = strlen( p );
318             }
319         }
320         i_len = __MIN( i_value_max - 1, i_len );
321         if( i_len > 0 )
322         {
323             strncpy( psz_value, p, i_len );
324             psz_value[i_len] = '\0';
325         }
326         else
327         {
328             strncpy( psz_value, "", i_value_max );
329         }
330     }
331     else
332     {
333         strncpy( psz_value, "", i_value_max );
334     }
335 }
336
337 static void uri_decode_url_encoded( char *psz )
338 {
339     char *dup = strdup( psz );
340     char *p = dup;
341
342     while( *p )
343     {
344         if( *p == '%' )
345         {
346             char val[3];
347             p++;
348             if( !*p )
349             {
350                 break;
351             }
352
353             val[0] = *p++;
354             val[1] = *p++;
355             val[2] = '\0';
356
357             *psz++ = strtol( val, NULL, 16 );
358         }
359         else if( *p == '+' )
360         {
361             *psz++ = ' ';
362             p++;
363         }
364         else
365         {
366             *psz++ = *p++;
367         }
368     }
369     *psz++  ='\0';
370     free( dup );
371 }
372
373 static int httpd_page_interface_get( httpd_file_callback_args_t *p_args,
374                                      uint8_t *p_request, int i_request,
375                                      uint8_t **pp_data, int *pi_data )
376 {
377     intf_thread_t *p_intf = (intf_thread_t *)p_args;
378     playlist_t *p_playlist;
379     int i_ret;
380
381     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
382     if( !p_playlist )
383     {
384         return VLC_EGENERIC;
385     }
386
387     if( i_request > 0)
388     {
389         char action[512];
390
391         uri_extract_value( p_request, "action", action, 512 );
392
393         if( !strcmp( action, "play" ) )
394         {
395             int i_item;
396             uri_extract_value( p_request, "item", action, 512 );
397             i_item = atol( action );
398             playlist_Command( p_playlist, PLAYLIST_GOTO, i_item );
399             msg_Dbg( p_intf, "requested playlist item: %i", i_item );
400         }
401         else if( !strcmp( action, "stop" ) )
402         {
403             playlist_Command( p_playlist, PLAYLIST_STOP, 0 );
404             msg_Dbg( p_intf, "requested playlist stop" );
405         }
406         else if( !strcmp( action, "pause" ) )
407         {
408             playlist_Command( p_playlist, PLAYLIST_PAUSE, 0 );
409             msg_Dbg( p_intf, "requested playlist pause" );
410         }
411         else if( !strcmp( action, "next" ) )
412         {
413             playlist_Command( p_playlist, PLAYLIST_GOTO,
414                               p_playlist->i_index + 1 );
415             msg_Dbg( p_intf, "requested playlist next" );
416         }
417         else if( !strcmp( action, "previous" ) )
418         {
419             playlist_Command( p_playlist, PLAYLIST_GOTO,
420                               p_playlist->i_index - 1 );
421             msg_Dbg( p_intf, "requested playlist previous" );
422         }
423         else if( !strcmp( action, "add" ) )
424         {
425             uri_extract_value( p_request, "mrl", action, 512 );
426             uri_decode_url_encoded( action );
427             playlist_Add( p_playlist, action,
428                           PLAYLIST_APPEND, PLAYLIST_END );
429             msg_Dbg( p_intf, "requested playlist add: %s", action );
430         }
431     }
432
433     i_ret = httpd_page_interface_update( p_intf, p_playlist, pp_data, pi_data, i_request ? VLC_TRUE : VLC_FALSE );
434
435     vlc_object_release( p_playlist );
436
437     return i_ret;
438 }
439
440 static int httpd_page_interface_update( intf_thread_t *p_intf,
441                                         playlist_t *p_playlist,
442                                         uint8_t **pp_data, int *pi_data, vlc_bool_t b_redirect )
443 {
444     int i, i_size = 0;
445     char *p;
446
447     vlc_mutex_lock( &p_playlist->object_lock );
448
449     /*
450      * Count playlist items for memory allocation
451      */
452     for ( i = 0; i < p_playlist->i_size; i++ )
453     {
454         i_size += sizeof("<a href=?action=play&item=?>? - </a><br />\n" );
455         i_size += strlen( p_playlist->pp_items[i]->psz_name );
456     }
457     /* add something for all the static strings below */
458     i_size += 8192;
459
460     p = *pp_data = malloc( i_size );
461     p += sprintf( p, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" );
462     p += sprintf( p, "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" );
463     p += sprintf( p, "<head>\n" );
464     p += sprintf( p, "<title>VLC Media Player</title>\n" );
465     if( b_redirect )
466     {
467         p += sprintf( p, "<meta http-equiv=\"refresh\" content=\"0;URL=/\"/>\n" );
468     }
469     p += sprintf( p, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"/>\n" );
470     /* p += sprintf( p, "<link rel=\"shortcut icon\" href=\"http://www.videolan.org/favicon.ico\">\n" ); */
471     p += sprintf( p, "</head>\n" );
472     p += sprintf( p, "<body>\n" );
473     p += sprintf( p, "<h2 style=\"align: center;\"><a href=\"http://www.videolan.org\">"
474                      "VLC Media Player</a> (http interface)</h2>\n" );
475
476     /*
477      * Display the controls
478      */
479     p += sprintf( p, "<hr/>\n" );
480
481     p += sprintf( p, "<form method=\"get\" action=\"/\">"
482         "<p><input type=\"submit\" name=\"action\" value=\"stop\" />"
483         "<input type=\"submit\" name=\"action\" value=\"pause\" />"
484         "<input type=\"submit\" name=\"action\" value=\"previous\" />"
485         "<input type=\"submit\" name=\"action\" value=\"next\" /></p>"
486         "</form>\n" );
487
488     p += sprintf( p, "<form method=\"get\" action=\"/\" "
489                      "enctype=\"text/plain\" >"
490         "<p>Media Resource Locator:<br/>"
491         "<input type=\"text\" name=\"mrl\" size=\"40\" />"
492         "<input type=\"submit\" name=\"action\" value=\"add\" /></p>"
493         "</form>\n" );
494
495     p += sprintf( p, "<hr/>\n" );
496
497     /*
498      * Display the playlist items
499      */
500     if ( p_playlist->i_size )
501     {
502         p += sprintf( p, "<ol>\n" );
503         for ( i = 0; i < p_playlist->i_size; i++ )
504         {
505             if( i == p_playlist->i_index ) p += sprintf( p, "<b>" );
506             
507             p += sprintf( p, "<li><a href=\"?action=play&amp;item=%i\">", i );
508             p += sprintf( p, "%s", p_playlist->pp_items[i]->psz_name );
509             p += sprintf( p, "</a></li>" );
510             
511             if( i == p_playlist->i_index ) p += sprintf( p, "</b>" );
512             p += sprintf( p, "\n" );
513         }
514         p += sprintf( p, "</ol>\n" );
515     }
516     else
517     {
518         p += sprintf( p, "<p>no entries</p>\n" );
519     }
520
521     p += sprintf( p, "</body>\n" );
522     p += sprintf( p, "</html>\n" );
523
524     *pi_data = strlen( *pp_data );
525
526     vlc_mutex_unlock( &p_playlist->object_lock );
527
528     return VLC_SUCCESS;
529 }