]> git.sesse.net Git - vlc/blob - plugins/access/http.c
befd1b240e6d2af66ad822927765d6189c60da2b
[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.1 2002/03/01 00:33:18 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 #include "stream_control.h"
43 #include "input_ext-intf.h"
44 #include "input_ext-dec.h"
45 #include "input_ext-plugins.h"
46
47 #include "network.h"
48
49 /*****************************************************************************
50  * Local prototypes
51  *****************************************************************************/
52 static void input_getfunctions( function_list_t * );
53 static int  HTTPOpen       ( struct input_thread_s * );
54 static int  HTTPSetProgram ( struct input_thread_s * , pgrm_descriptor_t * );  
55 static void HTTPSeek       ( struct input_thread_s *, off_t );
56
57 /*****************************************************************************
58  * Build configuration tree.
59  *****************************************************************************/
60 MODULE_CONFIG_START
61 MODULE_CONFIG_STOP
62  
63 MODULE_INIT_START
64     SET_DESCRIPTION( "HTTP access plug-in" )
65     ADD_CAPABILITY( ACCESS, 0 )
66     ADD_SHORTCUT( "http" )
67     ADD_SHORTCUT( "http4" )
68     ADD_SHORTCUT( "http6" )
69 MODULE_INIT_STOP
70  
71 MODULE_ACTIVATE_START
72     input_getfunctions( &p_module->p_functions->access );
73 MODULE_ACTIVATE_STOP
74  
75 MODULE_DEACTIVATE_START
76 MODULE_DEACTIVATE_STOP
77
78 /*****************************************************************************
79  * Functions exported as capabilities. They are declared as static so that
80  * we don't pollute the namespace too much.
81  *****************************************************************************/
82 static void input_getfunctions( function_list_t * p_function_list )
83 {
84 #define input p_function_list->functions.access
85     input.pf_open             = HTTPOpen;
86     input.pf_read             = input_FDNetworkRead;
87     input.pf_close            = input_FDClose;
88     input.pf_set_program      = HTTPSetProgram;
89     input.pf_set_area         = NULL;
90     input.pf_seek             = HTTPSeek;
91 #undef input
92 }
93
94 /*****************************************************************************
95  * _input_socket_t: private access plug-in data, modified to add private
96  *                  fields
97  *****************************************************************************/
98 typedef struct _input_socket_s
99 {
100     input_socket_t      _socket;
101
102     char *              psz_network;
103     network_socket_t    socket_desc;
104     char                psz_buffer[256];
105 } _input_socket_t;
106
107 /*****************************************************************************
108  * HTTPConnect: connect to the server and seek to i_tell
109  *****************************************************************************/
110 static int HTTPConnect( input_thread_t * p_input, off_t i_tell )
111 {
112     _input_socket_t *   p_access_data = p_input->p_access_data;
113     struct module_s *   p_network;
114     char                psz_buffer[256];
115     byte_t *            psz_parser;
116
117     /* Find an appropriate network module */
118     p_network = module_Need( MODULE_CAPABILITY_NETWORK,
119                              p_access_data->psz_network,
120                              &p_access_data->socket_desc );
121     if( p_network == NULL )
122     {
123         free( p_access_data );
124         return( -1 );
125     }
126     module_Unneed( p_network );
127
128     p_access_data->_socket.i_handle = p_access_data->socket_desc.i_handle;
129
130 #   define HTTP_USERAGENT "User-Agent: " COPYRIGHT_MESSAGE "\r\n"
131 #   define HTTP_END       "\r\n"
132  
133     snprintf( psz_buffer, sizeof(psz_buffer),
134               "%s"
135               "Range: bytes=%lld-\r\n"
136               HTTP_USERAGENT HTTP_END,
137               p_access_data->psz_buffer, i_tell );
138     psz_buffer[sizeof(psz_buffer) - 1] = '\0';
139
140     /* Send GET ... */
141     if( write( p_access_data->_socket.i_handle, psz_buffer,
142                strlen( psz_buffer ) ) == (-1) )
143     {
144         intf_ErrMsg( "http error: cannot send request (%s)", strerror(errno) );
145         input_FDClose( p_input );
146         return( -1 );
147     }
148
149     /* Prepare the input thread for reading. */ 
150     p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
151     /* FIXME: we shouldn't have to do that ! */
152     p_input->pf_read = input_FDRead;
153
154     while( !input_FillBuffer( p_input ) )
155     {
156         if( p_input->b_die || p_input->b_error )
157         {
158             input_FDClose( p_input );
159             return( -1 );
160         }
161     }
162
163     /* Parse HTTP header. */
164 #define MAX_LINE 1024
165     for( ; ; ) 
166     {
167         if( input_Peek( p_input, &psz_parser, MAX_LINE ) <= 0 )
168         {
169             intf_ErrMsg( "http error: not enough data" );
170             input_FDClose( p_input );
171             return( -1 );
172         }
173
174         if( psz_parser[0] == '\r' && psz_parser[1] == '\n' )
175         {
176             /* End of header. */
177             p_input->p_current_data += 2;
178             break;
179         }
180
181         if( !strncmp( psz_parser, "Content-Length: ",
182                       strlen("Content-Length: ") ) )
183         {
184             psz_parser += strlen("Content-Length: ");
185             /* FIXME : this won't work for 64-bit lengths */
186             vlc_mutex_lock( &p_input->stream.stream_lock );
187             p_input->stream.p_selected_area->i_size = atoi( psz_parser )
188                                                         + i_tell;
189             vlc_mutex_unlock( &p_input->stream.stream_lock );
190         }
191
192         while( *psz_parser != '\r' && psz_parser < p_input->p_last_data )
193         {
194             psz_parser++;
195         }
196         p_input->p_current_data = psz_parser + 2;
197     }
198
199     if( p_input->stream.p_selected_area->i_size )
200     {
201         vlc_mutex_lock( &p_input->stream.stream_lock );
202         p_input->stream.p_selected_area->i_tell = i_tell
203             + (p_input->p_last_data - p_input->p_current_data);
204         p_input->stream.b_seekable = 1;
205         p_input->stream.b_changed = 1;
206         vlc_mutex_unlock( &p_input->stream.stream_lock );
207     }
208
209     return( 0 );
210 }
211
212 /*****************************************************************************
213  * HTTPOpen: parse URL and open the remote file at the beginning
214  *****************************************************************************/
215 static int HTTPOpen( input_thread_t * p_input )
216 {
217     _input_socket_t *   p_access_data;
218     char *              psz_parser = p_input->psz_name;
219     char *              psz_server_addr = NULL;
220     char *              psz_server_port = NULL;
221     char *              psz_path = NULL;
222     char *              psz_proxy;
223     int                 i_server_port = 0;
224
225     p_access_data = p_input->p_access_data = malloc( sizeof(_input_socket_t) );
226     if( p_access_data == NULL )
227     {
228         intf_ErrMsg( "http error: Out of memory" );
229         return( -1 );
230     }
231
232     p_access_data->psz_network = NULL;
233     if( p_input->psz_access != NULL )
234     {
235         /* Find out which shortcut was used */
236         if( !strncmp( p_input->psz_access, "http6", 5 ) )
237         {
238             p_access_data->psz_network = "ipv6";
239         }
240         else if( !strncmp( p_input->psz_access, "http4", 5 ) )
241         {
242             p_access_data->psz_network = "ipv4";
243         }
244     }
245
246     /* Parse psz_name syntax :
247      * //<hostname>[:<port>][/<path>] */
248     while( *psz_parser == '/' )
249     {
250         psz_parser++;
251     }
252     psz_server_addr = psz_parser;
253
254     while( *psz_parser && *psz_parser != ':' && *psz_parser != '/' )
255     {
256         psz_parser++;
257     }
258
259     if ( *psz_parser == ':' )
260     {
261         *psz_parser = '\0';
262         psz_parser++;
263         psz_server_port = psz_parser;
264
265         while( *psz_parser && *psz_parser != '/' )
266         {
267             psz_parser++;
268         }
269     }
270
271     if( *psz_parser == '/' )
272     {
273         *psz_parser = '\0';
274         psz_parser++;
275         psz_path = psz_parser;
276     }
277
278     /* Convert port format */
279     if( psz_server_port != NULL )
280     {
281         i_server_port = strtol( psz_server_port, &psz_parser, 10 );
282         if( *psz_parser )
283         {
284             intf_ErrMsg( "input error: cannot parse server port near %s",
285                          psz_parser );
286             free( p_input->p_access_data );
287             return( -1 );
288         }
289     }
290
291     if( i_server_port == 0 )
292     {
293         i_server_port = 80;
294     }
295
296     if( psz_server_addr == NULL )
297     {
298         intf_ErrMsg( "input error: no server given" );
299         free( p_input->p_access_data );
300         return( -1 );
301     }
302
303     /* Check proxy */
304     if( (psz_proxy = getenv( "http_proxy" )) != NULL )
305     {
306         /* http://myproxy.mydomain:myport/ */
307         int                 i_proxy_port = 0;
308  
309         /* Skip the protocol name */
310         while( *psz_proxy && *psz_proxy != ':' )
311         {
312             psz_proxy++;
313         }
314  
315         /* Skip the "://" part */
316         while( *psz_proxy && (*psz_proxy == ':' || *psz_proxy == '/') )
317         {
318             psz_proxy++;
319         }
320  
321         /* Found a proxy name */
322         if( *psz_proxy )
323         {
324             char *psz_port = psz_proxy;
325  
326             /* Skip the hostname part */
327             while( *psz_port && *psz_port != ':' && *psz_port != '/' )
328             {
329                 psz_port++;
330             }
331  
332             /* Found a port name */
333             if( *psz_port )
334             {
335                 char * psz_junk;
336  
337                 /* Replace ':' with '\0' */
338                 *psz_port = '\0';
339                 psz_port++;
340  
341                 psz_junk = psz_port;
342                 while( *psz_junk && *psz_junk != '/' )
343                 {
344                     psz_junk++;
345                 }
346  
347                 if( *psz_junk )
348                 {
349                     *psz_junk = '\0';
350                 }
351  
352                 if( *psz_port != '\0' )
353                 {
354                     i_proxy_port = atoi( psz_port );
355                 }
356             }
357         }
358         else
359         {
360             intf_ErrMsg( "input error: http_proxy environment variable is invalid !" );
361             free( p_input->p_access_data );
362             return( -1 );
363         }
364
365         p_access_data->socket_desc.i_type = NETWORK_TCP;
366         p_access_data->socket_desc.psz_server_addr = psz_proxy;
367         p_access_data->socket_desc.i_server_port = i_proxy_port;
368
369         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
370                   "GET http://%s:%d/%s HTTP/1.1\r\n",
371                   psz_server_addr, i_server_port, psz_path );
372     }
373     else
374     {
375         /* No proxy, direct connection. */
376         p_access_data->socket_desc.i_type = NETWORK_TCP;
377         p_access_data->socket_desc.psz_server_addr = psz_server_addr;
378         p_access_data->socket_desc.i_server_port = i_server_port;
379
380         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
381                   "GET /%s HTTP/1.1\r\nHost: %s\r\n",
382                   psz_path, psz_server_addr );
383     }
384     p_access_data->psz_buffer[sizeof(p_access_data->psz_buffer) - 1] = '\0';
385
386     intf_WarnMsg( 2, "input: opening server=%s port=%d path=%s",
387                   psz_server_addr, i_server_port, psz_path );
388
389     vlc_mutex_lock( &p_input->stream.stream_lock );
390     p_input->stream.b_pace_control = 1;
391     p_input->stream.b_seekable = 0;
392     p_input->stream.p_selected_area->i_tell = 0;
393     p_input->stream.p_selected_area->i_size = 0;
394     p_input->stream.i_method = INPUT_METHOD_NETWORK;
395     vlc_mutex_unlock( &p_input->stream.stream_lock );
396     p_input->i_mtu = 0;
397  
398     return( HTTPConnect( p_input, 0 ) );
399 }
400
401 /*****************************************************************************
402  * HTTPSetProgram: do nothing
403  *****************************************************************************/
404 static int HTTPSetProgram( input_thread_t * p_input,
405                            pgrm_descriptor_t * p_program )
406 {
407     return( 0 );
408 }
409
410 /*****************************************************************************
411  * HTTPSeek: close and re-open a connection at the right place
412  *****************************************************************************/
413 static void HTTPSeek( input_thread_t * p_input, off_t i_pos )
414 {
415     _input_socket_t *   p_access_data = p_input->p_access_data;
416     close( p_access_data->_socket.i_handle );
417     intf_WarnMsg( 2, "http: seeking to position %lld", i_pos );
418     HTTPConnect( p_input, i_pos );
419 }
420