gstrtspconnection.c 92.6 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) <2005-2009> Wim Taymans <wim.taymans@gmail.com>
3
4
5
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
34
35
36
37
38
39
40
41
42
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
/*
 * Unless otherwise indicated, Source Code is licensed under MIT license.
 * See further explanation attached in License Statement (distributed in the file
 * LICENSE).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 * SECTION:gstrtspconnection
 * @short_description: manage RTSP connections
 * @see_also: gstrtspurl
 *  
 * <refsect2>
 * <para>
 * This object manages the RTSP connection to the server. It provides function
 * to receive and send bytes and messages.
 * </para>
 * </refsect2>
 *  
 * Last reviewed on 2007-07-24 (0.10.14)
 */

58
59
60
61
62
63
64
65
66
67
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

68
69
70
71
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

72
73
/* we include this here to get the G_OS_* defines */
#include <glib.h>
74
#include <gst/gst.h>
75
76

#ifdef G_OS_WIN32
Peter Kjellerstedt's avatar
Peter Kjellerstedt committed
77
78
/* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later.
 * minwg32 headers check WINVER before allowing the use of these */
79
80
81
#ifndef WINVER
#define WINVER 0x0501
#endif
82
#include <winsock2.h>
83
#include <ws2tcpip.h>
84
85
86
87
88
#define EINPROGRESS WSAEINPROGRESS
#else
#include <sys/ioctl.h>
#include <netdb.h>
#include <sys/socket.h>
89
#include <fcntl.h>
90
#include <netinet/in.h>
91
92
93
94
95
96
97
98
99
#endif

#ifdef HAVE_FIONREAD_IN_SYS_FILIO
#include <sys/filio.h>
#endif

#include "gstrtspconnection.h"
#include "gstrtspbase64.h"

100
101
102
103
104
105
106
107
union gst_sockaddr
{
  struct sockaddr sa;
  struct sockaddr_in sa_in;
  struct sockaddr_in6 sa_in6;
  struct sockaddr_storage sa_stor;
};

108
109
110
111
typedef struct
{
  gint state;
  guint save;
112
  guchar out[3];                /* the size must be evenly divisible by 3 */
113
  guint cout;
114
  guint coutl;
115
116
} DecodeCtx;

117
#ifdef G_OS_WIN32
118
119
120
#define READ_SOCKET(fd, buf, len) recv (fd, (char *)buf, len, 0)
#define WRITE_SOCKET(fd, buf, len) send (fd, (const char *)buf, len, 0)
#define SETSOCKOPT(sock, level, name, val, len) setsockopt (sock, level, name, (const char *)val, len)
121
#define CLOSE_SOCKET(sock) closesocket (sock)
122
123
#define ERRNO_IS_EAGAIN (WSAGetLastError () == WSAEWOULDBLOCK)
#define ERRNO_IS_EINTR (WSAGetLastError () == WSAEINTR)
124
125
/* According to Microsoft's connect() documentation this one returns
 * WSAEWOULDBLOCK and not WSAEINPROGRESS. */
126
#define ERRNO_IS_EINPROGRESS (WSAGetLastError () == WSAEWOULDBLOCK)
127
#else
128
129
#define READ_SOCKET(fd, buf, len) read (fd, buf, len)
#define WRITE_SOCKET(fd, buf, len) write (fd, buf, len)
130
#define SETSOCKOPT(sock, level, name, val, len) setsockopt (sock, level, name, val, len)
131
#define CLOSE_SOCKET(sock) close (sock)
132
133
134
#define ERRNO_IS_EAGAIN (errno == EAGAIN)
#define ERRNO_IS_EINTR (errno == EINTR)
#define ERRNO_IS_EINPROGRESS (errno == EINPROGRESS)
135
136
#endif

137
138
139
140
#define ADD_POLLFD(fdset, pfd, fd)        \
G_STMT_START {                            \
  (pfd)->fd = fd;                         \
  gst_poll_add_fd (fdset, pfd);           \
141
142
} G_STMT_END

143
144
145
146
147
148
149
150
#define REMOVE_POLLFD(fdset, pfd)          \
G_STMT_START {                             \
  if ((pfd)->fd != -1) {                   \
    GST_DEBUG ("remove fd %d", (pfd)->fd); \
    gst_poll_remove_fd (fdset, pfd);       \
    CLOSE_SOCKET ((pfd)->fd);              \
    (pfd)->fd = -1;                        \
  }                                        \
151
152
} G_STMT_END

153
154
155
156
157
158
159
160
161
162
typedef enum
{
  TUNNEL_STATE_NONE,
  TUNNEL_STATE_GET,
  TUNNEL_STATE_POST,
  TUNNEL_STATE_COMPLETE
} GstRTSPTunnelState;

#define TUNNELID_LEN   24

163
164
165
166
167
168
169
struct _GstRTSPConnection
{
  /*< private > */
  /* URL for the connection */
  GstRTSPUrl *url;

  /* connection state */
170
171
172
173
174
175
  GstPollFD fd0;
  GstPollFD fd1;

  GstPollFD *readfd;
  GstPollFD *writefd;

176
177
  gboolean manual_http;

178
  gchar tunnelid[TUNNELID_LEN];
179
  gboolean tunneled;
180
  GstRTSPTunnelState tstate;
181

182
183
184
  GstPoll *fdset;
  gchar *ip;

185
186
  gint read_ahead;

187
188
189
  gchar *initial_buffer;
  gsize initial_buffer_offset;

190
191
192
193
194
195
196
197
198
199
200
  /* Session state */
  gint cseq;                    /* sequence number */
  gchar session_id[512];        /* session id */
  gint timeout;                 /* session timeout in seconds */
  GTimer *timer;                /* timeout timer */

  /* Authentication */
  GstRTSPAuthMethod auth_method;
  gchar *username;
  gchar *passwd;
  GHashTable *auth_params;
201
202
203

  DecodeCtx ctx;
  DecodeCtx *ctxp;
Wim Taymans's avatar
Wim Taymans committed
204
205
206

  gchar *proxy_host;
  guint proxy_port;
207
208
};

209
210
211
212
213
214
215
216
217
218
enum
{
  STATE_START = 0,
  STATE_DATA_HEADER,
  STATE_DATA_BODY,
  STATE_READ_LINES,
  STATE_END,
  STATE_LAST
};

219
220
221
222
223
224
225
enum
{
  READ_AHEAD_EOH = -1,          /* end of headers */
  READ_AHEAD_CRLF = -2,
  READ_AHEAD_CRLFCR = -3
};

226
227
228
229
/* a structure for constructing RTSPMessages */
typedef struct
{
  gint state;
230
  GstRTSPResult status;
231
232
233
234
235
236
237
238
239
240
241
242
  guint8 buffer[4096];
  guint offset;

  guint line;
  guint8 *body_data;
  glong body_len;
} GstRTSPBuilder;

static void
build_reset (GstRTSPBuilder * builder)
{
  g_free (builder->body_data);
243
  memset (builder, 0, sizeof (GstRTSPBuilder));
244
245
}

246
247
248
/**
 * gst_rtsp_connection_create:
 * @url: a #GstRTSPUrl 
Wim Taymans's avatar
Wim Taymans committed
249
 * @conn: storage for a #GstRTSPConnection
250
251
252
253
254
 *
 * Create a newly allocated #GstRTSPConnection from @url and store it in @conn.
 * The connection will not yet attempt to connect to @url, use
 * gst_rtsp_connection_connect().
 *
255
256
 * A copy of @url will be made.
 *
257
258
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 */
259
GstRTSPResult
260
gst_rtsp_connection_create (const GstRTSPUrl * url, GstRTSPConnection ** conn)
261
262
{
  GstRTSPConnection *newconn;
263
264
265
266
#ifdef G_OS_WIN32
  WSADATA w;
  int error;
#endif
267
268
269

  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);

270
271
272
273
274
275
276
277
278
279
#ifdef G_OS_WIN32
  error = WSAStartup (0x0202, &w);

  if (error)
    goto startup_error;

  if (w.wVersion != 0x0202)
    goto version_error;
#endif

280
281
  newconn = g_new0 (GstRTSPConnection, 1);

282
  if ((newconn->fdset = gst_poll_new (TRUE)) == NULL)
283
    goto no_fdset;
284

285
  newconn->url = gst_rtsp_url_copy (url);
286
287
  newconn->fd0.fd = -1;
  newconn->fd1.fd = -1;
288
  newconn->timer = g_timer_new ();
289
  newconn->timeout = 60;
290
  newconn->cseq = 1;
291
292
293
294

  newconn->auth_method = GST_RTSP_AUTH_NONE;
  newconn->username = NULL;
  newconn->passwd = NULL;
295
  newconn->auth_params = NULL;
296
297
298
299
300
301

  *conn = newconn;

  return GST_RTSP_OK;

  /* ERRORS */
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#ifdef G_OS_WIN32
startup_error:
  {
    g_warning ("Error %d on WSAStartup", error);
    return GST_RTSP_EWSASTART;
  }
version_error:
  {
    g_warning ("Windows sockets are not version 0x202 (current 0x%x)",
        w.wVersion);
    WSACleanup ();
    return GST_RTSP_EWSAVERSION;
  }
#endif
316
no_fdset:
317
318
  {
    g_free (newconn);
319
320
321
#ifdef G_OS_WIN32
    WSACleanup ();
#endif
322
323
324
325
    return GST_RTSP_ESYS;
  }
}

Wim Taymans's avatar
Wim Taymans committed
326
/**
327
328
329
330
331
 * gst_rtsp_connection_create_from_fd:
 * @fd: a file descriptor
 * @ip: the IP address of the other end
 * @port: the port used by the other end
 * @initial_buffer: data already read from @fd
Wim Taymans's avatar
Wim Taymans committed
332
333
 * @conn: storage for a #GstRTSPConnection
 *
334
335
336
 * Create a new #GstRTSPConnection for handling communication on the existing
 * file descriptor @fd. The @initial_buffer contains any data already read from
 * @fd which should be used before starting to read new data.
Wim Taymans's avatar
Wim Taymans committed
337
338
339
 *
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 *
340
 * Since: 0.10.25
Wim Taymans's avatar
Wim Taymans committed
341
342
 */
GstRTSPResult
343
344
gst_rtsp_connection_create_from_fd (gint fd, const gchar * ip, guint16 port,
    const gchar * initial_buffer, GstRTSPConnection ** conn)
Wim Taymans's avatar
Wim Taymans committed
345
{
346
  GstRTSPConnection *newconn = NULL;
Wim Taymans's avatar
Wim Taymans committed
347
  GstRTSPUrl *url;
348
349
350
#ifdef G_OS_WIN32
  gulong flags = 1;
#endif
351
  GstRTSPResult res;
Wim Taymans's avatar
Wim Taymans committed
352

353
354
355
  g_return_val_if_fail (fd >= 0, GST_RTSP_EINVAL);
  g_return_val_if_fail (ip != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
356

Wim Taymans's avatar
Wim Taymans committed
357
358
359
360
361
362
363
364
365
  /* set to non-blocking mode so that we can cancel the communication */
#ifndef G_OS_WIN32
  fcntl (fd, F_SETFL, O_NONBLOCK);
#else
  ioctlsocket (fd, FIONBIO, &flags);
#endif /* G_OS_WIN32 */

  /* create a url for the client address */
  url = g_new0 (GstRTSPUrl, 1);
366
  url->host = g_strdup (ip);
367
  url->port = port;
Wim Taymans's avatar
Wim Taymans committed
368
369

  /* now create the connection object */
370
  GST_RTSP_CHECK (gst_rtsp_connection_create (url, &newconn), newconn_failed);
371
372
  gst_rtsp_url_free (url);

373
  ADD_POLLFD (newconn->fdset, &newconn->fd0, fd);
374

375
  /* both read and write initially */
376
377
  newconn->readfd = &newconn->fd0;
  newconn->writefd = &newconn->fd0;
Wim Taymans's avatar
Wim Taymans committed
378

379
380
381
382
  newconn->ip = g_strdup (ip);

  newconn->initial_buffer = g_strdup (initial_buffer);

Wim Taymans's avatar
Wim Taymans committed
383
384
385
386
  *conn = newconn;

  return GST_RTSP_OK;

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
432
433
434
435
436
437
438
439
440
  /* ERRORS */
newconn_failed:
  {
    gst_rtsp_url_free (url);
    return res;
  }
}

/**
 * gst_rtsp_connection_accept:
 * @sock: a socket
 * @conn: storage for a #GstRTSPConnection
 *
 * Accept a new connection on @sock and create a new #GstRTSPConnection for
 * handling communication on new socket.
 *
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 *
 * Since: 0.10.23
 */
GstRTSPResult
gst_rtsp_connection_accept (gint sock, GstRTSPConnection ** conn)
{
  int fd;
  union gst_sockaddr sa;
  socklen_t slen = sizeof (sa);
  gchar ip[INET6_ADDRSTRLEN];
  guint16 port;

  g_return_val_if_fail (sock >= 0, GST_RTSP_EINVAL);
  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);

  memset (&sa, 0, slen);

#ifndef G_OS_WIN32
  fd = accept (sock, &sa.sa, &slen);
#else
  fd = accept (sock, &sa.sa, (gint *) & slen);
#endif /* G_OS_WIN32 */
  if (fd == -1)
    goto accept_failed;

  if (getnameinfo (&sa.sa, slen, ip, sizeof (ip), NULL, 0, NI_NUMERICHOST) != 0)
    goto getnameinfo_failed;

  if (sa.sa.sa_family == AF_INET)
    port = sa.sa_in.sin_port;
  else if (sa.sa.sa_family == AF_INET6)
    port = sa.sa_in6.sin6_port;
  else
    goto wrong_family;

  return gst_rtsp_connection_create_from_fd (fd, ip, port, NULL, conn);

Wim Taymans's avatar
Wim Taymans committed
441
442
443
444
445
  /* ERRORS */
accept_failed:
  {
    return GST_RTSP_ESYS;
  }
446
447
448
getnameinfo_failed:
wrong_family:
  {
449
    CLOSE_SOCKET (fd);
450
451
    return GST_RTSP_ERROR;
  }
Wim Taymans's avatar
Wim Taymans committed
452
453
}

Wim Taymans's avatar
Wim Taymans committed
454
static gchar *
455
do_resolve (const gchar * host)
456
{
457
458
459
460
  static gchar ip[INET6_ADDRSTRLEN];
  struct addrinfo *aires;
  struct addrinfo *ai;
  gint aierr;
461

462
463
464
  aierr = getaddrinfo (host, NULL, NULL, &aires);
  if (aierr != 0)
    goto no_addrinfo;
465

466
467
468
469
  for (ai = aires; ai; ai = ai->ai_next) {
    if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
      break;
    }
470
  }
471
472
473
474
475
476
477
478
479
480
  if (ai == NULL)
    goto no_family;

  aierr = getnameinfo (ai->ai_addr, ai->ai_addrlen, ip, sizeof (ip), NULL, 0,
      NI_NUMERICHOST | NI_NUMERICSERV);
  if (aierr != 0)
    goto no_address;

  freeaddrinfo (aires);

Wim Taymans's avatar
Wim Taymans committed
481
  return g_strdup (ip);
482

483
  /* ERRORS */
484
485
486
487
488
489
no_addrinfo:
  {
    GST_ERROR ("no addrinfo found for %s: %s", host, gai_strerror (aierr));
    return NULL;
  }
no_family:
490
  {
491
492
    GST_ERROR ("no family found for %s", host);
    freeaddrinfo (aires);
493
494
    return NULL;
  }
495
no_address:
496
  {
497
498
    GST_ERROR ("no address found for %s: %s", host, gai_strerror (aierr));
    freeaddrinfo (aires);
499
500
501
502
503
504
505
506
507
    return NULL;
  }
}

static GstRTSPResult
do_connect (const gchar * ip, guint16 port, GstPollFD * fdout,
    GstPoll * fdset, GTimeVal * timeout)
{
  gint fd;
508
509
510
511
512
  struct addrinfo hints;
  struct addrinfo *aires;
  struct addrinfo *ai;
  gint aierr;
  gchar service[NI_MAXSERV];
513
514
515
516
517
518
519
  gint ret;
#ifdef G_OS_WIN32
  unsigned long flags = 1;
#endif /* G_OS_WIN32 */
  GstClockTime to;
  gint retval;

520
521
522
523
524
525
526
527
528
529
  memset (&hints, 0, sizeof hints);
  hints.ai_flags = AI_NUMERICHOST;
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  g_snprintf (service, sizeof (service) - 1, "%hu", port);
  service[sizeof (service) - 1] = '\0';

  aierr = getaddrinfo (ip, service, &hints, &aires);
  if (aierr != 0)
    goto no_addrinfo;
530

531
532
533
534
535
536
537
538
539
  for (ai = aires; ai; ai = ai->ai_next) {
    if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
      break;
    }
  }
  if (ai == NULL)
    goto no_family;

  fd = socket (ai->ai_family, SOCK_STREAM, 0);
540
  if (fd == -1)
541
    goto no_socket;
542
543
544
545
546
547
548
549

  /* set to non-blocking mode so that we can cancel the connect */
#ifndef G_OS_WIN32
  fcntl (fd, F_SETFL, O_NONBLOCK);
#else
  ioctlsocket (fd, FIONBIO, &flags);
#endif /* G_OS_WIN32 */

550
  /* add the socket to our fdset */
551
  ADD_POLLFD (fdset, fdout, fd);
552

553
  /* we are going to connect ASYNC now */
554
  ret = connect (fd, ai->ai_addr, ai->ai_addrlen);
555
556
  if (ret == 0)
    goto done;
557
  if (!ERRNO_IS_EINPROGRESS)
558
559
560
561
    goto sys_error;

  /* wait for connect to complete up to the specified timeout or until we got
   * interrupted. */
562
  gst_poll_fd_ctl_write (fdset, fdout, TRUE);
563

564
  to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
565
566

  do {
567
    retval = gst_poll_wait (fdset, to);
568
  } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
569
570
571
572
573
574

  if (retval == 0)
    goto timeout;
  else if (retval == -1)
    goto sys_error;

575
  /* we can still have an error connecting on windows */
576
  if (gst_poll_fd_has_error (fdset, fdout)) {
577
    socklen_t len = sizeof (errno);
578
#ifndef G_OS_WIN32
579
    getsockopt (fd, SOL_SOCKET, SO_ERROR, &errno, &len);
580
#else
581
    getsockopt (fd, SOL_SOCKET, SO_ERROR, (char *) &errno, &len);
582
#endif
583
    goto sys_error;
584
  }
585

586
  gst_poll_fd_ignored (fdset, fdout);
587

588
done:
589
  freeaddrinfo (aires);
590
591
592

  return GST_RTSP_OK;

593
  /* ERRORS */
594
595
596
597
598
599
600
601
602
603
604
no_addrinfo:
  {
    GST_ERROR ("no addrinfo found for %s: %s", ip, gai_strerror (aierr));
    return GST_RTSP_ERROR;
  }
no_family:
  {
    GST_ERROR ("no family found for %s", ip);
    freeaddrinfo (aires);
    return GST_RTSP_ERROR;
  }
605
606
607
no_socket:
  {
    GST_ERROR ("no socket %d (%s)", errno, g_strerror (errno));
608
    freeaddrinfo (aires);
609
610
    return GST_RTSP_ESYS;
  }
611
612
sys_error:
  {
613
    GST_ERROR ("system error %d (%s)", errno, g_strerror (errno));
614
    REMOVE_POLLFD (fdset, fdout);
615
    freeaddrinfo (aires);
616
617
    return GST_RTSP_ESYS;
  }
618
619
620
621
timeout:
  {
    GST_ERROR ("timeout");
    REMOVE_POLLFD (fdset, fdout);
622
    freeaddrinfo (aires);
623
624
625
626
627
628
629
630
631
    return GST_RTSP_ETIMEOUT;
  }
}

static GstRTSPResult
setup_tunneling (GstRTSPConnection * conn, GTimeVal * timeout)
{
  gint i;
  GstRTSPResult res;
632
633
634
  gchar *ip;
  gchar *uri;
  gchar *value;
Wim Taymans's avatar
Wim Taymans committed
635
  guint16 port, url_port;
636
  GstRTSPUrl *url;
Wim Taymans's avatar
Wim Taymans committed
637
  gchar *hostparam;
638
639
  GstRTSPMessage *msg;
  GstRTSPMessage response;
Wim Taymans's avatar
Wim Taymans committed
640
  gboolean old_http;
641
642
643

  memset (&response, 0, sizeof (response));
  gst_rtsp_message_init (&response);
644
645

  /* create a random sessionid */
646
647
648
649
650
  for (i = 0; i < TUNNELID_LEN; i++)
    conn->tunnelid[i] = g_random_int_range ('a', 'z');
  conn->tunnelid[TUNNELID_LEN - 1] = '\0';

  url = conn->url;
Wim Taymans's avatar
Wim Taymans committed
651
652
653
654
  /* get the port from the url */
  gst_rtsp_url_get_port (url, &url_port);

  if (conn->proxy_host) {
655
656
657
    uri = g_strdup_printf ("http://%s:%d%s%s%s", url->host, url_port,
        url->abspath, url->query ? "?" : "", url->query ? url->query : "");
    hostparam = g_strdup_printf ("%s:%d", url->host, url_port);
Wim Taymans's avatar
Wim Taymans committed
658
659
660
    ip = conn->proxy_host;
    port = conn->proxy_port;
  } else {
661
662
    uri = g_strdup_printf ("%s%s%s", url->abspath, url->query ? "?" : "",
        url->query ? url->query : "");
Wim Taymans's avatar
Wim Taymans committed
663
664
665
666
    hostparam = NULL;
    ip = conn->ip;
    port = url_port;
  }
667

668
669
670
671
672
673
674
675
676
677
678
679
680
  /* create the GET request for the read connection */
  GST_RTSP_CHECK (gst_rtsp_message_new_request (&msg, GST_RTSP_GET, uri),
      no_message);
  msg->type = GST_RTSP_MESSAGE_HTTP_REQUEST;

  if (hostparam != NULL)
    gst_rtsp_message_add_header (msg, GST_RTSP_HDR_HOST, hostparam);
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SESSIONCOOKIE,
      conn->tunnelid);
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_ACCEPT,
      "application/x-rtsp-tunnelled");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-cache");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
681
682
683
684

  /* we start by writing to this fd */
  conn->writefd = &conn->fd0;

685
686
687
688
689
690
691
692
693
694
695
  /* we need to temporarily set conn->tunneled to FALSE to prevent the HTTP
   * request from being base64 encoded */
  conn->tunneled = FALSE;
  GST_RTSP_CHECK (gst_rtsp_connection_send (conn, msg, timeout), write_failed);
  gst_rtsp_message_free (msg);
  conn->tunneled = TRUE;

  /* receive the response to the GET request */
  /* we need to temporarily set manual_http to TRUE since
   * gst_rtsp_connection_receive() will treat the HTTP response as a parsing
   * failure otherwise */
Wim Taymans's avatar
Wim Taymans committed
696
  old_http = conn->manual_http;
697
698
699
  conn->manual_http = TRUE;
  GST_RTSP_CHECK (gst_rtsp_connection_receive (conn, &response, timeout),
      read_failed);
Wim Taymans's avatar
Wim Taymans committed
700
  conn->manual_http = old_http;
701
702
703
704
705
706
707
708
709
710
711
712
713

  if (response.type != GST_RTSP_MESSAGE_HTTP_RESPONSE ||
      response.type_data.response.code != GST_RTSP_STS_OK)
    goto wrong_result;

  if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_X_SERVER_IP_ADDRESS,
          &value, 0) != GST_RTSP_OK) {
    if (conn->proxy_host) {
      /* if we use a proxy we need to change the destination url */
      g_free (url->host);
      url->host = g_strdup (value);
      g_free (hostparam);
      hostparam = g_strdup_printf ("%s:%d", url->host, url_port);
714
    } else {
715
716
717
718
719
      /* and resolve the new ip address */
      if (!(ip = do_resolve (conn->ip)))
        goto not_resolved;
      g_free (conn->ip);
      conn->ip = ip;
720
721
722
723
724
725
726
727
728
729
730
    }
  }

  /* connect to the host/port */
  res = do_connect (ip, port, &conn->fd1, conn->fdset, timeout);
  if (res != GST_RTSP_OK)
    goto connect_failed;

  /* this is now our writing socket */
  conn->writefd = &conn->fd1;

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
  /* create the POST request for the write connection */
  GST_RTSP_CHECK (gst_rtsp_message_new_request (&msg, GST_RTSP_POST, uri),
      no_message);
  msg->type = GST_RTSP_MESSAGE_HTTP_REQUEST;

  if (hostparam != NULL)
    gst_rtsp_message_add_header (msg, GST_RTSP_HDR_HOST, hostparam);
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SESSIONCOOKIE,
      conn->tunnelid);
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_ACCEPT,
      "application/x-rtsp-tunnelled");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-cache");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_EXPIRES,
      "Sun, 9 Jan 1972 00:00:00 GMT");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CONTENT_LENGTH, "32767");

  /* we need to temporarily set conn->tunneled to FALSE to prevent the HTTP
   * request from being base64 encoded */
  conn->tunneled = FALSE;
  GST_RTSP_CHECK (gst_rtsp_connection_send (conn, msg, timeout), write_failed);
  gst_rtsp_message_free (msg);
  conn->tunneled = TRUE;
754

755
exit:
756
  gst_rtsp_message_unset (&response);
757
  g_free (hostparam);
758
  g_free (uri);
759

760
761
762
  return res;

  /* ERRORS */
763
no_message:
764
  {
765
    GST_ERROR ("failed to create request (%d)", res);
766
    goto exit;
767
  }
768
write_failed:
769
  {
770
771
772
    GST_ERROR ("write failed (%d)", res);
    gst_rtsp_message_free (msg);
    conn->tunneled = TRUE;
773
    goto exit;
774
  }
775
read_failed:
776
  {
777
778
    GST_ERROR ("read failed (%d)", res);
    conn->manual_http = FALSE;
779
    goto exit;
780
781
782
  }
wrong_result:
  {
783
784
    GST_ERROR ("got failure response %d %s", response.type_data.response.code,
        response.type_data.response.reason);
785
786
    res = GST_RTSP_ERROR;
    goto exit;
787
788
789
790
  }
not_resolved:
  {
    GST_ERROR ("could not resolve %s", conn->ip);
791
792
    res = GST_RTSP_ENET;
    goto exit;
793
794
795
796
  }
connect_failed:
  {
    GST_ERROR ("failed to connect");
797
    goto exit;
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
  }
}

/**
 * gst_rtsp_connection_connect:
 * @conn: a #GstRTSPConnection 
 * @timeout: a #GTimeVal timeout
 *
 * Attempt to connect to the url of @conn made with
 * gst_rtsp_connection_create(). If @timeout is #NULL this function can block
 * forever. If @timeout contains a valid timeout, this function will return
 * #GST_RTSP_ETIMEOUT after the timeout expired.
 *
 * This function can be cancelled with gst_rtsp_connection_flush().
 *
 * Returns: #GST_RTSP_OK when a connection could be made.
 */
GstRTSPResult
gst_rtsp_connection_connect (GstRTSPConnection * conn, GTimeVal * timeout)
{
  GstRTSPResult res;
Wim Taymans's avatar
Wim Taymans committed
819
  gchar *ip;
820
821
822
823
824
825
826
827
828
  guint16 port;
  GstRTSPUrl *url;

  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (conn->url != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (conn->fd0.fd < 0, GST_RTSP_EINVAL);

  url = conn->url;

Wim Taymans's avatar
Wim Taymans committed
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
  if (conn->proxy_host && conn->tunneled) {
    if (!(ip = do_resolve (conn->proxy_host))) {
      GST_ERROR ("could not resolve %s", conn->proxy_host);
      goto not_resolved;
    }
    port = conn->proxy_port;
    g_free (conn->proxy_host);
    conn->proxy_host = ip;
  } else {
    if (!(ip = do_resolve (url->host))) {
      GST_ERROR ("could not resolve %s", url->host);
      goto not_resolved;
    }
    /* get the port from the url */
    gst_rtsp_url_get_port (url, &port);
844

Wim Taymans's avatar
Wim Taymans committed
845
846
847
    g_free (conn->ip);
    conn->ip = ip;
  }
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866

  /* connect to the host/port */
  res = do_connect (ip, port, &conn->fd0, conn->fdset, timeout);
  if (res != GST_RTSP_OK)
    goto connect_failed;

  /* this is our read URL */
  conn->readfd = &conn->fd0;

  if (conn->tunneled) {
    res = setup_tunneling (conn, timeout);
    if (res != GST_RTSP_OK)
      goto tunneling_failed;
  } else {
    conn->writefd = &conn->fd0;
  }

  return GST_RTSP_OK;

867
868
869
870
not_resolved:
  {
    return GST_RTSP_ENET;
  }
871
connect_failed:
872
  {
873
874
    GST_ERROR ("failed to connect");
    return res;
875
  }
876
tunneling_failed:
877
  {
878
879
    GST_ERROR ("failed to setup tunneling");
    return res;
880
881
882
  }
}

883
884
885
886
static void
auth_digest_compute_hex_urp (const gchar * username,
    const gchar * realm, const gchar * password, gchar hex_urp[33])
{
887
888
889
890
891
892
893
894
895
896
897
898
899
900
  GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
  const gchar *digest_string;

  g_checksum_update (md5_context, (const guchar *) username, strlen (username));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) realm, strlen (realm));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) password, strlen (password));
  digest_string = g_checksum_get_string (md5_context);

  memset (hex_urp, 0, 33);
  memcpy (hex_urp, digest_string, strlen (digest_string));

  g_checksum_free (md5_context);
901
902
903
904
905
906
907
}

static void
auth_digest_compute_response (const gchar * method,
    const gchar * uri, const gchar * hex_a1, const gchar * nonce,
    gchar response[33])
{
908
909
910
  char hex_a2[33] = { 0, };
  GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
  const gchar *digest_string;
911
912

  /* compute A2 */
913
914
915
916
917
  g_checksum_update (md5_context, (const guchar *) method, strlen (method));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) uri, strlen (uri));
  digest_string = g_checksum_get_string (md5_context);
  memcpy (hex_a2, digest_string, strlen (digest_string));
918
919

  /* compute KD */
920
921
922
923
924
925
926
927
  g_checksum_reset (md5_context);
  g_checksum_update (md5_context, (const guchar *) hex_a1, strlen (hex_a1));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) nonce, strlen (nonce));
  g_checksum_update (md5_context, (const guchar *) ":", 1);

  g_checksum_update (md5_context, (const guchar *) hex_a2, 32);
  digest_string = g_checksum_get_string (md5_context);
928
  memset (response, 0, 33);
929
930
931
  memcpy (response, digest_string, strlen (digest_string));

  g_checksum_free (md5_context);
932
933
}

934
935
936
937
938
static void
add_auth_header (GstRTSPConnection * conn, GstRTSPMessage * message)
{
  switch (conn->auth_method) {
    case GST_RTSP_AUTH_BASIC:{
Wim Taymans's avatar
Wim Taymans committed
939
940
941
942
943
944
945
      gchar *user_pass;
      gchar *user_pass64;
      gchar *auth_string;

      user_pass = g_strdup_printf ("%s:%s", conn->username, conn->passwd);
      user_pass64 = g_base64_encode ((guchar *) user_pass, strlen (user_pass));
      auth_string = g_strdup_printf ("Basic %s", user_pass64);
946

947
      gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
948
949
950
951
952
953
          auth_string);

      g_free (user_pass);
      g_free (user_pass64);
      break;
    }
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
    case GST_RTSP_AUTH_DIGEST:{
      gchar response[33], hex_urp[33];
      gchar *auth_string, *auth_string2;
      gchar *realm;
      gchar *nonce;
      gchar *opaque;
      const gchar *uri;
      const gchar *method;

      /* we need to have some params set */
      if (conn->auth_params == NULL)
        break;

      /* we need the realm and nonce */
      realm = (gchar *) g_hash_table_lookup (conn->auth_params, "realm");
      nonce = (gchar *) g_hash_table_lookup (conn->auth_params, "nonce");
      if (realm == NULL || nonce == NULL)
        break;

      auth_digest_compute_hex_urp (conn->username, realm, conn->passwd,
          hex_urp);

      method = gst_rtsp_method_as_text (message->type_data.request.method);
      uri = message->type_data.request.uri;

      /* Assume no qop, algorithm=md5, stale=false */
      /* For algorithm MD5, a1 = urp. */
      auth_digest_compute_response (method, uri, hex_urp, nonce, response);
      auth_string = g_strdup_printf ("Digest username=\"%s\", "
          "realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
          conn->username, realm, nonce, uri, response);

      opaque = (gchar *) g_hash_table_lookup (conn->auth_params, "opaque");
      if (opaque) {
        auth_string2 = g_strdup_printf ("%s, opaque=\"%s\"", auth_string,
            opaque);
        g_free (auth_string);
        auth_string = auth_string2;
      }
993
      gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
994
995
996
          auth_string);
      break;
    }
997
998
999
1000
1001
1002
1003
    default:
      /* Nothing to do */
      break;
  }
}

static void
1004
gen_date_string (gchar * date_string, guint len)
1005
1006
{
  GTimeVal tv;
1007
  time_t t;
1008
1009
1010
1011
#ifdef HAVE_GMTIME_R
  struct tm tm_;
#endif

1012
  g_get_current_time (&tv);
1013
  t = (time_t) tv.tv_sec;
1014
1015

#ifdef HAVE_GMTIME_R
1016
  strftime (date_string, len, "%a, %d %b %Y %H:%M:%S GMT", gmtime_r (&t, &tm_));
1017
#else
1018
  strftime (date_string, len, "%a, %d %b %Y %H:%M:%S GMT", gmtime (&t));
1019
#endif
1020
1021
}

1022
1023
1024
1025
1026
static GstRTSPResult
write_bytes (gint fd, const guint8 * buffer, guint * idx, guint size)
{
  guint left;

1027
  if (G_UNLIKELY (*idx > size))
1028
1029
1030
1031
1032
1033
1034
1035
    return GST_RTSP_ERROR;

  left = size - *idx;

  while (left) {
    gint r;

    r = WRITE_SOCKET (fd, &buffer[*idx], left);
1036
    if (G_UNLIKELY (r == 0)) {
1037
      return GST_RTSP_EINTR;
1038
    } else if (G_UNLIKELY (r < 0)) {
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
      if (ERRNO_IS_EAGAIN)
        return GST_RTSP_EINTR;
      if (!ERRNO_IS_EINTR)
        return GST_RTSP_ESYS;
    } else {
      left -= r;
      *idx += r;
    }
  }
  return GST_RTSP_OK;
}

1051
static gint
1052
fill_raw_bytes (GstRTSPConnection * conn, guint8 * buffer, guint size)
1053
1054
1055
{
  gint out = 0;

1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
  if (G_UNLIKELY (conn->initial_buffer != NULL)) {
    gsize left = strlen (&conn->initial_buffer[conn->initial_buffer_offset]);

    out = MIN (left, size);
    memcpy (buffer, &conn->initial_buffer[conn->initial_buffer_offset], out);

    if (left == (gsize) out) {
      g_free (conn->initial_buffer);
      conn->initial_buffer = NULL;
      conn->initial_buffer_offset = 0;
    } else
      conn->initial_buffer_offset += out;
  }

  if (G_LIKELY (size > (guint) out)) {
    gint r;

    r = READ_SOCKET (conn->readfd->fd, &buffer[out], size - out);
    if (r <= 0) {
      if (out == 0)
        out = r;
    } else
      out += r;
  }

  return out;
}

static gint
fill_bytes (GstRTSPConnection * conn, guint8 * buffer, guint size)
{
  DecodeCtx *ctx = conn->ctxp;
  gint out = 0;

1090
1091
  if (ctx) {
    while (size > 0) {
1092
1093
1094
      guint8 in[sizeof (ctx->out) * 4 / 3];
      gint r;

1095
      while (size > 0 && ctx->cout < ctx->coutl) {
1096
        /* we have some leftover bytes */
1097
        *buffer++ = ctx->out[ctx->cout++];
1098
1099
1100
        size--;
        out++;
      }
1101
1102

      /* got what we needed? */
1103
1104
1105
1106
      if (size == 0)
        break;

      /* try to read more bytes */
1107
      r = fill_raw_bytes (conn, in, sizeof (in));
1108
1109
1110
1111
1112
1113
      if (r <= 0) {
        if (out == 0)
          out = r;
        break;
      }

1114
1115
1116
1117
      ctx->cout = 0;
      ctx->coutl =
          g_base64_decode_step ((gchar *) in, r, ctx->out, &ctx->state,
          &ctx->save);
1118
1119
    }
  } else {
1120
    out = fill_raw_bytes (conn, buffer, size);
1121
1122
1123
1124
1125
  }

  return out;
}

1126
static GstRTSPResult
1127
read_bytes (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
1128
1129
1130
{
  guint left;

1131
  if (G_UNLIKELY (*idx > size))
1132
1133
1134
1135
1136
1137
1138
    return GST_RTSP_ERROR;

  left = size - *idx;

  while (left) {
    gint r;

1139
    r = fill_bytes (conn, &buffer[*idx], left);
1140
    if (G_UNLIKELY (r == 0)) {
1141
      return GST_RTSP_EEOF;
1142
    } else if (G_UNLIKELY (r < 0)) {
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
      if (ERRNO_IS_EAGAIN)
        return GST_RTSP_EINTR;
      if (!ERRNO_IS_EINTR)
        return GST_RTSP_ESYS;
    } else {
      left -= r;
      *idx += r;
    }
  }
  return GST_RTSP_OK;
}

1155
1156
1157
1158
1159
1160
/* The code below tries to handle clients using \r, \n or \r\n to indicate the
 * end of a line. It even does its best to handle clients which mix them (even
 * though this is a really stupid idea (tm).) It also handles Line White Space
 * (LWS), where a line end followed by whitespace is considered LWS. This is
 * the method used in RTSP (and HTTP) to break long lines.
 */
1161
static GstRTSPResult
1162
read_line (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
1163
1164
1165
1166
1167
{
  while (TRUE) {
    guint8 c;
    gint r;

1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
    if (conn->read_ahead == READ_AHEAD_EOH) {
      /* the last call to read_line() already determined that we have reached
       * the end of the headers, so convey that information now */
      conn->read_ahead = 0;
      break;
    } else if (conn->read_ahead == READ_AHEAD_CRLF) {
      /* the last call to read_line() left off after having read \r\n */
      c = '\n';
    } else if (conn->read_ahead == READ_AHEAD_CRLFCR) {
      /* the last call to read_line() left off after having read \r\n\r */
      c = '\r';
    } else if (conn->read_ahead != 0) {
      /* the last call to read_line() left us with a character to start with */
      c = (guint8) conn->read_ahead;
      conn->read_ahead = 0;
1183
    } else {
1184
1185
1186
1187
1188
1189
1190
1191
1192
      /* read the next character */
      r = fill_bytes (conn, &c, 1);
      if (G_UNLIKELY (r == 0)) {
        return GST_RTSP_EEOF;
      } else if (G_UNLIKELY (r < 0)) {
        if (ERRNO_IS_EAGAIN)
          return GST_RTSP_EINTR;
        if (!ERRNO_IS_EINTR)
          return GST_RTSP_ESYS;
1193
        continue;
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
      }
    }

    /* special treatment of line endings */
    if (c == '\r' || c == '\n') {
      guint8 read_ahead;

    retry:
      /* need to read ahead one more character to know what to do... */
      r = fill_bytes (conn, &read_ahead, 1);
      if (G_UNLIKELY (r == 0)) {
        return GST_RTSP_EEOF;
      } else if (G_UNLIKELY (r < 0)) {
        if (ERRNO_IS_EAGAIN) {
          /* remember the original character we read and try again next time */
          if (conn->read_ahead == 0)
            conn->read_ahead = c;
          return GST_RTSP_EINTR;
        }
        if (!ERRNO_IS_EINTR)
          return GST_RTSP_ESYS;
        goto retry;
      }
1217

1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
      if (read_ahead == ' ' || read_ahead == '\t') {
        if (conn->read_ahead == READ_AHEAD_CRLFCR) {
          /* got \r\n\r followed by whitespace, treat it as a normal line
           * followed by one starting with LWS */
          conn->read_ahead = read_ahead;
          break;
        } else {
          /* got LWS, change the line ending to a space and continue */
          c = ' ';
          conn->read_ahead = read_ahead;
        }
      } else if (conn->read_ahead == READ_AHEAD_CRLFCR) {
        if (read_ahead == '\r' || read_ahead == '\n') {
          /* got \r\n\r\r or \r\n\r\n, treat it as the end of the headers */
          conn->read_ahead = READ_AHEAD_EOH;
          break;
        } else {
          /* got \r\n\r followed by something else, this is not really
           * supported since we have probably just eaten the first character
           * of the body or the next message, so just ignore the second \r
           * and live with it... */
          conn->read_ahead = read_ahead;
          break;
        }
      } else if (conn->read_ahead == READ_AHEAD_CRLF) {
        if (read_ahead == '\r') {
          /* got \r\n\r so far, need one more character... */
          conn->read_ahead = READ_AHEAD_CRLFCR;
          goto retry;
        } else if (read_ahead == '\n') {
          /* got \r\n\n, treat it as the end of the headers */
          conn->read_ahead = READ_AHEAD_EOH;
          break;
        } else {
          /* found the end of a line, keep read_ahead for the next line */
          conn->read_ahead = read_ahead;
          break;
        }
      } else if (c == read_ahead) {
        /* got double \r or \n, treat it as the end of the headers */
        conn->read_ahead = READ_AHEAD_EOH;
        break;
      } else if (c == '\r' && read_ahead == '\n') {
        /* got \r\n so far, still need more to know what to do... */
        conn->read_ahead = READ_AHEAD_CRLF;
        goto retry;
      } else {
        /* found the end of a line, keep read_ahead for the next line */
        conn->read_ahead = read_ahead;
        break;
      }
1269
    }
1270
1271
1272

    if (G_LIKELY (*idx < size - 1))
      buffer[(*idx)++] = c;
1273
1274
1275
1276
1277
1278
  }
  buffer[*idx] = '\0';

  return GST_RTSP_OK;
}

1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
/**
 * gst_rtsp_connection_write:
 * @conn: a #GstRTSPConnection
 * @data: the data to write
 * @size: the size of @data
 * @timeout: a timeout value or #NULL
 *
 * Attempt to write @size bytes of @data to the connected @conn, blocking up to
 * the specified @timeout. @timeout can be #NULL, in which case this function
 * might block forever.
 * 
1290
 * This function can be cancelled with gst_rtsp_connection_flush().
1291
1292
1293
 *
 * Returns: #GST_RTSP_OK on success.
 */
1294
1295
1296
1297
GstRTSPResult
gst_rtsp_connection_write (GstRTSPConnection * conn, const guint8 * data,
    guint size, GTimeVal * timeout)
{
1298
  guint offset;
1299
  gint retval;
1300
  GstClockTime to;
1301
  GstRTSPResult res;
1302
1303
1304

  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (data != NULL || size == 0, GST_RTSP_EINVAL);
1305
  g_return_val_if_fail (conn->writefd != NULL, GST_RTSP_EINVAL);
1306

1307
  gst_poll_set_controllable (conn->fdset, TRUE);
1308
1309
  gst_poll_fd_ctl_write (conn->fdset, conn->writefd, TRUE);
  gst_poll_fd_ctl_read (conn->fdset, conn->readfd, FALSE);
1310
  /* clear all previous poll results */
1311
1312
  gst_poll_fd_ignored (conn->fdset, conn->writefd);
  gst_poll_fd_ignored (conn->fdset, conn->readfd);
1313

1314
  to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
1315

1316
  offset = 0;
1317

1318
1319
  while (TRUE) {
    /* try to write */
1320
    res = write_bytes (conn->writefd->fd, data, &offset, size);
1321
    if (G_LIKELY (res == GST_RTSP_OK))
1322
      break;
1323
    if (G_UNLIKELY (res != GST_RTSP_EINTR))
1324
      goto write_error;
1325

1326
    /* not all is written, wait until we can write more */
1327
    do {
1328
      retval = gst_poll_wait (conn->fdset, to);
1329
    } while (retval == -1 && (errno == EINTR || errno