* rtsp: mmmh, close the socket.
[vlc.git] / modules / access / rtsp.c
1 /*****************************************************************************
2  * rtsp.c: Retreive a description from an RTSP url.
3  *****************************************************************************
4  * Copyright (C) 2003 VideoLAN
5  * $Id: rtsp.c,v 1.2 2003/08/03 12:04:28 fenrir Exp $
6  *
7  * Authors: Laurent Aimar <fenrir@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 <vlc/vlc.h>
29 #include <vlc/input.h>
30
31 #ifdef HAVE_UNISTD_H
32 #   include <unistd.h>
33 #endif
34
35 #include <errno.h>
36
37 #ifdef WIN32
38 #   include <winsock2.h>
39 #   include <ws2tcpip.h>
40 #   ifndef IN_MULTICAST
41 #       define IN_MULTICAST(a) IN_CLASSD(a)
42 #   endif
43 #else
44 #   include <sys/socket.h>
45 #   include <netinet/in.h>
46 #   if HAVE_ARPA_INET_H
47 #      include <arpa/inet.h>
48 #   elif defined( SYS_BEOS )
49 #      include <net/netdb.h>
50 #   endif
51 #endif
52
53 #include "network.h"
54
55 /*
56  * TODO:
57  *  - login/password management
58  *  - RTSP over UDP
59  *
60  */
61 /*****************************************************************************
62  * Module descriptor
63  *****************************************************************************/
64 static int     Open   ( vlc_object_t * );
65 static void    Close  ( vlc_object_t * );
66
67 vlc_module_begin();
68     set_description( _("RTSP SDP request") );
69     set_capability( "access", 0 );
70     add_category_hint( "stream", NULL, VLC_FALSE );
71         add_integer( "rtsp-caching", 2 * DEFAULT_PTS_DELAY / 1000, NULL,
72                      "", "", VLC_TRUE );
73     add_shortcut( "rtsp" );
74     add_shortcut( "rtspu" );
75     set_callbacks( Open, Close );
76 vlc_module_end();
77
78 /*****************************************************************************
79  * Locales prototypes
80  *****************************************************************************/
81 static ssize_t Read   ( input_thread_t *, byte_t *, size_t );
82
83 typedef struct
84 {
85     char           *psz_host;
86     int            i_port;
87     char           *psz_path;
88     char           *psz_login;
89     char           *psz_password;
90 } url_t;
91
92 static void url_Parse( url_t *, char * );
93 static void url_Clean( url_t * );
94
95 static int  NetRead ( input_thread_t *, int , uint8_t *, int );
96 static void NetClose( input_thread_t *p_input, int i_handle );
97
98 struct access_sys_t
99 {
100     int i_handle;
101
102     /* description */
103     int     i_data;
104     uint8_t *p_data;
105     uint8_t *p_actu;
106 };
107
108 #define RTSP_DESCRIBE_REQUEST \
109     "DESCRIBE rtsp://%s:%d/%s RTSP/1.0\r\n" \
110     "CSeq: 1\r\n" \
111     "User-Agent: " COPYRIGHT_MESSAGE "\r\n" \
112     "Accept: application/sdp\r\n" \
113     "\r\n"
114
115 /*****************************************************************************
116  * Open: open the file
117  *****************************************************************************/
118 static int Open( vlc_object_t *p_this )
119 {
120     input_thread_t *p_input = (input_thread_t *)p_this;
121     access_sys_t   *p_sys;
122
123     url_t          url;
124     char           *psz_network = "";
125     vlc_bool_t     b_udp = VLC_FALSE;
126     module_t       *p_network;
127     int            i_handle = -1;
128
129     network_socket_t    socket_desc;
130     int                 i_request;
131     uint8_t             *p_request, *p;
132
133     int                 i_code;
134
135     vlc_value_t    val;
136
137     /* Parse the URL [user:password@]host[:port]/path */
138     url_Parse( &url, p_input->psz_name );
139     if( url.i_port <= 0 )
140     {
141         /* Default RTSP port */
142         url.i_port = 554;
143     }
144
145     /* ipv4 vs ipv6 */
146     var_Create( p_input, "ipv4", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
147     var_Get( p_input, "ipv4", &val );
148     if( val.i_int )
149     {
150         psz_network = "ipv4";
151     }
152
153     var_Create( p_input, "ipv6", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
154     var_Get( p_input, "ipv6", &val );
155     if( val.i_int )
156     {
157         psz_network = "ipv6";
158     }
159
160     /* UDP vs TCP */
161     if( !strcmp( p_input->psz_access, "rtspu" ) )
162     {
163         b_udp = VLC_TRUE;
164     }
165
166     if( b_udp )
167     {
168         msg_Err( p_input, "RTSP over UDP not supported" );
169         goto error;
170     }
171
172     /* Open the TCP connection */
173     socket_desc.i_type          = NETWORK_TCP;
174     socket_desc.psz_server_addr = url.psz_host;
175     socket_desc.i_server_port   = url.i_port;
176     socket_desc.psz_bind_addr   = "";
177     socket_desc.i_bind_port     = 0;
178     socket_desc.i_ttl           = 0;
179
180     p_input->p_private = (void*)&socket_desc;
181     p_network = module_Need( p_input, "network", psz_network );
182     if( p_network == NULL )
183     {
184         goto error;
185     }
186     module_Unneed( p_input, p_network );
187     i_handle = socket_desc.i_handle;
188     p_input->i_mtu    = socket_desc.i_mtu;
189
190     msg_Dbg( p_input, "connected with %s:%d", url.psz_host, url.i_port );
191
192     /* Build request */
193     i_request = strlen( RTSP_DESCRIBE_REQUEST ) +
194                 strlen( url.psz_host ) + 5 + strlen( url.psz_path ) + 1;
195     p_request = malloc( i_request );
196     sprintf( p_request, RTSP_DESCRIBE_REQUEST,
197              url.psz_host, url.i_port, url.psz_path );
198
199     msg_Dbg( p_input, "Request=%s", p_request );
200     /* Send the request */
201     for( p = p_request; p < p_request + strlen( p_request ); )
202     {
203         int i_send;
204
205         i_send = send( i_handle, p, strlen( p ), 0 );
206         if( i_send <= 0 )
207         {
208             goto error;
209         }
210
211         p += i_send;
212     }
213     free( p_request );
214
215     /* Read the complete answer*/
216     i_request = 1024;
217     p_request = p = malloc( i_request );
218     p_request[0] = '\0';
219     for( ;; )
220     {
221         int i_recv;
222
223         i_recv = NetRead( p_input, i_handle, p, i_request - ( p - p_request ) - 1);
224         if( i_recv <= 0 )
225         {
226             break;
227         }
228
229         p[i_recv] = '\0';
230
231         p += i_recv;
232
233         if( p >= p_request + i_request - 1 )
234         {
235             int i_size = p - p_request;
236
237             p_request = realloc( p_request, i_request + 1024 );
238
239             p = p_request + i_size;
240         }
241     }
242
243     if( strlen( p_request ) <= 0 ||
244         ( !strstr( p_request, "\n\n" ) && !strstr( p_request, "\r\n\r\n" )  ) )
245     {
246         msg_Dbg( p_input, "failed to retreive description" );
247         goto error;
248     }
249
250     /* Parse the header */
251     msg_Dbg( p_input, "answer header=%s", p_request );
252     if( strncmp( p_request, "RTSP/1.", strlen( "RTSP/1." ) ) )
253     {
254         msg_Err( p_input, "invalid answer" );
255         free( p_request );
256         goto error;
257     }
258     i_code = atoi( &p_request[strlen( "RTSP/1.x" )] );
259
260     if( i_code / 100 != 2 )
261     {
262         msg_Err( p_input, "return code is %d (!=2xx), not yet supported", i_code );
263         free( p_request );
264         goto error;
265     }
266
267     p_input->p_access_data = p_sys = malloc( sizeof( access_sys_t ) );
268     p_sys->i_handle = i_handle;
269     p_sys->p_data   = p_request;
270     p_sys->i_data   = strlen( p_request );
271     if( ( p = strstr( p_request, "\r\n\r\n" ) ) )
272     {
273         p_sys->p_actu = p + 4;
274     }
275     else if( ( p = strstr( p_request, "\n\n" ) ) )
276     {
277         p_sys->p_actu = p + 2;
278     }
279     else
280     {
281         msg_Err( p_input, "mhhh ? cannot happens" );
282         goto error;
283     }
284
285     /* Set exported functions */
286     p_input->pf_read = Read;
287     p_input->pf_seek = NULL;
288     p_input->pf_set_program = input_SetProgram;
289     p_input->pf_set_area = NULL;
290     p_input->p_private = NULL;
291
292     /* Finished to set some variable */
293     vlc_mutex_lock( &p_input->stream.stream_lock );
294     p_input->stream.b_pace_control = VLC_TRUE;
295     p_input->stream.p_selected_area->i_tell = 0;
296     p_input->stream.b_seekable = 0;
297     p_input->stream.p_selected_area->i_size = 0;
298     p_input->stream.i_method = INPUT_METHOD_NETWORK;
299     vlc_mutex_unlock( &p_input->stream.stream_lock );
300
301     /* Update default_pts to a suitable value for RTSP access */
302     p_input->i_pts_delay = config_GetInt( p_input, "rtsp-caching" ) * 1000;
303
304     NetClose( p_input, i_handle );
305     url_Clean( &url );
306     return VLC_SUCCESS;
307
308 error:
309     if( i_handle > 0 )
310     {
311         NetClose( p_input, i_handle );
312     }
313     url_Clean( &url );
314     return VLC_EGENERIC;
315 }
316
317 /*****************************************************************************
318  * Close: close the target
319  *****************************************************************************/
320 static void Close( vlc_object_t * p_this )
321 {
322     input_thread_t *p_input = (input_thread_t *)p_this;
323     access_sys_t   *p_sys   = p_input->p_access_data;
324
325     free( p_sys->p_data );
326     free( p_sys );
327 }
328
329 /*****************************************************************************
330  * Read: standard read on a file descriptor.
331  *****************************************************************************/
332 static ssize_t Read( input_thread_t * p_input, byte_t * p_buffer, size_t i_len )
333 {
334     access_sys_t   *p_sys   = p_input->p_access_data;
335
336     int            i_copy = __MIN( (int)i_len, &p_sys->p_data[p_sys->i_data] - p_sys->p_actu );
337
338     if( i_copy <= 0 )
339     {
340         return 0;
341     }
342
343     memcpy( p_buffer, p_sys->p_actu, i_copy );
344
345     p_sys->p_actu += i_copy;
346
347     return i_copy;
348 }
349
350 /*****************************************************************************
351  *
352  *****************************************************************************/
353 static void url_Parse( url_t *url, char *psz_url )
354 {
355     char *psz_dup = strdup( psz_url );
356     char *psz_tmp, *p;
357
358     p = psz_dup;
359
360     while( *p == '/' )
361     {
362         p++;
363     }
364     if( strchr( p, '@' ) )
365     {
366         /* TODO: There is a login/password */
367     }
368     url->psz_login = strdup( "" );
369     url->psz_password = strdup( "" );
370
371     psz_tmp = p;
372     /* Skip host */
373     while( *p && *p != ':' &&  *p != '/' )
374     {
375         if( *p == '[' )
376         {
377             /* ipv6 address */
378             while( *p && *p != ']' )
379             {
380                 *p++;
381             }
382         }
383         p++;
384     }
385
386     /* Get port */
387     if( *p == ':' )
388     {
389         char *psz_port;
390         *p++ = 0;
391         psz_port = p;
392
393         while( *p && *p != '/' )
394         {
395             p++;
396         }
397         url->i_port = atoi( psz_port );
398     }
399     else
400     {
401         url->i_port = 0;
402     }
403     /* Get host */
404     *p++ = '\0';
405     url->psz_host = strdup( psz_tmp );
406
407     /* Get path */
408     url->psz_path = strdup( p );
409
410     free( psz_dup );
411 }
412
413 static void url_Clean( url_t *url )
414 {
415     if( url->psz_host )
416     {
417         free( url->psz_host );
418     }
419     if( url->psz_path )
420     {
421         free( url->psz_path );
422     }
423     if( url->psz_login )
424     {
425         free( url->psz_login );
426     }
427     if( url->psz_password )
428     {
429         free( url->psz_password );
430     }
431 }
432
433
434
435 static int NetRead( input_thread_t *p_input,
436                     int i_handle,
437                     uint8_t *p_buffer, int i_len )
438 {
439     struct timeval  timeout;
440     fd_set          fds;
441     int             i_recv;
442     int             i_ret;
443
444     /* Initialize file descriptor set */
445     FD_ZERO( &fds );
446     FD_SET( i_handle, &fds );
447
448     /* We'll wait 1 second if nothing happens */
449     timeout.tv_sec  = 1;
450     timeout.tv_usec = 0;
451
452     /* Find if some data is available */
453     while( (i_ret = select( i_handle + 1, &fds,
454                             NULL, NULL, &timeout )) == 0
455            || (i_ret < 0 && errno == EINTR) )
456     {
457         FD_ZERO( &fds );
458         FD_SET( i_handle, &fds );
459         timeout.tv_sec  = 1;
460         timeout.tv_usec = 0;
461
462         if( p_input->b_die || p_input->b_error )
463         {
464             return 0;
465         }
466     }
467
468     if( i_ret < 0 )
469     {
470         msg_Err( p_input, "network select error (%s)", strerror(errno) );
471         return -1;
472     }
473
474     i_recv = recv( i_handle, p_buffer, i_len, 0 );
475
476     if( i_recv < 0 )
477     {
478         msg_Err( p_input, "recv failed (%s)", strerror(errno) );
479     }
480
481     return i_recv;
482 }
483
484
485
486 static void NetClose( input_thread_t *p_input, int i_handle )
487 {
488 #if defined( UNDER_CE )
489     CloseHandle( (HANDLE)i_handle );
490 #elif defined( WIN32 )
491     closesocket( i_handle );
492 #else
493     close( i_handle );
494 #endif
495 }
496
497