rtspconnection.c 19.4 KB
Newer Older
Wim Taymans's avatar
Wim Taymans committed
1
/* GStreamer
2
 * Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com>
Wim Taymans's avatar
Wim Taymans committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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.
 */
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
/*
 * 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.
 */
Wim Taymans's avatar
Wim Taymans committed
42

43 44 45 46
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

Wim Taymans's avatar
Wim Taymans committed
47
#include <stdio.h>
Wim Taymans's avatar
Wim Taymans committed
48
#include <errno.h>
Wim Taymans's avatar
Wim Taymans committed
49 50 51
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
52
#include <fcntl.h>
53

54 55 56
/* we include this here to get the G_OS_* defines */
#include <glib.h>

57 58 59
#ifdef G_OS_WIN32
#include <winsock2.h>
#else
60
#include <sys/ioctl.h>
61 62
#include <netdb.h>
#include <sys/socket.h>
63 64
#include <netinet/in.h>
#include <arpa/inet.h>
65
#endif
Wim Taymans's avatar
Wim Taymans committed
66

67 68 69 70
#ifdef HAVE_FIONREAD_IN_SYS_FILIO
#include <sys/filio.h>
#endif

Wim Taymans's avatar
Wim Taymans committed
71
#include "rtspconnection.h"
72
#include "base64.h"
Wim Taymans's avatar
Wim Taymans committed
73

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
/* the select call is also performed on the control sockets, that way
 * we can send special commands to unlock or restart the select call */
#define CONTROL_RESTART        'R'      /* restart the select call */
#define CONTROL_STOP           'S'      /* stop the select call */
#define CONTROL_SOCKETS(conn)  conn->control_sock
#define WRITE_SOCKET(conn)     conn->control_sock[1]
#define READ_SOCKET(conn)      conn->control_sock[0]

#define SEND_COMMAND(conn, command)              \
G_STMT_START {                                  \
  unsigned char c; c = command;                 \
  write (WRITE_SOCKET(conn), &c, 1);             \
} G_STMT_END

#define READ_COMMAND(conn, command, res)         \
G_STMT_START {                                  \
  res = read(READ_SOCKET(conn), &command, 1);    \
} G_STMT_END

93
#ifdef G_OS_WIN32
94
#define IOCTL_SOCKET ioctlsocket
95 96
#define CLOSE_SOCKET(sock) closesocket(sock);
#else
97
#define IOCTL_SOCKET ioctl
98 99 100
#define CLOSE_SOCKET(sock) close(sock);
#endif

101
#ifdef G_OS_WIN32
102 103 104
static int
inet_aton (const char *c, struct in_addr *paddr)
{
105 106 107
  /* note that inet_addr is deprecated on unix because
   * inet_addr returns -1 (INADDR_NONE) for the valid 255.255.255.255
   * address. */
108 109 110 111 112 113 114 115 116
  paddr->s_addr = inet_addr (c);

  if (paddr->s_addr == INADDR_NONE)
    return 0;

  return 1;
}
#endif

Wim Taymans's avatar
Wim Taymans committed
117
RTSPResult
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
rtsp_connection_create (RTSPUrl * url, RTSPConnection ** conn)
{
  gint ret;
  RTSPConnection *newconn;

  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);

  newconn = g_new (RTSPConnection, 1);

#ifdef G_OS_WIN32
  /* This should work on UNIX too. PF_UNIX sockets replaced with pipe */
  /* pipe( CONTROL_SOCKETS(newconn) ) */
  if ((ret = pipe (CONTROL_SOCKETS (newconn))) < 0)
    goto no_socket_pair;
#else
  if ((ret =
          socketpair (PF_UNIX, SOCK_STREAM, 0, CONTROL_SOCKETS (newconn))) < 0)
    goto no_socket_pair;

  fcntl (READ_SOCKET (newconn), F_SETFL, O_NONBLOCK);
  fcntl (WRITE_SOCKET (newconn), F_SETFL, O_NONBLOCK);
#endif

  newconn->url = url;
142
  newconn->fd = -1;
143 144 145 146
  newconn->cseq = 0;
  newconn->session_id[0] = 0;
  newconn->state = RTSP_STATE_INIT;

147 148 149 150
  newconn->auth_method = RTSP_AUTH_NONE;
  newconn->username = NULL;
  newconn->passwd = NULL;

151 152 153 154 155 156 157 158 159 160 161 162 163 164
  *conn = newconn;

  return RTSP_OK;

  /* ERRORS */
no_socket_pair:
  {
    g_free (newconn);
    return RTSP_ESYS;
  }
}

RTSPResult
rtsp_connection_connect (RTSPConnection * conn)
Wim Taymans's avatar
Wim Taymans committed
165 166 167 168 169 170 171 172
{
  gint fd;
  struct sockaddr_in sin;
  struct hostent *hostinfo;
  char **addrs;
  gchar *ip;
  struct in_addr addr;
  gint ret;
173 174
  guint16 port;
  RTSPUrl *url;
Wim Taymans's avatar
Wim Taymans committed
175

176
  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);
177 178
  g_return_val_if_fail (conn->url != NULL, RTSP_EINVAL);
  g_return_val_if_fail (conn->fd < 0, RTSP_EINVAL);
Wim Taymans's avatar
Wim Taymans committed
179

180
  url = conn->url;
Wim Taymans's avatar
Wim Taymans committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196

  /* first check if it already is an IP address */
  if (inet_aton (url->host, &addr)) {
    ip = url->host;
  } else {
    hostinfo = gethostbyname (url->host);
    if (!hostinfo)
      goto not_resolved;        /* h_errno set */

    if (hostinfo->h_addrtype != AF_INET)
      goto not_ip;              /* host not an IP host */

    addrs = hostinfo->h_addr_list;
    ip = inet_ntoa (*(struct in_addr *) *addrs);
  }

197 198
  /* get the port from the url */
  rtsp_url_get_port (url, &port);
Wim Taymans's avatar
Wim Taymans committed
199 200 201

  memset (&sin, 0, sizeof (sin));
  sin.sin_family = AF_INET;     /* network socket */
202
  sin.sin_port = htons (port);  /* on port */
Wim Taymans's avatar
Wim Taymans committed
203 204
  sin.sin_addr.s_addr = inet_addr (ip); /* on host ip */

205 206 207 208
  fd = socket (AF_INET, SOCK_STREAM, 0);
  if (fd == -1)
    goto sys_error;

Wim Taymans's avatar
Wim Taymans committed
209 210 211 212
  ret = connect (fd, (struct sockaddr *) &sin, sizeof (sin));
  if (ret != 0)
    goto sys_error;

213 214 215
  conn->fd = fd;

  return RTSP_OK;
Wim Taymans's avatar
Wim Taymans committed
216 217 218 219 220 221 222

sys_error:
  {
    return RTSP_ESYS;
  }
not_resolved:
  {
223
    return RTSP_ENET;
Wim Taymans's avatar
Wim Taymans committed
224 225 226
  }
not_ip:
  {
227
    return RTSP_ENOTIP;
Wim Taymans's avatar
Wim Taymans committed
228 229 230 231 232 233 234 235 236 237 238
  }
}

static void
append_header (gint key, gchar * value, GString * str)
{
  const gchar *keystr = rtsp_header_as_text (key);

  g_string_append_printf (str, "%s: %s\r\n", keystr, value);
}

239 240 241 242 243 244 245
static void
append_auth_header (RTSPConnection * conn, RTSPMessage * message, GString * str)
{
  switch (conn->auth_method) {
    case RTSP_AUTH_BASIC:{
      gchar *user_pass =
          g_strdup_printf ("%s:%s", conn->username, conn->passwd);
246
      gchar *user_pass64 = util_base64_encode (user_pass, strlen (user_pass));
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
      gchar *auth_string = g_strdup_printf ("Basic %s", user_pass64);

      append_header (RTSP_HDR_AUTHORIZATION, auth_string, str);

      g_free (user_pass);
      g_free (user_pass64);
      g_free (auth_string);
      break;
    }
    default:
      /* Nothing to do */
      break;
  }
}

Wim Taymans's avatar
Wim Taymans committed
262 263 264 265
RTSPResult
rtsp_connection_send (RTSPConnection * conn, RTSPMessage * message)
{
  GString *str;
Wim Taymans's avatar
Wim Taymans committed
266 267
  gint towrite;
  gchar *data;
Wim Taymans's avatar
Wim Taymans committed
268

269 270 271 272 273
#ifdef G_OS_WIN32
  WSADATA w;
  int error;
#endif

274 275
  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);
  g_return_val_if_fail (message != NULL, RTSP_EINVAL);
Wim Taymans's avatar
Wim Taymans committed
276

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

  if (error)
    goto startup_error;

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

Wim Taymans's avatar
Wim Taymans committed
287 288
  str = g_string_new ("");

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
  switch (message->type) {
    case RTSP_MESSAGE_REQUEST:
      /* create request string, add CSeq */
      g_string_append_printf (str, "%s %s RTSP/1.0\r\n"
          "CSeq: %d\r\n",
          rtsp_method_as_text (message->type_data.request.method),
          message->type_data.request.uri, conn->cseq++);
      break;
    case RTSP_MESSAGE_RESPONSE:
      /* create response string */
      g_string_append_printf (str, "RTSP/1.0 %d %s\r\n",
          message->type_data.response.code, message->type_data.response.reason);
      break;
    default:
      g_assert_not_reached ();
      break;
  }
Wim Taymans's avatar
Wim Taymans committed
306

Wim Taymans's avatar
Wim Taymans committed
307
  /* append session id if we have one */
Wim Taymans's avatar
Wim Taymans committed
308
  if (conn->session_id[0] != '\0') {
309
    append_header (RTSP_HDR_SESSION, conn->session_id, str);
Wim Taymans's avatar
Wim Taymans committed
310 311
  }

Wim Taymans's avatar
Wim Taymans committed
312
  /* append headers */
Wim Taymans's avatar
Wim Taymans committed
313 314
  g_hash_table_foreach (message->hdr_fields, (GHFunc) append_header, str);

315 316 317
  /* Append any authentication headers */
  append_auth_header (conn, message, str);

Wim Taymans's avatar
Wim Taymans committed
318 319 320
  /* append Content-Length and body if needed */
  if (message->body != NULL && message->body_size > 0) {
    gchar *len;
Wim Taymans's avatar
Wim Taymans committed
321

Wim Taymans's avatar
Wim Taymans committed
322 323 324 325 326
    len = g_strdup_printf ("%d", message->body_size);
    append_header (RTSP_HDR_CONTENT_LENGTH, len, str);
    g_free (len);
    /* header ends here */
    g_string_append (str, "\r\n");
327 328
    str =
        g_string_append_len (str, (gchar *) message->body, message->body_size);
Wim Taymans's avatar
Wim Taymans committed
329 330 331 332 333 334 335 336
  } else {
    /* just end headers */
    g_string_append (str, "\r\n");
  }

  /* write request */
  towrite = str->len;
  data = str->str;
Wim Taymans's avatar
Wim Taymans committed
337

Wim Taymans's avatar
Wim Taymans committed
338 339 340 341 342 343 344 345 346 347 348 349
  while (towrite > 0) {
    gint written;

    written = write (conn->fd, data, towrite);
    if (written < 0) {
      if (errno != EAGAIN && errno != EINTR)
        goto write_error;
    } else {
      towrite -= written;
      data += written;
    }
  }
Wim Taymans's avatar
Wim Taymans committed
350 351 352
  g_string_free (str, TRUE);

  return RTSP_OK;
Wim Taymans's avatar
Wim Taymans committed
353

354
#ifdef G_OS_WIN32
355 356
startup_error:
  {
357 358
    g_warning ("Error %d on WSAStartup", error);
    return RTSP_EWSASTART;
359 360 361
  }
version_error:
  {
362 363
    g_warning ("Windows sockets are not version 0x202 (current 0x%x)",
        w.wVersion);
364
    WSACleanup ();
365
    return RTSP_EWSAVERSION;
366 367
  }
#endif
Wim Taymans's avatar
Wim Taymans committed
368 369 370 371 372
write_error:
  {
    g_string_free (str, TRUE);
    return RTSP_ESYS;
  }
Wim Taymans's avatar
Wim Taymans committed
373 374 375 376 377
}

static RTSPResult
read_line (gint fd, gchar * buffer, guint size)
{
378
  guint idx;
Wim Taymans's avatar
Wim Taymans committed
379
  gchar c;
Wim Taymans's avatar
Wim Taymans committed
380
  gint r;
Wim Taymans's avatar
Wim Taymans committed
381 382 383

  idx = 0;
  while (TRUE) {
Wim Taymans's avatar
Wim Taymans committed
384
    r = read (fd, &c, 1);
385 386 387
    if (r == 0) {
      goto eof;
    } else if (r < 0) {
Wim Taymans's avatar
Wim Taymans committed
388 389 390 391 392 393 394
      if (errno != EAGAIN && errno != EINTR)
        goto read_error;
    } else {
      if (c == '\n')            /* end on \n */
        break;
      if (c == '\r')            /* ignore \r */
        continue;
Wim Taymans's avatar
Wim Taymans committed
395

Wim Taymans's avatar
Wim Taymans committed
396 397 398
      if (idx < size - 1)
        buffer[idx++] = c;
    }
Wim Taymans's avatar
Wim Taymans committed
399 400 401 402 403
  }
  buffer[idx] = '\0';

  return RTSP_OK;

404 405 406 407
eof:
  {
    return RTSP_EEOF;
  }
Wim Taymans's avatar
Wim Taymans committed
408
read_error:
Wim Taymans's avatar
Wim Taymans committed
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 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
  {
    return RTSP_ESYS;
  }
}

static void
read_string (gchar * dest, gint size, gchar ** src)
{
  gint idx;

  idx = 0;
  /* skip spaces */
  while (g_ascii_isspace (**src))
    (*src)++;

  while (!g_ascii_isspace (**src) && **src != '\0') {
    if (idx < size - 1)
      dest[idx++] = **src;
    (*src)++;
  }
  if (size > 0)
    dest[idx] = '\0';
}

static void
read_key (gchar * dest, gint size, gchar ** src)
{
  gint idx;

  idx = 0;
  while (**src != ':' && **src != '\0') {
    if (idx < size - 1)
      dest[idx++] = **src;
    (*src)++;
  }
  if (size > 0)
    dest[idx] = '\0';
}

static RTSPResult
parse_response_status (gchar * buffer, RTSPMessage * msg)
{
  gchar versionstr[20];
  gchar codestr[4];
  gint code;
  gchar *bptr;

  bptr = buffer;

  read_string (versionstr, sizeof (versionstr), &bptr);
  if (strcmp (versionstr, "RTSP/1.0") != 0)
    goto wrong_version;

  read_string (codestr, sizeof (codestr), &bptr);
  code = atoi (codestr);

  while (g_ascii_isspace (*bptr))
    bptr++;

468
  rtsp_message_init_response (msg, code, bptr, NULL);
Wim Taymans's avatar
Wim Taymans committed
469 470 471 472 473 474 475 476 477

  return RTSP_OK;

wrong_version:
  {
    return RTSP_EINVAL;
  }
}

Wim Taymans's avatar
Wim Taymans committed
478 479 480 481 482 483 484 485 486 487 488 489 490
static RTSPResult
parse_request_line (gchar * buffer, RTSPMessage * msg)
{
  gchar versionstr[20];
  gchar methodstr[20];
  gchar urlstr[4096];
  gchar *bptr;
  RTSPMethod method;

  bptr = buffer;

  read_string (methodstr, sizeof (methodstr), &bptr);
  method = rtsp_find_method (methodstr);
491
  if (method == RTSP_INVALID)
Wim Taymans's avatar
Wim Taymans committed
492 493 494 495 496 497 498 499
    goto wrong_method;

  read_string (urlstr, sizeof (urlstr), &bptr);

  read_string (versionstr, sizeof (versionstr), &bptr);
  if (strcmp (versionstr, "RTSP/1.0") != 0)
    goto wrong_version;

500
  rtsp_message_init_request (msg, method, urlstr);
Wim Taymans's avatar
Wim Taymans committed
501 502 503 504 505 506 507 508 509 510 511 512 513 514

  return RTSP_OK;

wrong_method:
  {
    return RTSP_EINVAL;
  }
wrong_version:
  {
    return RTSP_EINVAL;
  }
}

/* parsing lines means reading a Key: Value pair */
Wim Taymans's avatar
Wim Taymans committed
515 516 517 518 519 520 521 522 523
static RTSPResult
parse_line (gchar * buffer, RTSPMessage * msg)
{
  gchar key[32];
  gchar *bptr;
  RTSPHeaderField field;

  bptr = buffer;

Wim Taymans's avatar
Wim Taymans committed
524
  /* read key */
Wim Taymans's avatar
Wim Taymans committed
525 526 527 528 529 530 531
  read_key (key, sizeof (key), &bptr);
  if (*bptr != ':')
    return RTSP_EINVAL;

  bptr++;

  field = rtsp_find_header_field (key);
532
  if (field != RTSP_HDR_INVALID) {
Wim Taymans's avatar
Wim Taymans committed
533 534 535 536 537 538 539 540
    while (g_ascii_isspace (*bptr))
      bptr++;
    rtsp_message_add_header (msg, field, bptr);
  }

  return RTSP_OK;
}

541 542 543 544 545 546
RTSPResult
rtsp_connection_read (RTSPConnection * conn, gpointer data, guint size)
{
  fd_set readfds;
  guint toread;
  gint retval;
547 548

#ifndef G_OS_WIN32
549
  gint avail;
550 551 552
#else
  gulong avail;
#endif
553 554 555 556 557 558 559 560 561 562 563

  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);
  g_return_val_if_fail (data != NULL, RTSP_EINVAL);

  if (size == 0)
    return RTSP_OK;

  toread = size;

  /* if the call fails, just go in the select.. it should not fail. Else if
   * there is enough data to read, skip the select call al together.*/
564
  if (IOCTL_SOCKET (conn->fd, FIONREAD, &avail) < 0)
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
    avail = 0;
  else if (avail >= toread)
    goto do_read;

  FD_ZERO (&readfds);
  FD_SET (conn->fd, &readfds);
  FD_SET (READ_SOCKET (conn), &readfds);

  while (toread > 0) {
    gint bytes;

    do {
      retval = select (FD_SETSIZE, &readfds, NULL, NULL, NULL);
    } while ((retval == -1 && errno == EINTR));

    if (retval == -1)
      goto select_error;

    if (FD_ISSET (READ_SOCKET (conn), &readfds)) {
      /* read all stop commands */
      while (TRUE) {
        gchar command;
        int res;

        READ_COMMAND (conn, command, res);
590
        if (res <= 0) {
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
          /* no more commands */
          break;
        }
      }
      goto stopped;
    }

  do_read:
    /* if we get here there is activity on the real fd since the select
     * completed and the control socket was not readable. */
    bytes = read (conn->fd, data, toread);

    if (bytes == 0) {
      goto eof;
    } else if (bytes < 0) {
      if (errno != EAGAIN && errno != EINTR)
        goto read_error;
    } else {
      toread -= bytes;
610
      data = (char *) data + bytes;
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
    }
  }
  return RTSP_OK;

  /* ERRORS */
select_error:
  {
    return RTSP_ESYS;
  }
stopped:
  {
    return RTSP_EINTR;
  }
eof:
  {
    return RTSP_EEOF;
  }
read_error:
  {
    return RTSP_ESYS;
  }
}

Wim Taymans's avatar
Wim Taymans committed
634
static RTSPResult
635
read_body (RTSPConnection * conn, glong content_length, RTSPMessage * msg)
Wim Taymans's avatar
Wim Taymans committed
636
{
637 638
  gchar *body;
  RTSPResult res;
Wim Taymans's avatar
Wim Taymans committed
639 640

  if (content_length <= 0) {
Wim Taymans's avatar
Wim Taymans committed
641 642 643
    body = NULL;
    content_length = 0;
    goto done;
Wim Taymans's avatar
Wim Taymans committed
644 645
  }

646 647
  body = g_malloc (content_length + 1);
  body[content_length] = '\0';
648 649

  RTSP_CHECK (rtsp_connection_read (conn, body, content_length), read_error);
Wim Taymans's avatar
Wim Taymans committed
650

651 652
  content_length += 1;

Wim Taymans's avatar
Wim Taymans committed
653
done:
654
  rtsp_message_take_body (msg, (guint8 *) body, content_length);
Wim Taymans's avatar
Wim Taymans committed
655 656

  return RTSP_OK;
Wim Taymans's avatar
Wim Taymans committed
657

658
  /* ERRORS */
Wim Taymans's avatar
Wim Taymans committed
659 660 661
read_error:
  {
    g_free (body);
662
    return res;
Wim Taymans's avatar
Wim Taymans committed
663
  }
Wim Taymans's avatar
Wim Taymans committed
664 665 666 667 668 669 670 671 672 673 674 675
}

RTSPResult
rtsp_connection_receive (RTSPConnection * conn, RTSPMessage * msg)
{
  gchar buffer[4096];
  gint line;
  gchar *hdrval;
  glong content_length;
  RTSPResult res;
  gboolean need_body;

676 677
  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);
  g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
Wim Taymans's avatar
Wim Taymans committed
678 679 680 681 682

  line = 0;

  need_body = TRUE;

Wim Taymans's avatar
Wim Taymans committed
683
  res = RTSP_OK;
Wim Taymans's avatar
Wim Taymans committed
684
  /* parse first line and headers */
Wim Taymans's avatar
Wim Taymans committed
685
  while (res == RTSP_OK) {
Wim Taymans's avatar
Wim Taymans committed
686 687
    gchar c;

Wim Taymans's avatar
Wim Taymans committed
688
    /* read first character, this identifies data messages */
689
    RTSP_CHECK (rtsp_connection_read (conn, &c, 1), read_error);
Wim Taymans's avatar
Wim Taymans committed
690

Wim Taymans's avatar
Wim Taymans committed
691
    /* check for data packet, first character is $ */
Wim Taymans's avatar
Wim Taymans committed
692 693 694
    if (c == '$') {
      guint16 size;

Wim Taymans's avatar
Wim Taymans committed
695 696 697
      /* data packets are $<1 byte channel><2 bytes length,BE><data bytes> */

      /* read channel, which is the next char */
698
      RTSP_CHECK (rtsp_connection_read (conn, &c, 1), read_error);
Wim Taymans's avatar
Wim Taymans committed
699

Wim Taymans's avatar
Wim Taymans committed
700
      /* now we create a data message */
701
      rtsp_message_init_data (msg, (gint) c);
Wim Taymans's avatar
Wim Taymans committed
702

Wim Taymans's avatar
Wim Taymans committed
703
      /* next two bytes are the length of the data */
704
      RTSP_CHECK (rtsp_connection_read (conn, &size, 2), read_error);
Wim Taymans's avatar
Wim Taymans committed
705 706 707

      size = GUINT16_FROM_BE (size);

Wim Taymans's avatar
Wim Taymans committed
708
      /* and read the body */
709
      res = read_body (conn, size, msg);
Wim Taymans's avatar
Wim Taymans committed
710 711 712 713 714
      need_body = FALSE;
      break;
    } else {
      gint offset = 0;

Wim Taymans's avatar
Wim Taymans committed
715
      /* we have a regular response */
Wim Taymans's avatar
Wim Taymans committed
716 717 718 719 720 721 722 723
      if (c != '\r') {
        buffer[0] = c;
        offset = 1;
      }
      /* should not happen */
      if (c == '\n')
        break;

Wim Taymans's avatar
Wim Taymans committed
724
      /* read lines */
725 726
      RTSP_CHECK (read_line (conn->fd, buffer + offset,
              sizeof (buffer) - offset), read_error);
Wim Taymans's avatar
Wim Taymans committed
727 728 729 730 731

      if (buffer[0] == '\0')
        break;

      if (line == 0) {
Wim Taymans's avatar
Wim Taymans committed
732
        /* first line, check for response status */
Wim Taymans's avatar
Wim Taymans committed
733
        if (g_str_has_prefix (buffer, "RTSP")) {
Wim Taymans's avatar
Wim Taymans committed
734
          res = parse_response_status (buffer, msg);
Wim Taymans's avatar
Wim Taymans committed
735
        } else {
Wim Taymans's avatar
Wim Taymans committed
736
          res = parse_request_line (buffer, msg);
Wim Taymans's avatar
Wim Taymans committed
737 738
        }
      } else {
Wim Taymans's avatar
Wim Taymans committed
739
        /* else just parse the line */
Wim Taymans's avatar
Wim Taymans committed
740 741 742 743 744 745
        parse_line (buffer, msg);
      }
    }
    line++;
  }

Wim Taymans's avatar
Wim Taymans committed
746
  /* read the rest of the body if needed */
Wim Taymans's avatar
Wim Taymans committed
747
  if (need_body) {
Wim Taymans's avatar
Wim Taymans committed
748 749 750 751
    /* see if there is a Content-Length header */
    if (rtsp_message_get_header (msg, RTSP_HDR_CONTENT_LENGTH,
            &hdrval) == RTSP_OK) {
      /* there is, read the body */
Wim Taymans's avatar
Wim Taymans committed
752
      content_length = atol (hdrval);
753
      RTSP_CHECK (read_body (conn, content_length, msg), read_error);
Wim Taymans's avatar
Wim Taymans committed
754 755
    }

Wim Taymans's avatar
Wim Taymans committed
756
    /* save session id in the connection for further use */
Wim Taymans's avatar
Wim Taymans committed
757 758 759 760 761
    {
      gchar *session_id;

      if (rtsp_message_get_header (msg, RTSP_HDR_SESSION,
              &session_id) == RTSP_OK) {
762 763 764 765 766 767 768 769 770 771
        gint sesslen, maxlen, i;

        sesslen = strlen (session_id);
        maxlen = sizeof (conn->session_id) - 1;
        /* the sessionid can have attributes marked with ;
         * Make sure we strip them */
        for (i = 0; i < sesslen; i++) {
          if (session_id[i] == ';')
            maxlen = i;
        }
Wim Taymans's avatar
Wim Taymans committed
772 773 774 775

        /* make sure to not overflow */
        strncpy (conn->session_id, session_id, maxlen);
        conn->session_id[maxlen] = '\0';
Wim Taymans's avatar
Wim Taymans committed
776 777 778
      }
    }
  }
Wim Taymans's avatar
Wim Taymans committed
779
  return res;
Wim Taymans's avatar
Wim Taymans committed
780 781 782

read_error:
  {
783
    return res;
Wim Taymans's avatar
Wim Taymans committed
784 785 786 787 788 789 790 791
  }
}

RTSPResult
rtsp_connection_close (RTSPConnection * conn)
{
  gint res;

792
  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);
793
  g_return_val_if_fail (conn->fd >= 0, RTSP_EINVAL);
794

795
  res = CLOSE_SOCKET (conn->fd);
796
#ifdef G_OS_WIN32
797 798
  WSACleanup ();
#endif
Wim Taymans's avatar
Wim Taymans committed
799 800 801 802 803 804 805 806 807 808 809
  conn->fd = -1;
  if (res != 0)
    goto sys_error;

  return RTSP_OK;

sys_error:
  {
    return RTSP_ESYS;
  }
}
Wim Taymans's avatar
Wim Taymans committed
810 811 812 813

RTSPResult
rtsp_connection_free (RTSPConnection * conn)
{
814
  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);
Wim Taymans's avatar
Wim Taymans committed
815

816 817 818 819
#ifdef G_OS_WIN32
  WSACleanup ();
#endif

820 821 822
  g_free (conn->username);
  g_free (conn->passwd);

Wim Taymans's avatar
Wim Taymans committed
823
  g_free (conn);
824 825

  return RTSP_OK;
Wim Taymans's avatar
Wim Taymans committed
826
}
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848

RTSPResult
rtsp_connection_flush (RTSPConnection * conn, gboolean flush)
{
  g_return_val_if_fail (conn != NULL, RTSP_EINVAL);

  if (flush) {
    SEND_COMMAND (conn, CONTROL_STOP);
  } else {
    while (TRUE) {
      gchar command;
      int res;

      READ_COMMAND (conn, command, res);
      if (res <= 0) {
        /* no more commands */
        break;
      }
    }
  }
  return RTSP_OK;
}
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874

RTSPResult
rtsp_connection_set_auth (RTSPConnection * conn, RTSPAuthMethod method,
    gchar * user, gchar * pass)
{
  /* Digest isn't implemented yet */
  if (method == RTSP_AUTH_DIGEST)
    return RTSP_ENOTIMPL;

  /* Make sure the username and passwd are being set for authentication */
  if (method == RTSP_AUTH_NONE && (user == NULL || pass == NULL))
    return RTSP_EINVAL;

  /* ":" chars are not allowed in usernames for basic auth */
  if (method == RTSP_AUTH_BASIC && g_strrstr (user, ":") != NULL)
    return RTSP_EINVAL;

  g_free (conn->username);
  g_free (conn->passwd);

  conn->auth_method = method;
  conn->username = g_strdup (user);
  conn->passwd = g_strdup (pass);

  return RTSP_OK;
}