]> git.sesse.net Git - vlc/blob - plugins/access/http.c
* Backported RTP access module from HEAD.
[vlc] / plugins / access / http.c
1 /*****************************************************************************
2  * http.c: HTTP access plug-in
3  *****************************************************************************
4  * Copyright (C) 2001, 2002 VideoLAN
5  * $Id: http.c,v 1.10.2.4 2002/10/03 22:14:58 massiot Exp $
6  *
7  * Authors: Christophe Massiot <massiot@via.ecp.fr>
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>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <fcntl.h>
33
34 #include <videolan/vlc.h>
35
36 #ifdef HAVE_UNISTD_H
37 #   include <unistd.h>
38 #elif defined( _MSC_VER ) && defined( _WIN32 )
39 #   include <io.h>
40 #endif
41
42 #ifdef WIN32
43 #   include <winsock2.h>
44 #   include <ws2tcpip.h>
45 #   ifndef IN_MULTICAST
46 #       define IN_MULTICAST(a) IN_CLASSD(a)
47 #   endif
48 #else
49 #   include <sys/socket.h>
50 #endif
51
52 #include "stream_control.h"
53 #include "input_ext-intf.h"
54 #include "input_ext-dec.h"
55 #include "input_ext-plugins.h"
56
57 #include "network.h"
58
59 /*****************************************************************************
60  * Local prototypes
61  *****************************************************************************/
62 static void input_getfunctions( function_list_t * );
63 static int  HTTPOpen       ( struct input_thread_s * );
64 static void HTTPClose      ( struct input_thread_s * );
65 static int  HTTPSetProgram ( struct input_thread_s * , pgrm_descriptor_t * );  
66 static void HTTPSeek       ( struct input_thread_s *, off_t );
67
68 /*****************************************************************************
69  * Build configuration tree.
70  *****************************************************************************/
71 MODULE_CONFIG_START
72 MODULE_CONFIG_STOP
73  
74 MODULE_INIT_START
75     SET_DESCRIPTION( _("HTTP access plug-in") )
76     ADD_CAPABILITY( ACCESS, 0 )
77     ADD_SHORTCUT( "http" )
78     ADD_SHORTCUT( "http4" )
79     ADD_SHORTCUT( "http6" )
80 MODULE_INIT_STOP
81  
82 MODULE_ACTIVATE_START
83     input_getfunctions( &p_module->p_functions->access );
84 MODULE_ACTIVATE_STOP
85  
86 MODULE_DEACTIVATE_START
87 MODULE_DEACTIVATE_STOP
88
89 /*****************************************************************************
90  * Functions exported as capabilities. They are declared as static so that
91  * we don't pollute the namespace too much.
92  *****************************************************************************/
93 static void input_getfunctions( function_list_t * p_function_list )
94 {
95 #define input p_function_list->functions.access
96     input.pf_open             = HTTPOpen;
97     input.pf_read             = input_FDNetworkRead;
98     input.pf_close            = HTTPClose;
99     input.pf_set_program      = HTTPSetProgram;
100     input.pf_set_area         = NULL;
101     input.pf_seek             = HTTPSeek;
102 #undef input
103 }
104
105 /*****************************************************************************
106  * _input_socket_t: private access plug-in data, modified to add private
107  *                  fields
108  *****************************************************************************/
109 typedef struct _input_socket_s
110 {
111     input_socket_t      _socket;
112
113     char *              psz_network;
114     network_socket_t    socket_desc;
115     char                psz_buffer[256];
116     char *              psz_name;
117 } _input_socket_t;
118
119 /*****************************************************************************
120  * HTTPConnect: connect to the server and seek to i_tell
121  *****************************************************************************/
122 static int HTTPConnect( input_thread_t * p_input, off_t i_tell )
123 {
124     _input_socket_t *   p_access_data = p_input->p_access_data;
125     struct module_s *   p_network;
126     char                psz_buffer[256];
127     byte_t *            psz_parser;
128     int                 i_returncode, i;
129     char *              psz_return_alpha;
130
131     /* Find an appropriate network module */
132     p_network = module_Need( MODULE_CAPABILITY_NETWORK,
133                              p_access_data->psz_network,
134                              &p_access_data->socket_desc );
135     if( p_network == NULL )
136     {
137         free( p_access_data );
138         return( -1 );
139     }
140     module_Unneed( p_network );
141
142     p_access_data->_socket.i_handle = p_access_data->socket_desc.i_handle;
143
144 #   define HTTP_USERAGENT "User-Agent: " COPYRIGHT_MESSAGE "\r\n"
145 #   define HTTP_END       "\r\n"
146  
147     if ( p_input->stream.b_seekable )
148     {
149          snprintf( psz_buffer, sizeof(psz_buffer),
150                    "%s"
151                    "Range: bytes=%lld-\r\n"
152                    HTTP_USERAGENT HTTP_END,
153                    p_access_data->psz_buffer, i_tell );
154     }
155     else
156     {
157          snprintf( psz_buffer, sizeof(psz_buffer),
158                    "%s"
159                    HTTP_USERAGENT HTTP_END,
160                    p_access_data->psz_buffer );
161     }
162     psz_buffer[sizeof(psz_buffer) - 1] = '\0';
163
164     /* Send GET ... */
165     if( send( p_access_data->_socket.i_handle, psz_buffer,
166                strlen( psz_buffer ), 0 ) == (-1) )
167     {
168         intf_ErrMsg( "http error: cannot send request (%s)", strerror(errno) );
169         input_FDNetworkClose( p_input );
170         return( -1 );
171     }
172
173     /* Prepare the input thread for reading. */ 
174     p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
175     /* FIXME: we shouldn't have to do that ! */
176     p_input->pf_read = input_FDNetworkRead;
177
178     while( !input_FillBuffer( p_input ) )
179     {
180         if( p_input->b_die || p_input->b_error )
181         {
182             input_FDNetworkClose( p_input );
183             return( -1 );
184         }
185     }
186
187     /* Parse HTTP header. */
188 #define MAX_LINE 1024
189     /* get the returncode */
190     if( input_Peek( p_input, &psz_parser, MAX_LINE ) <= 0 )
191     {
192         intf_ErrMsg( "not enough data" );
193         input_FDNetworkClose( p_input );
194         return( -1 );
195     }
196
197     if( !strncmp( psz_parser, "HTTP/1.",
198                   strlen("HTTP/1.") ) )
199     {
200         psz_parser += strlen("HTTP 1.") + 2;
201         i_returncode = atoi( psz_parser );
202         intf_WarnMsg( 3, "HTTP server replied: %i", i_returncode );
203         psz_parser += 4;
204         for ( i = 0; psz_parser[i] != '\r' || psz_parser[i+1] != '\n'; i++ )
205         {
206             ;
207         }
208         psz_return_alpha = malloc( i + 1 );
209         memcpy( psz_return_alpha, psz_parser, i );
210         psz_return_alpha[i] = '\0';
211     }
212     else
213     {
214         intf_ErrMsg( "http error: invalid http reply" );
215         return -1;
216     }
217     
218     if ( i_returncode >= 400 ) /* something is wrong */
219     {
220         intf_ErrMsg( "http error: %i %s", i_returncode, psz_return_alpha );
221         return -1;
222     }
223
224     for( ; ; ) 
225     {
226         if( input_Peek( p_input, &psz_parser, MAX_LINE ) <= 0 )
227         {
228             intf_ErrMsg( "http error: not enough data" );
229             input_FDNetworkClose( p_input );
230             return( -1 );
231         }
232
233         if( psz_parser[0] == '\r' && psz_parser[1] == '\n' )
234         {
235             /* End of header. */
236             p_input->p_current_data += 2;
237             break;
238         }
239
240         if( !strncmp( psz_parser, "Content-Length: ",
241                       strlen("Content-Length: ") ) )
242         {
243             psz_parser += strlen("Content-Length: ");
244             vlc_mutex_lock( &p_input->stream.stream_lock );
245 #ifdef HAVE_ATOLL
246             p_input->stream.p_selected_area->i_size = atoll( psz_parser )
247                                                         + i_tell;
248 #else
249             /* FIXME : this won't work for 64-bit lengths */
250             p_input->stream.p_selected_area->i_size = atoi( psz_parser )
251                                                         + i_tell;
252 #endif
253             vlc_mutex_unlock( &p_input->stream.stream_lock );
254         }
255
256         while( *psz_parser != '\r' && psz_parser < p_input->p_last_data )
257         {
258             psz_parser++;
259         }
260         p_input->p_current_data = psz_parser + 2;
261     }
262
263     if( p_input->stream.p_selected_area->i_size )
264     {
265         vlc_mutex_lock( &p_input->stream.stream_lock );
266         p_input->stream.p_selected_area->i_tell = i_tell
267             + (p_input->p_last_data - p_input->p_current_data);
268         p_input->stream.b_seekable = 1;
269         p_input->stream.b_changed = 1;
270         vlc_mutex_unlock( &p_input->stream.stream_lock );
271     }
272
273     return( 0 );
274 }
275
276 /*****************************************************************************
277  * HTTPOpen: parse URL and open the remote file at the beginning
278  *****************************************************************************/
279 static int HTTPOpen( input_thread_t * p_input )
280 {
281     _input_socket_t *   p_access_data;
282     char *              psz_name = strdup(p_input->psz_name);
283     char *              psz_parser = psz_name;
284     char *              psz_server_addr = "";
285     char *              psz_server_port = "";
286     char *              psz_path = "";
287     char *              psz_proxy;
288     int                 i_server_port = 0;
289
290     p_access_data = p_input->p_access_data = malloc( sizeof(_input_socket_t) );
291     if( p_access_data == NULL )
292     {
293         intf_ErrMsg( "http error: Out of memory" );
294         free(psz_name);
295         return( -1 );
296     }
297
298     p_access_data->psz_name = psz_name;
299     p_access_data->psz_network = "";
300     if( config_GetIntVariable( "ipv4" ) )
301     {
302         p_access_data->psz_network = "ipv4";
303     }
304     if( config_GetIntVariable( "ipv6" ) )
305     {
306         p_access_data->psz_network = "ipv6";
307     }
308     if( *p_input->psz_access )
309     {
310         /* Find out which shortcut was used */
311         if( !strncmp( p_input->psz_access, "http6", 6 ) )
312         {
313             p_access_data->psz_network = "ipv6";
314         }
315         else if( !strncmp( p_input->psz_access, "http4", 6 ) )
316         {
317             p_access_data->psz_network = "ipv4";
318         }
319     }
320
321     /* Parse psz_name syntax :
322      * //<hostname>[:<port>][/<path>] */
323     while( *psz_parser == '/' )
324     {
325         psz_parser++;
326     }
327     psz_server_addr = psz_parser;
328
329     while( *psz_parser && *psz_parser != ':' && *psz_parser != '/' )
330     {
331         psz_parser++;
332     }
333
334     if ( *psz_parser == ':' )
335     {
336         *psz_parser = '\0';
337         psz_parser++;
338         psz_server_port = psz_parser;
339
340         while( *psz_parser && *psz_parser != '/' )
341         {
342             psz_parser++;
343         }
344     }
345
346     if( *psz_parser == '/' )
347     {
348         *psz_parser = '\0';
349         psz_parser++;
350         psz_path = psz_parser;
351     }
352
353     /* Convert port format */
354     if( *psz_server_port )
355     {
356         i_server_port = strtol( psz_server_port, &psz_parser, 10 );
357         if( *psz_parser )
358         {
359             intf_ErrMsg( "input error: cannot parse server port near %s",
360                          psz_parser );
361             free( p_input->p_access_data );
362             free( psz_name );
363             return( -1 );
364         }
365     }
366
367     if( i_server_port == 0 )
368     {
369         i_server_port = 80;
370     }
371
372     if( !*psz_server_addr )
373     {
374         intf_ErrMsg( "input error: no server given" );
375         free( p_input->p_access_data );
376         free( psz_name );
377         return( -1 );
378     }
379
380     /* Check proxy */
381     if( (psz_proxy = getenv( "http_proxy" )) != NULL && *psz_proxy )
382     {
383         /* http://myproxy.mydomain:myport/ */
384         int                 i_proxy_port = 0;
385  
386         /* Skip the protocol name */
387         while( *psz_proxy && *psz_proxy != ':' )
388         {
389             psz_proxy++;
390         }
391  
392         /* Skip the "://" part */
393         while( *psz_proxy && (*psz_proxy == ':' || *psz_proxy == '/') )
394         {
395             psz_proxy++;
396         }
397  
398         /* Found a proxy name */
399         if( *psz_proxy )
400         {
401             char *psz_port = psz_proxy;
402  
403             /* Skip the hostname part */
404             while( *psz_port && *psz_port != ':' && *psz_port != '/' )
405             {
406                 psz_port++;
407             }
408  
409             /* Found a port name */
410             if( *psz_port )
411             {
412                 char * psz_junk;
413  
414                 /* Replace ':' with '\0' */
415                 *psz_port = '\0';
416                 psz_port++;
417  
418                 psz_junk = psz_port;
419                 while( *psz_junk && *psz_junk != '/' )
420                 {
421                     psz_junk++;
422                 }
423  
424                 if( *psz_junk )
425                 {
426                     *psz_junk = '\0';
427                 }
428  
429                 if( *psz_port != '\0' )
430                 {
431                     i_proxy_port = atoi( psz_port );
432                 }
433             }
434         }
435         else
436         {
437             intf_ErrMsg( "input error: http_proxy environment variable is invalid !" );
438             free( p_input->p_access_data );
439             free( psz_name );
440             return( -1 );
441         }
442
443         p_access_data->socket_desc.i_type = NETWORK_TCP;
444         p_access_data->socket_desc.psz_server_addr = psz_proxy;
445         p_access_data->socket_desc.i_server_port = i_proxy_port;
446
447         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
448                   "GET http://%s:%d/%s\r\n HTTP/1.0\r\n",
449                   psz_server_addr, i_server_port, psz_path );
450     }
451     else
452     {
453         /* No proxy, direct connection. */
454         p_access_data->socket_desc.i_type = NETWORK_TCP;
455         p_access_data->socket_desc.psz_server_addr = psz_server_addr;
456         p_access_data->socket_desc.i_server_port = i_server_port;
457
458         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
459                   "GET /%s HTTP/1.1\r\nHost: %s\r\n",
460                   psz_path, psz_server_addr );
461     }
462     p_access_data->psz_buffer[sizeof(p_access_data->psz_buffer) - 1] = '\0';
463
464     intf_WarnMsg( 2, "input: opening server=%s port=%d path=%s",
465                   psz_server_addr, i_server_port, psz_path );
466
467     vlc_mutex_lock( &p_input->stream.stream_lock );
468     p_input->stream.b_pace_control = 1;
469     p_input->stream.b_seekable = 1;
470     p_input->stream.p_selected_area->i_tell = 0;
471     p_input->stream.p_selected_area->i_size = 0;
472     p_input->stream.i_method = INPUT_METHOD_NETWORK;
473     vlc_mutex_unlock( &p_input->stream.stream_lock );
474     p_input->i_mtu = 0;
475  
476     if( HTTPConnect( p_input, 0 ) )
477     {
478         char * psz_pos = strstr(p_access_data->psz_buffer, "HTTP/1.1");
479         p_input->stream.b_seekable = 0;
480         psz_pos[7] = '0';
481         return( HTTPConnect( p_input, 0 ) );
482     }
483     return 0;
484 }
485
486 /*****************************************************************************
487  * HTTPClose: free unused data structures
488  *****************************************************************************/
489 static void HTTPClose( input_thread_t * p_input )
490 {
491     input_FDNetworkClose( p_input );
492 }
493
494 /*****************************************************************************
495  * HTTPSetProgram: do nothing
496  *****************************************************************************/
497 static int HTTPSetProgram( input_thread_t * p_input,
498                            pgrm_descriptor_t * p_program )
499 {
500     return( 0 );
501 }
502
503 /*****************************************************************************
504  * HTTPSeek: close and re-open a connection at the right place
505  *****************************************************************************/
506 static void HTTPSeek( input_thread_t * p_input, off_t i_pos )
507 {
508     _input_socket_t *   p_access_data = p_input->p_access_data;
509     close( p_access_data->_socket.i_handle );
510     intf_WarnMsg( 2, "http: seeking to position %lld", i_pos );
511     HTTPConnect( p_input, i_pos );
512 }
513