]> git.sesse.net Git - vlc/blob - plugins/access/http.c
520082ae225171d62ff71004248acb7aa79fec27
[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 2002/05/22 23:11:00 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
129     /* Find an appropriate network module */
130     p_network = module_Need( MODULE_CAPABILITY_NETWORK,
131                              p_access_data->psz_network,
132                              &p_access_data->socket_desc );
133     if( p_network == NULL )
134     {
135         free( p_access_data );
136         return( -1 );
137     }
138     module_Unneed( p_network );
139
140     p_access_data->_socket.i_handle = p_access_data->socket_desc.i_handle;
141
142 #   define HTTP_USERAGENT "User-Agent: " COPYRIGHT_MESSAGE "\r\n"
143 #   define HTTP_END       "\r\n"
144  
145     snprintf( psz_buffer, sizeof(psz_buffer),
146               "%s"
147               "Range: bytes=%lld-\r\n"
148               HTTP_USERAGENT HTTP_END,
149               p_access_data->psz_buffer, i_tell );
150     psz_buffer[sizeof(psz_buffer) - 1] = '\0';
151
152     /* Send GET ... */
153     if( send( p_access_data->_socket.i_handle, psz_buffer,
154                strlen( psz_buffer ), 0 ) == (-1) )
155     {
156         intf_ErrMsg( "http error: cannot send request (%s)", strerror(errno) );
157         input_FDNetworkClose( p_input );
158         return( -1 );
159     }
160
161     /* Prepare the input thread for reading. */ 
162     p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
163     /* FIXME: we shouldn't have to do that ! */
164     p_input->pf_read = input_FDNetworkRead;
165
166     while( !input_FillBuffer( p_input ) )
167     {
168         if( p_input->b_die || p_input->b_error )
169         {
170             input_FDNetworkClose( p_input );
171             return( -1 );
172         }
173     }
174
175     /* Parse HTTP header. */
176 #define MAX_LINE 1024
177     for( ; ; ) 
178     {
179         if( input_Peek( p_input, &psz_parser, MAX_LINE ) <= 0 )
180         {
181             intf_ErrMsg( "http error: not enough data" );
182             input_FDNetworkClose( p_input );
183             return( -1 );
184         }
185
186         if( psz_parser[0] == '\r' && psz_parser[1] == '\n' )
187         {
188             /* End of header. */
189             p_input->p_current_data += 2;
190             break;
191         }
192
193         if( !strncmp( psz_parser, "Content-Length: ",
194                       strlen("Content-Length: ") ) )
195         {
196             psz_parser += strlen("Content-Length: ");
197             /* FIXME : this won't work for 64-bit lengths */
198             vlc_mutex_lock( &p_input->stream.stream_lock );
199             p_input->stream.p_selected_area->i_size = atoi( psz_parser )
200                                                         + i_tell;
201             vlc_mutex_unlock( &p_input->stream.stream_lock );
202         }
203
204         while( *psz_parser != '\r' && psz_parser < p_input->p_last_data )
205         {
206             psz_parser++;
207         }
208         p_input->p_current_data = psz_parser + 2;
209     }
210
211     if( p_input->stream.p_selected_area->i_size )
212     {
213         vlc_mutex_lock( &p_input->stream.stream_lock );
214         p_input->stream.p_selected_area->i_tell = i_tell
215             + (p_input->p_last_data - p_input->p_current_data);
216         p_input->stream.b_seekable = 1;
217         p_input->stream.b_changed = 1;
218         vlc_mutex_unlock( &p_input->stream.stream_lock );
219     }
220
221     return( 0 );
222 }
223
224 /*****************************************************************************
225  * HTTPOpen: parse URL and open the remote file at the beginning
226  *****************************************************************************/
227 static int HTTPOpen( input_thread_t * p_input )
228 {
229     _input_socket_t *   p_access_data;
230     char *              psz_name = strdup(p_input->psz_name);
231     char *              psz_parser = psz_name;
232     char *              psz_server_addr = "";
233     char *              psz_server_port = "";
234     char *              psz_path = "";
235     char *              psz_proxy;
236     int                 i_server_port = 0;
237
238     p_access_data = p_input->p_access_data = malloc( sizeof(_input_socket_t) );
239     if( p_access_data == NULL )
240     {
241         intf_ErrMsg( "http error: Out of memory" );
242         free(psz_name);
243         return( -1 );
244     }
245
246     p_access_data->psz_name = psz_name;
247     p_access_data->psz_network = "";
248     if( config_GetIntVariable( "ipv4" ) )
249     {
250         p_access_data->psz_network = "ipv4";
251     }
252     if( config_GetIntVariable( "ipv6" ) )
253     {
254         p_access_data->psz_network = "ipv6";
255     }
256     if( *p_input->psz_access )
257     {
258         /* Find out which shortcut was used */
259         if( !strncmp( p_input->psz_access, "http6", 6 ) )
260         {
261             p_access_data->psz_network = "ipv6";
262         }
263         else if( !strncmp( p_input->psz_access, "http4", 6 ) )
264         {
265             p_access_data->psz_network = "ipv4";
266         }
267     }
268
269     /* Parse psz_name syntax :
270      * //<hostname>[:<port>][/<path>] */
271     while( *psz_parser == '/' )
272     {
273         psz_parser++;
274     }
275     psz_server_addr = psz_parser;
276
277     while( *psz_parser && *psz_parser != ':' && *psz_parser != '/' )
278     {
279         psz_parser++;
280     }
281
282     if ( *psz_parser == ':' )
283     {
284         *psz_parser = '\0';
285         psz_parser++;
286         psz_server_port = psz_parser;
287
288         while( *psz_parser && *psz_parser != '/' )
289         {
290             psz_parser++;
291         }
292     }
293
294     if( *psz_parser == '/' )
295     {
296         *psz_parser = '\0';
297         psz_parser++;
298         psz_path = psz_parser;
299     }
300
301     /* Convert port format */
302     if( *psz_server_port )
303     {
304         i_server_port = strtol( psz_server_port, &psz_parser, 10 );
305         if( *psz_parser )
306         {
307             intf_ErrMsg( "input error: cannot parse server port near %s",
308                          psz_parser );
309             free( p_input->p_access_data );
310             free( psz_name );
311             return( -1 );
312         }
313     }
314
315     if( i_server_port == 0 )
316     {
317         i_server_port = 80;
318     }
319
320     if( !*psz_server_addr )
321     {
322         intf_ErrMsg( "input error: no server given" );
323         free( p_input->p_access_data );
324         free( psz_name );
325         return( -1 );
326     }
327
328     /* Check proxy */
329     if( (psz_proxy = getenv( "http_proxy" )) != NULL && *psz_proxy )
330     {
331         /* http://myproxy.mydomain:myport/ */
332         int                 i_proxy_port = 0;
333  
334         /* Skip the protocol name */
335         while( *psz_proxy && *psz_proxy != ':' )
336         {
337             psz_proxy++;
338         }
339  
340         /* Skip the "://" part */
341         while( *psz_proxy && (*psz_proxy == ':' || *psz_proxy == '/') )
342         {
343             psz_proxy++;
344         }
345  
346         /* Found a proxy name */
347         if( *psz_proxy )
348         {
349             char *psz_port = psz_proxy;
350  
351             /* Skip the hostname part */
352             while( *psz_port && *psz_port != ':' && *psz_port != '/' )
353             {
354                 psz_port++;
355             }
356  
357             /* Found a port name */
358             if( *psz_port )
359             {
360                 char * psz_junk;
361  
362                 /* Replace ':' with '\0' */
363                 *psz_port = '\0';
364                 psz_port++;
365  
366                 psz_junk = psz_port;
367                 while( *psz_junk && *psz_junk != '/' )
368                 {
369                     psz_junk++;
370                 }
371  
372                 if( *psz_junk )
373                 {
374                     *psz_junk = '\0';
375                 }
376  
377                 if( *psz_port != '\0' )
378                 {
379                     i_proxy_port = atoi( psz_port );
380                 }
381             }
382         }
383         else
384         {
385             intf_ErrMsg( "input error: http_proxy environment variable is invalid !" );
386             free( p_input->p_access_data );
387             free( psz_name );
388             return( -1 );
389         }
390
391         p_access_data->socket_desc.i_type = NETWORK_TCP;
392         p_access_data->socket_desc.psz_server_addr = psz_proxy;
393         p_access_data->socket_desc.i_server_port = i_proxy_port;
394
395         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
396                   "GET http://%s:%d/%s HTTP/1.1\r\n",
397                   psz_server_addr, i_server_port, psz_path );
398     }
399     else
400     {
401         /* No proxy, direct connection. */
402         p_access_data->socket_desc.i_type = NETWORK_TCP;
403         p_access_data->socket_desc.psz_server_addr = psz_server_addr;
404         p_access_data->socket_desc.i_server_port = i_server_port;
405
406         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
407                   "GET /%s HTTP/1.1\r\nHost: %s\r\n",
408                   psz_path, psz_server_addr );
409     }
410     p_access_data->psz_buffer[sizeof(p_access_data->psz_buffer) - 1] = '\0';
411
412     intf_WarnMsg( 2, "input: opening server=%s port=%d path=%s",
413                   psz_server_addr, i_server_port, psz_path );
414
415     vlc_mutex_lock( &p_input->stream.stream_lock );
416     p_input->stream.b_pace_control = 1;
417     p_input->stream.b_seekable = 0;
418     p_input->stream.p_selected_area->i_tell = 0;
419     p_input->stream.p_selected_area->i_size = 0;
420     p_input->stream.i_method = INPUT_METHOD_NETWORK;
421     vlc_mutex_unlock( &p_input->stream.stream_lock );
422     p_input->i_mtu = 0;
423  
424     return( HTTPConnect( p_input, 0 ) );
425 }
426
427 /*****************************************************************************
428  * HTTPClose: free unused data structures
429  *****************************************************************************/
430 static void HTTPClose( input_thread_t * p_input )
431 {
432     input_FDNetworkClose( p_input );
433 }
434
435 /*****************************************************************************
436  * HTTPSetProgram: do nothing
437  *****************************************************************************/
438 static int HTTPSetProgram( input_thread_t * p_input,
439                            pgrm_descriptor_t * p_program )
440 {
441     return( 0 );
442 }
443
444 /*****************************************************************************
445  * HTTPSeek: close and re-open a connection at the right place
446  *****************************************************************************/
447 static void HTTPSeek( input_thread_t * p_input, off_t i_pos )
448 {
449     _input_socket_t *   p_access_data = p_input->p_access_data;
450     close( p_access_data->_socket.i_handle );
451     intf_WarnMsg( 2, "http: seeking to position %lld", i_pos );
452     HTTPConnect( p_input, i_pos );
453 }
454