]> git.sesse.net Git - vlc/blob - plugins/access/http.c
* ./Makefile: fixed OS X vlc.app compilation dependencies.
[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.4 2002/03/15 04:41:54 sam 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 = "";
220     char *              psz_server_port = "";
221     char *              psz_path = "";
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 = "";
233     if( config_GetIntVariable( "ipv4" ) )
234     {
235         p_access_data->psz_network = "ipv4";
236     }
237     if( config_GetIntVariable( "ipv6" ) )
238     {
239         p_access_data->psz_network = "ipv6";
240     }
241     if( *p_input->psz_access )
242     {
243         /* Find out which shortcut was used */
244         if( !strncmp( p_input->psz_access, "http6", 5 ) )
245         {
246             p_access_data->psz_network = "ipv6";
247         }
248         else if( !strncmp( p_input->psz_access, "http4", 5 ) )
249         {
250             p_access_data->psz_network = "ipv4";
251         }
252     }
253
254     /* Parse psz_name syntax :
255      * //<hostname>[:<port>][/<path>] */
256     while( *psz_parser == '/' )
257     {
258         psz_parser++;
259     }
260     psz_server_addr = psz_parser;
261
262     while( *psz_parser && *psz_parser != ':' && *psz_parser != '/' )
263     {
264         psz_parser++;
265     }
266
267     if ( *psz_parser == ':' )
268     {
269         *psz_parser = '\0';
270         psz_parser++;
271         psz_server_port = psz_parser;
272
273         while( *psz_parser && *psz_parser != '/' )
274         {
275             psz_parser++;
276         }
277     }
278
279     if( *psz_parser == '/' )
280     {
281         *psz_parser = '\0';
282         psz_parser++;
283         psz_path = psz_parser;
284     }
285
286     /* Convert port format */
287     if( *psz_server_port )
288     {
289         i_server_port = strtol( psz_server_port, &psz_parser, 10 );
290         if( *psz_parser )
291         {
292             intf_ErrMsg( "input error: cannot parse server port near %s",
293                          psz_parser );
294             free( p_input->p_access_data );
295             return( -1 );
296         }
297     }
298
299     if( i_server_port == 0 )
300     {
301         i_server_port = 80;
302     }
303
304     if( !*psz_server_addr )
305     {
306         intf_ErrMsg( "input error: no server given" );
307         free( p_input->p_access_data );
308         return( -1 );
309     }
310
311     /* Check proxy */
312     if( (psz_proxy = getenv( "http_proxy" )) != NULL )
313     {
314         /* http://myproxy.mydomain:myport/ */
315         int                 i_proxy_port = 0;
316  
317         /* Skip the protocol name */
318         while( *psz_proxy && *psz_proxy != ':' )
319         {
320             psz_proxy++;
321         }
322  
323         /* Skip the "://" part */
324         while( *psz_proxy && (*psz_proxy == ':' || *psz_proxy == '/') )
325         {
326             psz_proxy++;
327         }
328  
329         /* Found a proxy name */
330         if( *psz_proxy )
331         {
332             char *psz_port = psz_proxy;
333  
334             /* Skip the hostname part */
335             while( *psz_port && *psz_port != ':' && *psz_port != '/' )
336             {
337                 psz_port++;
338             }
339  
340             /* Found a port name */
341             if( *psz_port )
342             {
343                 char * psz_junk;
344  
345                 /* Replace ':' with '\0' */
346                 *psz_port = '\0';
347                 psz_port++;
348  
349                 psz_junk = psz_port;
350                 while( *psz_junk && *psz_junk != '/' )
351                 {
352                     psz_junk++;
353                 }
354  
355                 if( *psz_junk )
356                 {
357                     *psz_junk = '\0';
358                 }
359  
360                 if( *psz_port != '\0' )
361                 {
362                     i_proxy_port = atoi( psz_port );
363                 }
364             }
365         }
366         else
367         {
368             intf_ErrMsg( "input error: http_proxy environment variable is invalid !" );
369             free( p_input->p_access_data );
370             return( -1 );
371         }
372
373         p_access_data->socket_desc.i_type = NETWORK_TCP;
374         p_access_data->socket_desc.psz_server_addr = psz_proxy;
375         p_access_data->socket_desc.i_server_port = i_proxy_port;
376
377         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
378                   "GET http://%s:%d/%s HTTP/1.1\r\n",
379                   psz_server_addr, i_server_port, psz_path );
380     }
381     else
382     {
383         /* No proxy, direct connection. */
384         p_access_data->socket_desc.i_type = NETWORK_TCP;
385         p_access_data->socket_desc.psz_server_addr = psz_server_addr;
386         p_access_data->socket_desc.i_server_port = i_server_port;
387
388         snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
389                   "GET /%s HTTP/1.1\r\nHost: %s\r\n",
390                   psz_path, psz_server_addr );
391     }
392     p_access_data->psz_buffer[sizeof(p_access_data->psz_buffer) - 1] = '\0';
393
394     intf_WarnMsg( 2, "input: opening server=%s port=%d path=%s",
395                   psz_server_addr, i_server_port, psz_path );
396
397     vlc_mutex_lock( &p_input->stream.stream_lock );
398     p_input->stream.b_pace_control = 1;
399     p_input->stream.b_seekable = 0;
400     p_input->stream.p_selected_area->i_tell = 0;
401     p_input->stream.p_selected_area->i_size = 0;
402     p_input->stream.i_method = INPUT_METHOD_NETWORK;
403     vlc_mutex_unlock( &p_input->stream.stream_lock );
404     p_input->i_mtu = 0;
405  
406     return( HTTPConnect( p_input, 0 ) );
407 }
408
409 /*****************************************************************************
410  * HTTPSetProgram: do nothing
411  *****************************************************************************/
412 static int HTTPSetProgram( input_thread_t * p_input,
413                            pgrm_descriptor_t * p_program )
414 {
415     return( 0 );
416 }
417
418 /*****************************************************************************
419  * HTTPSeek: close and re-open a connection at the right place
420  *****************************************************************************/
421 static void HTTPSeek( input_thread_t * p_input, off_t i_pos )
422 {
423     _input_socket_t *   p_access_data = p_input->p_access_data;
424     close( p_access_data->_socket.i_handle );
425     intf_WarnMsg( 2, "http: seeking to position %lld", i_pos );
426     HTTPConnect( p_input, i_pos );
427 }
428