gstrtspconnection.c 94.1 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
118
119
120
121
122
#ifdef MSG_NOSIGNAL
#define SEND_FLAGS MSG_NOSIGNAL
#else
#define SEND_FLAGS 0
#endif

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

143
144
145
146
#define ADD_POLLFD(fdset, pfd, fd)        \
G_STMT_START {                            \
  (pfd)->fd = fd;                         \
  gst_poll_add_fd (fdset, pfd);           \
147
148
} G_STMT_END

149
150
151
152
153
154
155
156
#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;                        \
  }                                        \
157
158
} G_STMT_END

159
160
161
162
163
164
165
166
167
168
typedef enum
{
  TUNNEL_STATE_NONE,
  TUNNEL_STATE_GET,
  TUNNEL_STATE_POST,
  TUNNEL_STATE_COMPLETE
} GstRTSPTunnelState;

#define TUNNELID_LEN   24

169
170
171
172
173
174
175
struct _GstRTSPConnection
{
  /*< private > */
  /* URL for the connection */
  GstRTSPUrl *url;

  /* connection state */
176
177
178
179
180
181
  GstPollFD fd0;
  GstPollFD fd1;

  GstPollFD *readfd;
  GstPollFD *writefd;

182
183
  gboolean manual_http;

184
  gchar tunnelid[TUNNELID_LEN];
185
  gboolean tunneled;
186
  GstRTSPTunnelState tstate;
187

188
189
190
  GstPoll *fdset;
  gchar *ip;

191
192
  gint read_ahead;

193
194
195
  gchar *initial_buffer;
  gsize initial_buffer_offset;

196
197
198
199
200
201
202
203
204
205
206
  /* 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;
207
208
209

  DecodeCtx ctx;
  DecodeCtx *ctxp;
Wim Taymans's avatar
Wim Taymans committed
210
211
212

  gchar *proxy_host;
  guint proxy_port;
213
214
};

215
216
217
218
219
220
221
222
223
224
enum
{
  STATE_START = 0,
  STATE_DATA_HEADER,
  STATE_DATA_BODY,
  STATE_READ_LINES,
  STATE_END,
  STATE_LAST
};

225
226
227
228
229
230
231
enum
{
  READ_AHEAD_EOH = -1,          /* end of headers */
  READ_AHEAD_CRLF = -2,
  READ_AHEAD_CRLFCR = -3
};

232
233
234
235
/* a structure for constructing RTSPMessages */
typedef struct
{
  gint state;
236
  GstRTSPResult status;
237
238
239
240
241
242
243
244
245
246
247
248
  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);
249
  memset (builder, 0, sizeof (GstRTSPBuilder));
250
251
}

252
253
254
/**
 * gst_rtsp_connection_create:
 * @url: a #GstRTSPUrl 
Wim Taymans's avatar
Wim Taymans committed
255
 * @conn: storage for a #GstRTSPConnection
256
257
258
259
260
 *
 * 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().
 *
261
262
 * A copy of @url will be made.
 *
263
264
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 */
265
GstRTSPResult
266
gst_rtsp_connection_create (const GstRTSPUrl * url, GstRTSPConnection ** conn)
267
268
{
  GstRTSPConnection *newconn;
269
270
271
272
#ifdef G_OS_WIN32
  WSADATA w;
  int error;
#endif
273
274
275

  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);

276
277
278
279
280
281
282
283
284
285
#ifdef G_OS_WIN32
  error = WSAStartup (0x0202, &w);

  if (error)
    goto startup_error;

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

286
287
  newconn = g_new0 (GstRTSPConnection, 1);

288
  if ((newconn->fdset = gst_poll_new (TRUE)) == NULL)
289
    goto no_fdset;
290

291
  newconn->url = gst_rtsp_url_copy (url);
292
293
  newconn->fd0.fd = -1;
  newconn->fd1.fd = -1;
294
  newconn->timer = g_timer_new ();
295
  newconn->timeout = 60;
296
  newconn->cseq = 1;
297
298
299
300

  newconn->auth_method = GST_RTSP_AUTH_NONE;
  newconn->username = NULL;
  newconn->passwd = NULL;
301
  newconn->auth_params = NULL;
302
303
304
305
306
307

  *conn = newconn;

  return GST_RTSP_OK;

  /* ERRORS */
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#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
322
no_fdset:
323
324
  {
    g_free (newconn);
325
326
327
#ifdef G_OS_WIN32
    WSACleanup ();
#endif
328
329
330
331
    return GST_RTSP_ESYS;
  }
}

Wim Taymans's avatar
Wim Taymans committed
332
/**
333
334
335
336
337
 * 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
338
339
 * @conn: storage for a #GstRTSPConnection
 *
340
341
342
 * 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
343
344
345
 *
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 *
346
 * Since: 0.10.25
Wim Taymans's avatar
Wim Taymans committed
347
348
 */
GstRTSPResult
349
350
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
351
{
352
  GstRTSPConnection *newconn = NULL;
Wim Taymans's avatar
Wim Taymans committed
353
  GstRTSPUrl *url;
354
355
356
#ifdef G_OS_WIN32
  gulong flags = 1;
#endif
357
  GstRTSPResult res;
Wim Taymans's avatar
Wim Taymans committed
358

359
360
361
  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);
362

Wim Taymans's avatar
Wim Taymans committed
363
364
365
366
367
368
369
370
371
  /* 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);
372
  url->host = g_strdup (ip);
373
  url->port = port;
Wim Taymans's avatar
Wim Taymans committed
374
375

  /* now create the connection object */
376
  GST_RTSP_CHECK (gst_rtsp_connection_create (url, &newconn), newconn_failed);
377
378
  gst_rtsp_url_free (url);

379
  ADD_POLLFD (newconn->fdset, &newconn->fd0, fd);
380

381
  /* both read and write initially */
382
383
  newconn->readfd = &newconn->fd0;
  newconn->writefd = &newconn->fd0;
Wim Taymans's avatar
Wim Taymans committed
384

385
386
387
388
  newconn->ip = g_strdup (ip);

  newconn->initial_buffer = g_strdup (initial_buffer);

Wim Taymans's avatar
Wim Taymans committed
389
390
391
392
  *conn = newconn;

  return GST_RTSP_OK;

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
441
442
443
444
445
446
  /* 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
447
448
449
450
451
  /* ERRORS */
accept_failed:
  {
    return GST_RTSP_ESYS;
  }
452
453
454
getnameinfo_failed:
wrong_family:
  {
455
    CLOSE_SOCKET (fd);
456
457
    return GST_RTSP_ERROR;
  }
Wim Taymans's avatar
Wim Taymans committed
458
459
}

Wim Taymans's avatar
Wim Taymans committed
460
static gchar *
461
do_resolve (const gchar * host)
462
{
463
  static gchar ip[INET6_ADDRSTRLEN];
464
  struct addrinfo *aires, hints;
465
466
  struct addrinfo *ai;
  gint aierr;
467

468
469
470
471
472
473
474
475
476
477
  memset (&hints, 0, sizeof (struct addrinfo));
  hints.ai_family = AF_UNSPEC;  /* Allow IPv4 or IPv6 */
  hints.ai_socktype = SOCK_DGRAM;       /* Datagram socket */
  hints.ai_flags = AI_PASSIVE;  /* For wildcard IP address */
  hints.ai_protocol = 0;        /* Any protocol */
  hints.ai_canonname = NULL;
  hints.ai_addr = NULL;
  hints.ai_next = NULL;

  aierr = getaddrinfo (host, NULL, &hints, &aires);
478
479
  if (aierr != 0)
    goto no_addrinfo;
480

481
482
483
484
  for (ai = aires; ai; ai = ai->ai_next) {
    if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
      break;
    }
485
  }
486
487
488
489
490
491
492
493
494
495
  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
496
  return g_strdup (ip);
497

498
  /* ERRORS */
499
500
501
502
503
504
no_addrinfo:
  {
    GST_ERROR ("no addrinfo found for %s: %s", host, gai_strerror (aierr));
    return NULL;
  }
no_family:
505
  {
506
507
    GST_ERROR ("no family found for %s", host);
    freeaddrinfo (aires);
508
509
    return NULL;
  }
510
no_address:
511
  {
512
513
    GST_ERROR ("no address found for %s: %s", host, gai_strerror (aierr));
    freeaddrinfo (aires);
514
515
516
517
518
519
520
521
522
    return NULL;
  }
}

static GstRTSPResult
do_connect (const gchar * ip, guint16 port, GstPollFD * fdout,
    GstPoll * fdset, GTimeVal * timeout)
{
  gint fd;
523
524
525
526
527
  struct addrinfo hints;
  struct addrinfo *aires;
  struct addrinfo *ai;
  gint aierr;
  gchar service[NI_MAXSERV];
528
529
530
531
532
533
534
  gint ret;
#ifdef G_OS_WIN32
  unsigned long flags = 1;
#endif /* G_OS_WIN32 */
  GstClockTime to;
  gint retval;

535
536
537
538
539
540
541
542
543
544
  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;
545

546
547
548
549
550
551
552
553
554
  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);
555
  if (fd == -1)
556
    goto no_socket;
557
558
559
560
561
562
563
564

  /* 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 */

565
  /* add the socket to our fdset */
566
  ADD_POLLFD (fdset, fdout, fd);
567

568
  /* we are going to connect ASYNC now */
569
  ret = connect (fd, ai->ai_addr, ai->ai_addrlen);
570
571
  if (ret == 0)
    goto done;
572
  if (!ERRNO_IS_EINPROGRESS)
573
574
575
576
    goto sys_error;

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

579
  to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
580
581

  do {
582
    retval = gst_poll_wait (fdset, to);
583
  } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
584
585
586
587
588
589

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

590
  /* we can still have an error connecting on windows */
591
  if (gst_poll_fd_has_error (fdset, fdout)) {
592
    socklen_t len = sizeof (errno);
593
#ifndef G_OS_WIN32
594
    getsockopt (fd, SOL_SOCKET, SO_ERROR, &errno, &len);
595
#else
596
    getsockopt (fd, SOL_SOCKET, SO_ERROR, (char *) &errno, &len);
597
#endif
598
    goto sys_error;
599
  }
600

601
  gst_poll_fd_ignored (fdset, fdout);
602

603
done:
604
  freeaddrinfo (aires);
605
606
607

  return GST_RTSP_OK;

608
  /* ERRORS */
609
610
611
612
613
614
615
616
617
618
619
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;
  }
620
621
622
no_socket:
  {
    GST_ERROR ("no socket %d (%s)", errno, g_strerror (errno));
623
    freeaddrinfo (aires);
624
625
    return GST_RTSP_ESYS;
  }
626
627
sys_error:
  {
628
    GST_ERROR ("system error %d (%s)", errno, g_strerror (errno));
629
    REMOVE_POLLFD (fdset, fdout);
630
    freeaddrinfo (aires);
631
632
    return GST_RTSP_ESYS;
  }
633
634
635
636
timeout:
  {
    GST_ERROR ("timeout");
    REMOVE_POLLFD (fdset, fdout);
637
    freeaddrinfo (aires);
638
639
640
641
642
643
644
645
646
    return GST_RTSP_ETIMEOUT;
  }
}

static GstRTSPResult
setup_tunneling (GstRTSPConnection * conn, GTimeVal * timeout)
{
  gint i;
  GstRTSPResult res;
647
648
649
  gchar *ip;
  gchar *uri;
  gchar *value;
Wim Taymans's avatar
Wim Taymans committed
650
  guint16 port, url_port;
651
  GstRTSPUrl *url;
Wim Taymans's avatar
Wim Taymans committed
652
  gchar *hostparam;
653
654
  GstRTSPMessage *msg;
  GstRTSPMessage response;
Wim Taymans's avatar
Wim Taymans committed
655
  gboolean old_http;
656
657
658

  memset (&response, 0, sizeof (response));
  gst_rtsp_message_init (&response);
659
660

  /* create a random sessionid */
661
662
663
664
665
  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
666
667
668
669
  /* get the port from the url */
  gst_rtsp_url_get_port (url, &url_port);

  if (conn->proxy_host) {
670
671
672
    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
673
674
675
    ip = conn->proxy_host;
    port = conn->proxy_port;
  } else {
676
677
    uri = g_strdup_printf ("%s%s%s", url->abspath, url->query ? "?" : "",
        url->query ? url->query : "");
Wim Taymans's avatar
Wim Taymans committed
678
679
680
681
    hostparam = NULL;
    ip = conn->ip;
    port = url_port;
  }
682

683
684
685
686
687
688
689
690
691
692
693
694
695
  /* 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");
696
697
698
699

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

700
701
702
703
704
705
706
707
708
709
710
  /* 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
711
  old_http = conn->manual_http;
712
713
714
  conn->manual_http = TRUE;
  GST_RTSP_CHECK (gst_rtsp_connection_receive (conn, &response, timeout),
      read_failed);
Wim Taymans's avatar
Wim Taymans committed
715
  conn->manual_http = old_http;
716
717
718
719
720
721

  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,
722
          &value, 0) == GST_RTSP_OK) {
723
724
725
726
727
728
    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);
729
    } else {
730
      /* and resolve the new ip address */
731
      if (!(ip = do_resolve (value)))
732
733
734
        goto not_resolved;
      g_free (conn->ip);
      conn->ip = ip;
735
736
737
738
739
740
741
742
743
744
745
    }
  }

  /* 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;

746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
  /* 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;
769

770
exit:
771
  gst_rtsp_message_unset (&response);
772
  g_free (hostparam);
773
  g_free (uri);
774

775
776
777
  return res;

  /* ERRORS */
778
no_message:
779
  {
780
    GST_ERROR ("failed to create request (%d)", res);
781
    goto exit;
782
  }
783
write_failed:
784
  {
785
786
787
    GST_ERROR ("write failed (%d)", res);
    gst_rtsp_message_free (msg);
    conn->tunneled = TRUE;
788
    goto exit;
789
  }
790
read_failed:
791
  {
792
793
    GST_ERROR ("read failed (%d)", res);
    conn->manual_http = FALSE;
794
    goto exit;
795
796
797
  }
wrong_result:
  {
798
799
    GST_ERROR ("got failure response %d %s", response.type_data.response.code,
        response.type_data.response.reason);
800
801
    res = GST_RTSP_ERROR;
    goto exit;
802
803
804
805
  }
not_resolved:
  {
    GST_ERROR ("could not resolve %s", conn->ip);
806
807
    res = GST_RTSP_ENET;
    goto exit;
808
809
810
811
  }
connect_failed:
  {
    GST_ERROR ("failed to connect");
812
    goto exit;
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
  }
}

/**
 * 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
834
  gchar *ip;
835
836
837
838
839
840
841
842
843
  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
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
  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);
859

Wim Taymans's avatar
Wim Taymans committed
860
861
862
    g_free (conn->ip);
    conn->ip = ip;
  }
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881

  /* 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;

882
883
884
885
not_resolved:
  {
    return GST_RTSP_ENET;
  }
886
connect_failed:
887
  {
888
889
    GST_ERROR ("failed to connect");
    return res;
890
  }
891
tunneling_failed:
892
  {
893
894
    GST_ERROR ("failed to setup tunneling");
    return res;
895
896
897
  }
}

898
899
900
901
static void
auth_digest_compute_hex_urp (const gchar * username,
    const gchar * realm, const gchar * password, gchar hex_urp[33])
{
902
903
904
905
906
907
908
909
910
911
912
913
914
915
  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);
916
917
918
919
920
921
922
}

static void
auth_digest_compute_response (const gchar * method,
    const gchar * uri, const gchar * hex_a1, const gchar * nonce,
    gchar response[33])
{
923
924
925
  char hex_a2[33] = { 0, };
  GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
  const gchar *digest_string;
926
927

  /* compute A2 */
928
929
930
931
932
  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));
933
934

  /* compute KD */
935
936
937
938
939
940
941
942
  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);
943
  memset (response, 0, 33);
944
945
946
  memcpy (response, digest_string, strlen (digest_string));

  g_checksum_free (md5_context);
947
948
}

949
950
951
952
953
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
954
955
956
957
      gchar *user_pass;
      gchar *user_pass64;
      gchar *auth_string;

958
959
960
      if (conn->username == NULL || conn->passwd == NULL)
        break;

Wim Taymans's avatar
Wim Taymans committed
961
962
963
      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);
964

965
      gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
966
967
968
969
970
971
          auth_string);

      g_free (user_pass);
      g_free (user_pass64);
      break;
    }
972
973
974
975
976
977
978
979
980
981
    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 */
982
983
      if (conn->auth_params == NULL || conn->username == NULL ||
          conn->passwd == NULL)
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
        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;
      }
1012
      gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
1013
1014
1015
          auth_string);
      break;
    }
1016
1017
1018
1019
1020
1021
1022
    default:
      /* Nothing to do */
      break;
  }
}

static void
1023
gen_date_string (gchar * date_string, guint len)
1024
1025
{
  GTimeVal tv;
1026
  time_t t;
1027
1028
1029
1030
#ifdef HAVE_GMTIME_R
  struct tm tm_;
#endif

1031
  g_get_current_time (&tv);
1032
  t = (time_t) tv.tv_sec;
1033
1034

#ifdef HAVE_GMTIME_R
1035
  strftime (date_string, len, "%a, %d %b %Y %H:%M:%S GMT", gmtime_r (&t, &tm_));
1036
#else
1037
  strftime (date_string, len, "%a, %d %b %Y %H:%M:%S GMT", gmtime (&t));
1038
#endif
1039
1040
}

1041
1042
1043
1044
1045
static GstRTSPResult
write_bytes (gint fd, const guint8 * buffer, guint * idx, guint size)
{
  guint left;

1046
  if (G_UNLIKELY (*idx > size))
1047
1048
1049
1050
1051
1052
1053
1054
    return GST_RTSP_ERROR;

  left = size - *idx;

  while (left) {
    gint r;

    r = WRITE_SOCKET (fd, &buffer[*idx], left);
1055
    if (G_UNLIKELY (r == 0)) {
1056
      return GST_RTSP_EINTR;
1057
    } else if (G_UNLIKELY (r < 0)) {
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
      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;
}

1070
static gint
1071
fill_raw_bytes (GstRTSPConnection * conn, guint8 * buffer, guint size)
1072
1073
1074
{
  gint out = 0;

1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
  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;

1109
1110
  if (ctx) {
    while (size > 0) {
1111
1112
1113
      guint8 in[sizeof (ctx->out) * 4 / 3];
      gint r;

1114
      while (size > 0 && ctx->cout < ctx->coutl) {
1115
        /* we have some leftover bytes */
1116
        *buffer++ = ctx->out[ctx->cout++];
1117
1118
1119
        size--;
        out++;
      }
1120
1121

      /* got what we needed? */
1122
1123
1124
1125
      if (size == 0)
        break;

      /* try to read more bytes */
1126
      r = fill_raw_bytes (conn, in, sizeof (in));
1127
1128
1129
1130
1131
1132
      if (r <= 0) {
        if (out == 0)
          out = r;
        break;
      }

1133
1134
1135
1136
      ctx->cout = 0;
      ctx->coutl =
          g_base64_decode_step ((gchar *) in, r, ctx->out, &ctx->state,
          &ctx->save);
1137
1138
    }
  } else {
1139
    out = fill_raw_bytes (conn, buffer, size);
1140
1141
1142
1143
1144
  }

  return out;
}

1145
static GstRTSPResult
1146
read_bytes (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
1147
1148
1149
{
  guint left;

1150
  if (G_UNLIKELY (*idx > size))
1151
1152
1153
1154
1155
1156
1157
    return GST_RTSP_ERROR;

  left = size - *idx;

  while (left) {
    gint r;

1158
    r = fill_bytes (conn, &buffer[*idx], left);
1159
    if (G_UNLIKELY (r == 0)) {
1160
      return GST_RTSP_EEOF;
1161
    } else if (G_UNLIKELY (r < 0)) {
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
      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;
}

1174
1175
1176
1177
1178
1179
/* 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.
 */
1180
static GstRTSPResult
1181
read_line (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
1182
1183
1184
1185
1186
{
  while (TRUE) {
    guint8 c;
    gint r;

1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
    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;
1202
    } else {
1203
1204
1205
1206
1207
1208
1209
1210
1211
      /* 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;
1212
        continue;
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
      }
    }

    /* 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;
      }
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
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
      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;
      }
1288
    }
1289
1290
1291

    if (G_LIKELY (*idx < size - 1))
      buffer[(*idx)++] = c;
1292
1293
1294
1295
1296
1297
  }
  buffer[*idx] = '\0';

  return GST_RTSP_OK;
}

1298
1299
1300
1301
1302
1303
1304
1305
1306
1307