http.c 15.8 KB
Newer Older
Christophe Massiot's avatar
Christophe Massiot committed
1
2
3
4
/*****************************************************************************
 * http.c: HTTP access plug-in
 *****************************************************************************
 * Copyright (C) 2001, 2002 VideoLAN
5
 * $Id: http.c,v 1.18 2002/07/25 21:53:53 sigmunau Exp $
Christophe Massiot's avatar
Christophe Massiot committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

34
35
#include <vlc/vlc.h>
#include <vlc/input.h>
Christophe Massiot's avatar
Christophe Massiot committed
36
37
38
39
40
41
42

#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#elif defined( _MSC_VER ) && defined( _WIN32 )
#   include <io.h>
#endif

43
44
45
46
47
48
49
50
51
52
#ifdef WIN32
#   include <winsock2.h>
#   include <ws2tcpip.h>
#   ifndef IN_MULTICAST
#       define IN_MULTICAST(a) IN_CLASSD(a)
#   endif
#else
#   include <sys/socket.h>
#endif

Christophe Massiot's avatar
Christophe Massiot committed
53
54
55
56
57
58
#include "network.h"

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static void input_getfunctions( function_list_t * );
59
60
61
62
static int  HTTPOpen       ( input_thread_t * );
static void HTTPClose      ( input_thread_t * );
static int  HTTPSetProgram ( input_thread_t *, pgrm_descriptor_t * );  
static void HTTPSeek       ( input_thread_t *, off_t );
Christophe Massiot's avatar
Christophe Massiot committed
63
64
65
66
67
68
69
70

/*****************************************************************************
 * Build configuration tree.
 *****************************************************************************/
MODULE_CONFIG_START
MODULE_CONFIG_STOP
 
MODULE_INIT_START
Sam Hocevar's avatar
   
Sam Hocevar committed
71
    SET_DESCRIPTION( _("HTTP access plug-in") )
Christophe Massiot's avatar
Christophe Massiot committed
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    ADD_CAPABILITY( ACCESS, 0 )
    ADD_SHORTCUT( "http4" )
    ADD_SHORTCUT( "http6" )
MODULE_INIT_STOP
 
MODULE_ACTIVATE_START
    input_getfunctions( &p_module->p_functions->access );
MODULE_ACTIVATE_STOP
 
MODULE_DEACTIVATE_START
MODULE_DEACTIVATE_STOP

/*****************************************************************************
 * Functions exported as capabilities. They are declared as static so that
 * we don't pollute the namespace too much.
 *****************************************************************************/
static void input_getfunctions( function_list_t * p_function_list )
{
#define input p_function_list->functions.access
    input.pf_open             = HTTPOpen;
    input.pf_read             = input_FDNetworkRead;
Christophe Massiot's avatar
Christophe Massiot committed
93
    input.pf_close            = HTTPClose;
Christophe Massiot's avatar
Christophe Massiot committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    input.pf_set_program      = HTTPSetProgram;
    input.pf_set_area         = NULL;
    input.pf_seek             = HTTPSeek;
#undef input
}

/*****************************************************************************
 * _input_socket_t: private access plug-in data, modified to add private
 *                  fields
 *****************************************************************************/
typedef struct _input_socket_s
{
    input_socket_t      _socket;

    char *              psz_network;
    network_socket_t    socket_desc;
    char                psz_buffer[256];
Christophe Massiot's avatar
Christophe Massiot committed
111
    char *              psz_name;
Christophe Massiot's avatar
Christophe Massiot committed
112
113
114
115
116
117
118
119
} _input_socket_t;

/*****************************************************************************
 * HTTPConnect: connect to the server and seek to i_tell
 *****************************************************************************/
static int HTTPConnect( input_thread_t * p_input, off_t i_tell )
{
    _input_socket_t *   p_access_data = p_input->p_access_data;
120
    module_t *          p_network;
Christophe Massiot's avatar
Christophe Massiot committed
121
122
    char                psz_buffer[256];
    byte_t *            psz_parser;
123
124
    int                 i_returncode, i;
    char *              psz_return_alpha;
Christophe Massiot's avatar
Christophe Massiot committed
125
126

    /* Find an appropriate network module */
127
    p_network = module_Need( p_input, MODULE_CAPABILITY_NETWORK,
Christophe Massiot's avatar
Christophe Massiot committed
128
129
130
131
132
133
134
135
136
137
138
139
140
                             p_access_data->psz_network,
                             &p_access_data->socket_desc );
    if( p_network == NULL )
    {
        return( -1 );
    }
    module_Unneed( p_network );

    p_access_data->_socket.i_handle = p_access_data->socket_desc.i_handle;

#   define HTTP_USERAGENT "User-Agent: " COPYRIGHT_MESSAGE "\r\n"
#   define HTTP_END       "\r\n"
 
141
142
143
144
145
146
147
148
149
150
151
152
153
    if ( p_input->stream.b_seekable )
    {
         snprintf( psz_buffer, sizeof(psz_buffer),
                   "%s"
                   "Range: bytes=%lld-\r\n"
                   HTTP_USERAGENT HTTP_END,
                   p_access_data->psz_buffer, i_tell );
    }
    else
    {
         snprintf( psz_buffer, sizeof(psz_buffer),
                   "%s"
                   HTTP_USERAGENT HTTP_END,
154
                   p_access_data->psz_buffer );
155
    }
Christophe Massiot's avatar
Christophe Massiot committed
156
157
158
    psz_buffer[sizeof(psz_buffer) - 1] = '\0';

    /* Send GET ... */
159
160
    if( send( p_access_data->_socket.i_handle, psz_buffer,
               strlen( psz_buffer ), 0 ) == (-1) )
Christophe Massiot's avatar
Christophe Massiot committed
161
    {
162
        msg_Err( p_input, "cannot send request (%s)", strerror(errno) );
163
        input_FDNetworkClose( p_input );
Christophe Massiot's avatar
Christophe Massiot committed
164
165
166
167
168
169
        return( -1 );
    }

    /* Prepare the input thread for reading. */ 
    p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
    /* FIXME: we shouldn't have to do that ! */
170
    p_input->pf_read = input_FDNetworkRead;
Christophe Massiot's avatar
Christophe Massiot committed
171
172
173
174
175

    while( !input_FillBuffer( p_input ) )
    {
        if( p_input->b_die || p_input->b_error )
        {
176
            input_FDNetworkClose( p_input );
Christophe Massiot's avatar
Christophe Massiot committed
177
178
179
180
181
182
            return( -1 );
        }
    }

    /* Parse HTTP header. */
#define MAX_LINE 1024
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

    /* get the returncode */
    if( input_Peek( p_input, &psz_parser, MAX_LINE ) <= 0 )
    {
        msg_Err( p_input, "not enough data" );
        input_FDNetworkClose( p_input );
        return( -1 );
    }

    if( !strncmp( psz_parser, "HTTP/1.",
                  strlen("HTTP/1.") ) )
    {
        psz_parser += strlen("HTTP 1.") + 2;
        i_returncode = atoi( psz_parser );
        msg_Dbg( p_input, "HTTP server replied: %i", i_returncode );
        psz_parser += 4;
        for ( i = 0; psz_parser[i] != '\r' || psz_parser[i+1] != '\n'; i++ )
        {
            ;
        }
        psz_return_alpha = malloc( i + 1 );
        memcpy( psz_return_alpha, psz_parser, i );
        psz_return_alpha[i] = '\0';
    }
    else
    {
209
        msg_Err( p_input, "invalid http reply" );
210
211
212
213
214
215
216
217
218
        return -1;
    }
    
    if ( i_returncode >= 400 ) /* something is wrong */
    {
        msg_Err( p_input, "%i %s", i_returncode,
                 psz_return_alpha );
        return -1;
    }
219
    
Christophe Massiot's avatar
Christophe Massiot committed
220
221
222
223
    for( ; ; ) 
    {
        if( input_Peek( p_input, &psz_parser, MAX_LINE ) <= 0 )
        {
224
            msg_Err( p_input, "not enough data" );
225
            input_FDNetworkClose( p_input );
Christophe Massiot's avatar
Christophe Massiot committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
            return( -1 );
        }

        if( psz_parser[0] == '\r' && psz_parser[1] == '\n' )
        {
            /* End of header. */
            p_input->p_current_data += 2;
            break;
        }

        if( !strncmp( psz_parser, "Content-Length: ",
                      strlen("Content-Length: ") ) )
        {
            psz_parser += strlen("Content-Length: ");
            vlc_mutex_lock( &p_input->stream.stream_lock );
241
242
243
244
245
#ifdef HAVE_ATOLL
            p_input->stream.p_selected_area->i_size = atoll( psz_parser )
                                                        + i_tell;
#else
            /* FIXME : this won't work for 64-bit lengths */
Christophe Massiot's avatar
Christophe Massiot committed
246
247
            p_input->stream.p_selected_area->i_size = atoi( psz_parser )
                                                        + i_tell;
248
#endif
Christophe Massiot's avatar
Christophe Massiot committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
            vlc_mutex_unlock( &p_input->stream.stream_lock );
        }

        while( *psz_parser != '\r' && psz_parser < p_input->p_last_data )
        {
            psz_parser++;
        }
        p_input->p_current_data = psz_parser + 2;
    }

    if( p_input->stream.p_selected_area->i_size )
    {
        vlc_mutex_lock( &p_input->stream.stream_lock );
        p_input->stream.p_selected_area->i_tell = i_tell
            + (p_input->p_last_data - p_input->p_current_data);
        p_input->stream.b_seekable = 1;
        p_input->stream.b_changed = 1;
        vlc_mutex_unlock( &p_input->stream.stream_lock );
    }

    return( 0 );
}

/*****************************************************************************
 * HTTPOpen: parse URL and open the remote file at the beginning
 *****************************************************************************/
static int HTTPOpen( input_thread_t * p_input )
{
    _input_socket_t *   p_access_data;
Christophe Massiot's avatar
Christophe Massiot committed
278
279
    char *              psz_name = strdup(p_input->psz_name);
    char *              psz_parser = psz_name;
Sam Hocevar's avatar
   
Sam Hocevar committed
280
281
282
    char *              psz_server_addr = "";
    char *              psz_server_port = "";
    char *              psz_path = "";
Christophe Massiot's avatar
Christophe Massiot committed
283
284
285
286
287
288
    char *              psz_proxy;
    int                 i_server_port = 0;

    p_access_data = p_input->p_access_data = malloc( sizeof(_input_socket_t) );
    if( p_access_data == NULL )
    {
289
        msg_Err( p_input, "out of memory" );
Christophe Massiot's avatar
Christophe Massiot committed
290
        free(psz_name);
Christophe Massiot's avatar
Christophe Massiot committed
291
292
293
        return( -1 );
    }

Christophe Massiot's avatar
Christophe Massiot committed
294
    p_access_data->psz_name = psz_name;
Sam Hocevar's avatar
   
Sam Hocevar committed
295
    p_access_data->psz_network = "";
296
    if( config_GetInt( p_input, "ipv4" ) )
297
298
299
    {
        p_access_data->psz_network = "ipv4";
    }
300
    if( config_GetInt( p_input, "ipv6" ) )
301
302
303
    {
        p_access_data->psz_network = "ipv6";
    }
Sam Hocevar's avatar
   
Sam Hocevar committed
304
    if( *p_input->psz_access )
Christophe Massiot's avatar
Christophe Massiot committed
305
306
    {
        /* Find out which shortcut was used */
Sam Hocevar's avatar
   
Sam Hocevar committed
307
        if( !strncmp( p_input->psz_access, "http6", 6 ) )
Christophe Massiot's avatar
Christophe Massiot committed
308
309
310
        {
            p_access_data->psz_network = "ipv6";
        }
Sam Hocevar's avatar
   
Sam Hocevar committed
311
        else if( !strncmp( p_input->psz_access, "http4", 6 ) )
Christophe Massiot's avatar
Christophe Massiot committed
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
        {
            p_access_data->psz_network = "ipv4";
        }
    }

    /* Parse psz_name syntax :
     * //<hostname>[:<port>][/<path>] */
    while( *psz_parser == '/' )
    {
        psz_parser++;
    }
    psz_server_addr = psz_parser;

    while( *psz_parser && *psz_parser != ':' && *psz_parser != '/' )
    {
        psz_parser++;
    }

    if ( *psz_parser == ':' )
    {
        *psz_parser = '\0';
        psz_parser++;
        psz_server_port = psz_parser;

        while( *psz_parser && *psz_parser != '/' )
        {
            psz_parser++;
        }
    }

    if( *psz_parser == '/' )
    {
        *psz_parser = '\0';
        psz_parser++;
        psz_path = psz_parser;
    }

    /* Convert port format */
Sam Hocevar's avatar
   
Sam Hocevar committed
350
    if( *psz_server_port )
Christophe Massiot's avatar
Christophe Massiot committed
351
352
353
354
    {
        i_server_port = strtol( psz_server_port, &psz_parser, 10 );
        if( *psz_parser )
        {
355
            msg_Err( p_input, "cannot parse server port near %s", psz_parser );
Christophe Massiot's avatar
Christophe Massiot committed
356
            free( p_input->p_access_data );
Christophe Massiot's avatar
Christophe Massiot committed
357
            free( psz_name );
Christophe Massiot's avatar
Christophe Massiot committed
358
359
360
361
362
363
364
365
366
            return( -1 );
        }
    }

    if( i_server_port == 0 )
    {
        i_server_port = 80;
    }

Sam Hocevar's avatar
   
Sam Hocevar committed
367
    if( !*psz_server_addr )
Christophe Massiot's avatar
Christophe Massiot committed
368
    {
369
        msg_Err( p_input, "no server given" );
Christophe Massiot's avatar
Christophe Massiot committed
370
        free( p_input->p_access_data );
Christophe Massiot's avatar
Christophe Massiot committed
371
        free( psz_name );
Christophe Massiot's avatar
Christophe Massiot committed
372
373
374
375
        return( -1 );
    }

    /* Check proxy */
Sam Hocevar's avatar
   
Sam Hocevar committed
376
    if( (psz_proxy = getenv( "http_proxy" )) != NULL && *psz_proxy )
Christophe Massiot's avatar
Christophe Massiot committed
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
    {
        /* http://myproxy.mydomain:myport/ */
        int                 i_proxy_port = 0;
 
        /* Skip the protocol name */
        while( *psz_proxy && *psz_proxy != ':' )
        {
            psz_proxy++;
        }
 
        /* Skip the "://" part */
        while( *psz_proxy && (*psz_proxy == ':' || *psz_proxy == '/') )
        {
            psz_proxy++;
        }
 
        /* Found a proxy name */
        if( *psz_proxy )
        {
            char *psz_port = psz_proxy;
 
            /* Skip the hostname part */
            while( *psz_port && *psz_port != ':' && *psz_port != '/' )
            {
                psz_port++;
            }
 
            /* Found a port name */
            if( *psz_port )
            {
                char * psz_junk;
 
                /* Replace ':' with '\0' */
                *psz_port = '\0';
                psz_port++;
 
                psz_junk = psz_port;
                while( *psz_junk && *psz_junk != '/' )
                {
                    psz_junk++;
                }
 
                if( *psz_junk )
                {
                    *psz_junk = '\0';
                }
 
                if( *psz_port != '\0' )
                {
                    i_proxy_port = atoi( psz_port );
                }
            }
        }
        else
        {
432
            msg_Err( p_input, "http_proxy environment variable is invalid!" );
Christophe Massiot's avatar
Christophe Massiot committed
433
            free( p_input->p_access_data );
Christophe Massiot's avatar
Christophe Massiot committed
434
            free( psz_name );
Christophe Massiot's avatar
Christophe Massiot committed
435
436
437
438
439
440
441
442
            return( -1 );
        }

        p_access_data->socket_desc.i_type = NETWORK_TCP;
        p_access_data->socket_desc.psz_server_addr = psz_proxy;
        p_access_data->socket_desc.i_server_port = i_proxy_port;

        snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
443
                  "GET http://%s:%d/%s\r\n HTTP/1.0\r\n",
Christophe Massiot's avatar
Christophe Massiot committed
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
                  psz_server_addr, i_server_port, psz_path );
    }
    else
    {
        /* No proxy, direct connection. */
        p_access_data->socket_desc.i_type = NETWORK_TCP;
        p_access_data->socket_desc.psz_server_addr = psz_server_addr;
        p_access_data->socket_desc.i_server_port = i_server_port;

        snprintf( p_access_data->psz_buffer, sizeof(p_access_data->psz_buffer),
                  "GET /%s HTTP/1.1\r\nHost: %s\r\n",
                  psz_path, psz_server_addr );
    }
    p_access_data->psz_buffer[sizeof(p_access_data->psz_buffer) - 1] = '\0';

459
460
    msg_Dbg( p_input, "opening server=%s port=%d path=%s",
                      psz_server_addr, i_server_port, psz_path );
Christophe Massiot's avatar
Christophe Massiot committed
461
462
463

    vlc_mutex_lock( &p_input->stream.stream_lock );
    p_input->stream.b_pace_control = 1;
464
    p_input->stream.b_seekable = 1;
Christophe Massiot's avatar
Christophe Massiot committed
465
466
467
468
469
470
    p_input->stream.p_selected_area->i_tell = 0;
    p_input->stream.p_selected_area->i_size = 0;
    p_input->stream.i_method = INPUT_METHOD_NETWORK;
    vlc_mutex_unlock( &p_input->stream.stream_lock );
    p_input->i_mtu = 0;
 
471
472
473
474
    if( HTTPConnect( p_input, 0 ) )
    {
        char * psz_pos = strstr(p_access_data->psz_buffer, "HTTP/1.1");
        p_input->stream.b_seekable = 0;
475
        psz_pos[7] = '0';
476
477
478
479
480
481
        if( HTTPConnect( p_input, 0 ) )
        {
            free( p_input->p_access_data );
            free( psz_name );
            return( -1 );
        }
482
483
    }
    return 0;
Christophe Massiot's avatar
Christophe Massiot committed
484
485
}

Christophe Massiot's avatar
Christophe Massiot committed
486
487
488
489
490
/*****************************************************************************
 * HTTPClose: free unused data structures
 *****************************************************************************/
static void HTTPClose( input_thread_t * p_input )
{
491
492
493
494
    _input_socket_t * p_access_data = 
        (_input_socket_t *)p_input->p_access_data;

    free( p_access_data->psz_name );
495
    input_FDNetworkClose( p_input );
Christophe Massiot's avatar
Christophe Massiot committed
496
497
}

Christophe Massiot's avatar
Christophe Massiot committed
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
/*****************************************************************************
 * HTTPSetProgram: do nothing
 *****************************************************************************/
static int HTTPSetProgram( input_thread_t * p_input,
                           pgrm_descriptor_t * p_program )
{
    return( 0 );
}

/*****************************************************************************
 * HTTPSeek: close and re-open a connection at the right place
 *****************************************************************************/
static void HTTPSeek( input_thread_t * p_input, off_t i_pos )
{
    _input_socket_t *   p_access_data = p_input->p_access_data;
    close( p_access_data->_socket.i_handle );
514
    msg_Dbg( p_input, "seeking to position %lld", i_pos );
Christophe Massiot's avatar
Christophe Massiot committed
515
516
517
    HTTPConnect( p_input, i_pos );
}