Coverage Report

Created: 2026-01-25 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/rtsp.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
#include "urldata.h"
26
#include "rtsp.h"
27
28
#ifndef CURL_DISABLE_RTSP
29
30
#include "transfer.h"
31
#include "sendf.h"
32
#include "curl_trc.h"
33
#include "multiif.h"
34
#include "http.h"
35
#include "url.h"
36
#include "progress.h"
37
#include "strcase.h"
38
#include "select.h"
39
#include "connect.h"
40
#include "cfilters.h"
41
#include "strdup.h"
42
#include "bufref.h"
43
#include "curlx/strparse.h"
44
45
/* meta key for storing protocol meta at easy handle */
46
0
#define CURL_META_RTSP_EASY   "meta:proto:rtsp:easy"
47
/* meta key for storing protocol meta at connection */
48
0
#define CURL_META_RTSP_CONN   "meta:proto:rtsp:conn"
49
50
typedef enum {
51
  RTP_PARSE_SKIP,
52
  RTP_PARSE_CHANNEL,
53
  RTP_PARSE_LEN,
54
  RTP_PARSE_DATA
55
} rtp_parse_st;
56
57
/* RTSP Connection data
58
 * Currently, only used for tracking incomplete RTP data reads */
59
struct rtsp_conn {
60
  struct dynbuf buf;
61
  int rtp_channel;
62
  size_t rtp_len;
63
  rtp_parse_st state;
64
  BIT(in_header);
65
};
66
67
/* RTSP transfer data */
68
struct RTSP {
69
  uint32_t CSeq_sent; /* CSeq of this request */
70
  uint32_t CSeq_recv; /* CSeq received */
71
};
72
73
0
#define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \
74
0
                            ((unsigned int)((unsigned char)((p)[3]))))
75
76
/* this returns the socket to wait for in the DO and DOING state for the multi
77
   interface and then we are always _sending_ a request and thus we wait for
78
   the single socket to become writable only */
79
static CURLcode rtsp_do_pollset(struct Curl_easy *data,
80
                                struct easy_pollset *ps)
81
0
{
82
  /* write mode */
83
0
  return Curl_pollset_add_out(data, ps, data->conn->sock[FIRSTSOCKET]);
84
0
}
85
86
0
#define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */
87
88
static void rtsp_easy_dtor(void *key, size_t klen, void *entry)
89
0
{
90
0
  struct RTSP *rtsp = entry;
91
0
  (void)key;
92
0
  (void)klen;
93
0
  curlx_free(rtsp);
94
0
}
95
96
static void rtsp_conn_dtor(void *key, size_t klen, void *entry)
97
0
{
98
0
  struct rtsp_conn *rtspc = entry;
99
0
  (void)key;
100
0
  (void)klen;
101
0
  curlx_dyn_free(&rtspc->buf);
102
0
  curlx_free(rtspc);
103
0
}
104
105
static CURLcode rtsp_setup_connection(struct Curl_easy *data,
106
                                      struct connectdata *conn)
107
0
{
108
0
  struct rtsp_conn *rtspc;
109
0
  struct RTSP *rtsp;
110
111
0
  rtspc = curlx_calloc(1, sizeof(*rtspc));
112
0
  if(!rtspc)
113
0
    return CURLE_OUT_OF_MEMORY;
114
0
  curlx_dyn_init(&rtspc->buf, MAX_RTP_BUFFERSIZE);
115
0
  if(Curl_conn_meta_set(conn, CURL_META_RTSP_CONN, rtspc, rtsp_conn_dtor))
116
0
    return CURLE_OUT_OF_MEMORY;
117
118
0
  rtsp = curlx_calloc(1, sizeof(struct RTSP));
119
0
  if(!rtsp ||
120
0
     Curl_meta_set(data, CURL_META_RTSP_EASY, rtsp, rtsp_easy_dtor))
121
0
    return CURLE_OUT_OF_MEMORY;
122
123
0
  return CURLE_OK;
124
0
}
125
126
/*
127
 * Function to check on various aspects of a connection.
128
 */
129
static uint32_t rtsp_conncheck(struct Curl_easy *data,
130
                               struct connectdata *conn,
131
                               uint32_t checks_to_perform)
132
0
{
133
0
  unsigned int ret_val = CONNRESULT_NONE;
134
135
0
  if(checks_to_perform & CONNCHECK_ISDEAD) {
136
0
    bool input_pending;
137
0
    if(!Curl_conn_is_alive(data, conn, &input_pending))
138
0
      ret_val |= CONNRESULT_DEAD;
139
0
  }
140
141
0
  return ret_val;
142
0
}
143
144
static CURLcode rtsp_connect(struct Curl_easy *data, bool *done)
145
0
{
146
0
  struct rtsp_conn *rtspc =
147
0
    Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
148
149
0
  if(!rtspc)
150
0
    return CURLE_FAILED_INIT;
151
152
  /* Initialize the CSeq if not already done */
153
0
  if(data->state.rtsp_next_client_CSeq == 0)
154
0
    data->state.rtsp_next_client_CSeq = 1;
155
0
  if(data->state.rtsp_next_server_CSeq == 0)
156
0
    data->state.rtsp_next_server_CSeq = 1;
157
158
0
  rtspc->rtp_channel = -1;
159
0
  *done = TRUE;
160
0
  return CURLE_OK;
161
0
}
162
163
static CURLcode rtsp_done(struct Curl_easy *data,
164
                          CURLcode status, bool premature)
165
0
{
166
0
  struct rtsp_conn *rtspc =
167
0
    Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
168
0
  struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
169
0
  CURLcode httpStatus;
170
171
0
  if(!rtspc || !rtsp)
172
0
    return CURLE_FAILED_INIT;
173
174
  /* Bypass HTTP empty-reply checks on receive */
175
0
  if(data->set.rtspreq == RTSPREQ_RECEIVE)
176
0
    premature = TRUE;
177
178
0
  httpStatus = Curl_http_done(data, status, premature);
179
180
0
  if(!status && !httpStatus) {
181
    /* Check the sequence numbers */
182
0
    uint32_t CSeq_sent = rtsp->CSeq_sent;
183
0
    uint32_t CSeq_recv = rtsp->CSeq_recv;
184
0
    if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) {
185
0
      failf(data,
186
0
            "The CSeq of this request %u did not match the response %u",
187
0
            CSeq_sent, CSeq_recv);
188
0
      return CURLE_RTSP_CSEQ_ERROR;
189
0
    }
190
0
    if(data->set.rtspreq == RTSPREQ_RECEIVE && (rtspc->rtp_channel == -1)) {
191
0
      infof(data, "Got an RTP Receive with a CSeq of %u", CSeq_recv);
192
0
    }
193
0
    if(data->set.rtspreq == RTSPREQ_RECEIVE &&
194
0
       data->req.eos_written) {
195
0
      failf(data, "Server prematurely closed the RTSP connection.");
196
0
      return CURLE_RECV_ERROR;
197
0
    }
198
0
  }
199
200
0
  return httpStatus;
201
0
}
202
203
static CURLcode rtsp_setup_body(struct Curl_easy *data,
204
                                Curl_RtspReq rtspreq,
205
                                struct dynbuf *reqp)
206
0
{
207
0
  CURLcode result;
208
0
  if(rtspreq == RTSPREQ_ANNOUNCE ||
209
0
     rtspreq == RTSPREQ_SET_PARAMETER ||
210
0
     rtspreq == RTSPREQ_GET_PARAMETER) {
211
0
    curl_off_t req_clen; /* request content length */
212
213
0
    if(data->state.upload) {
214
0
      req_clen = data->state.infilesize;
215
0
      data->state.httpreq = HTTPREQ_PUT;
216
0
      result = Curl_creader_set_fread(data, req_clen);
217
0
      if(result)
218
0
        return result;
219
0
    }
220
0
    else {
221
0
      if(data->set.postfields) {
222
0
        size_t plen = (data->set.postfieldsize >= 0) ?
223
0
          (size_t)data->set.postfieldsize : strlen(data->set.postfields);
224
0
        req_clen = (curl_off_t)plen;
225
0
        result = Curl_creader_set_buf(data, data->set.postfields, plen);
226
0
      }
227
0
      else if(data->state.infilesize >= 0) {
228
0
        req_clen = data->state.infilesize;
229
0
        result = Curl_creader_set_fread(data, req_clen);
230
0
      }
231
0
      else {
232
0
        req_clen = 0;
233
0
        result = Curl_creader_set_null(data);
234
0
      }
235
0
      if(result)
236
0
        return result;
237
0
    }
238
239
0
    if(req_clen > 0) {
240
      /* As stated in the http comments, it is probably not wise to
241
       * actually set a custom Content-Length in the headers */
242
0
      if(!Curl_checkheaders(data, STRCONST("Content-Length"))) {
243
0
        result = curlx_dyn_addf(reqp, "Content-Length: %" FMT_OFF_T"\r\n",
244
0
                                req_clen);
245
0
        if(result)
246
0
          return result;
247
0
      }
248
249
0
      if(rtspreq == RTSPREQ_SET_PARAMETER ||
250
0
         rtspreq == RTSPREQ_GET_PARAMETER) {
251
0
        if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
252
0
          result = curlx_dyn_addn(reqp, STRCONST("Content-Type: "
253
0
                                                 "text/parameters\r\n"));
254
0
          if(result)
255
0
            return result;
256
0
        }
257
0
      }
258
259
0
      if(rtspreq == RTSPREQ_ANNOUNCE) {
260
0
        if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
261
0
          result = curlx_dyn_addn(reqp, STRCONST("Content-Type: "
262
0
                                                 "application/sdp\r\n"));
263
0
          if(result)
264
0
            return result;
265
0
        }
266
0
      }
267
0
    }
268
0
    else if(rtspreq == RTSPREQ_GET_PARAMETER) {
269
      /* Check for an empty GET_PARAMETER (heartbeat) request */
270
0
      data->state.httpreq = HTTPREQ_HEAD;
271
0
      data->req.no_body = TRUE;
272
0
    }
273
0
  }
274
0
  else
275
0
    result = Curl_creader_set_null(data);
276
0
  return result;
277
0
}
278
279
static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
280
0
{
281
0
  struct connectdata *conn = data->conn;
282
0
  CURLcode result = CURLE_OK;
283
0
  const Curl_RtspReq rtspreq = data->set.rtspreq;
284
0
  struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
285
0
  struct dynbuf req_buffer;
286
0
  const unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort
287
                                           of... */
288
0
  const char *p_request = NULL;
289
0
  const char *p_session_id = NULL;
290
0
  const char *p_accept = NULL;
291
0
  const char *p_accept_encoding = NULL;
292
0
  const char *p_range = NULL;
293
0
  const char *p_referrer = NULL;
294
0
  const char *p_stream_uri = NULL;
295
0
  const char *p_transport = NULL;
296
0
  const char *p_uagent = NULL;
297
0
  const char *p_proxyuserpwd = NULL;
298
0
  const char *p_userpwd = NULL;
299
300
0
  *done = TRUE;
301
0
  if(!rtsp)
302
0
    return CURLE_FAILED_INIT;
303
304
  /* Initialize a dynamic send buffer */
305
0
  curlx_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER);
306
307
0
  rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq;
308
0
  rtsp->CSeq_recv = 0;
309
310
  /* Setup the first_* fields to allow auth details get sent
311
     to this origin */
312
313
0
  if(!data->state.first_host) {
314
0
    data->state.first_host = curlx_strdup(conn->host.name);
315
0
    if(!data->state.first_host)
316
0
      return CURLE_OUT_OF_MEMORY;
317
318
0
    data->state.first_remote_port = conn->remote_port;
319
0
    data->state.first_remote_protocol = conn->scheme->protocol;
320
0
  }
321
322
  /* Setup the 'p_request' pointer to the proper p_request string
323
   * Since all RTSP requests are included here, there is no need to
324
   * support custom requests like HTTP.
325
   **/
326
0
  data->req.no_body = TRUE; /* most requests do not contain a body */
327
0
  switch(rtspreq) {
328
0
  default:
329
0
    failf(data, "Got invalid RTSP request");
330
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
331
0
  case RTSPREQ_OPTIONS:
332
0
    p_request = "OPTIONS";
333
0
    break;
334
0
  case RTSPREQ_DESCRIBE:
335
0
    p_request = "DESCRIBE";
336
0
    data->req.no_body = FALSE;
337
0
    break;
338
0
  case RTSPREQ_ANNOUNCE:
339
0
    p_request = "ANNOUNCE";
340
0
    break;
341
0
  case RTSPREQ_SETUP:
342
0
    p_request = "SETUP";
343
0
    break;
344
0
  case RTSPREQ_PLAY:
345
0
    p_request = "PLAY";
346
0
    break;
347
0
  case RTSPREQ_PAUSE:
348
0
    p_request = "PAUSE";
349
0
    break;
350
0
  case RTSPREQ_TEARDOWN:
351
0
    p_request = "TEARDOWN";
352
0
    break;
353
0
  case RTSPREQ_GET_PARAMETER:
354
    /* GET_PARAMETER's no_body status is determined later */
355
0
    p_request = "GET_PARAMETER";
356
0
    data->req.no_body = FALSE;
357
0
    break;
358
0
  case RTSPREQ_SET_PARAMETER:
359
0
    p_request = "SET_PARAMETER";
360
0
    break;
361
0
  case RTSPREQ_RECORD:
362
0
    p_request = "RECORD";
363
0
    break;
364
0
  case RTSPREQ_RECEIVE:
365
0
    p_request = "";
366
    /* Treat interleaved RTP as body */
367
0
    data->req.no_body = FALSE;
368
0
    break;
369
0
  case RTSPREQ_LAST:
370
0
    failf(data, "Got invalid RTSP request: RTSPREQ_LAST");
371
0
    return CURLE_BAD_FUNCTION_ARGUMENT;
372
0
  }
373
374
0
  if(rtspreq == RTSPREQ_RECEIVE) {
375
0
    Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
376
0
    goto out;
377
0
  }
378
379
0
  p_session_id = data->set.str[STRING_RTSP_SESSION_ID];
380
0
  if(!p_session_id &&
381
0
     (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS |
382
0
                                RTSPREQ_DESCRIBE |
383
0
                                RTSPREQ_SETUP))) {
384
0
    failf(data, "Refusing to issue an RTSP request [%s] without a session ID.",
385
0
          p_request);
386
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
387
0
    goto out;
388
0
  }
389
390
  /* Stream URI. Default to server '*' if not specified */
391
0
  if(data->set.str[STRING_RTSP_STREAM_URI]) {
392
0
    p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI];
393
0
  }
394
0
  else {
395
0
    p_stream_uri = "*";
396
0
  }
397
398
  /* Transport Header for SETUP requests */
399
0
  p_transport = Curl_checkheaders(data, STRCONST("Transport"));
400
0
  if(rtspreq == RTSPREQ_SETUP && !p_transport) {
401
    /* New Transport: setting? */
402
0
    if(data->set.str[STRING_RTSP_TRANSPORT]) {
403
0
      curlx_free(data->state.aptr.rtsp_transport);
404
0
      data->state.aptr.rtsp_transport =
405
0
        curl_maprintf("Transport: %s\r\n",
406
0
                      data->set.str[STRING_RTSP_TRANSPORT]);
407
0
      if(!data->state.aptr.rtsp_transport)
408
0
        return CURLE_OUT_OF_MEMORY;
409
0
    }
410
0
    else {
411
0
      failf(data,
412
0
            "Refusing to issue an RTSP SETUP without a Transport: header.");
413
0
      result = CURLE_BAD_FUNCTION_ARGUMENT;
414
0
      goto out;
415
0
    }
416
417
0
    p_transport = data->state.aptr.rtsp_transport;
418
0
  }
419
420
  /* Accept Headers for DESCRIBE requests */
421
0
  if(rtspreq == RTSPREQ_DESCRIBE) {
422
    /* Accept Header */
423
0
    p_accept = Curl_checkheaders(data, STRCONST("Accept")) ?
424
0
      NULL : "Accept: application/sdp\r\n";
425
426
    /* Accept-Encoding header */
427
0
    if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
428
0
       data->set.str[STRING_ENCODING]) {
429
0
      curlx_free(data->state.aptr.accept_encoding);
430
0
      data->state.aptr.accept_encoding =
431
0
        curl_maprintf("Accept-Encoding: %s\r\n",
432
0
                      data->set.str[STRING_ENCODING]);
433
434
0
      if(!data->state.aptr.accept_encoding) {
435
0
        result = CURLE_OUT_OF_MEMORY;
436
0
        goto out;
437
0
      }
438
0
      p_accept_encoding = data->state.aptr.accept_encoding;
439
0
    }
440
0
  }
441
442
  /* The User-Agent string might have been allocated in url.c already, because
443
     it might have been used in the proxy connect, but if we have got a header
444
     with the user-agent string specified, we erase the previously made string
445
     here. */
446
0
  if(Curl_checkheaders(data, STRCONST("User-Agent")) &&
447
0
     data->state.aptr.uagent) {
448
0
    Curl_safefree(data->state.aptr.uagent);
449
0
  }
450
0
  else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
451
0
          data->set.str[STRING_USERAGENT]) {
452
0
    p_uagent = data->state.aptr.uagent;
453
0
  }
454
455
  /* setup the authentication headers */
456
0
  result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET,
457
0
                                 p_stream_uri, FALSE);
458
0
  if(result)
459
0
    goto out;
460
461
0
#ifndef CURL_DISABLE_PROXY
462
0
  p_proxyuserpwd = data->state.aptr.proxyuserpwd;
463
0
#endif
464
0
  p_userpwd = data->state.aptr.userpwd;
465
466
  /* Referrer */
467
0
  Curl_safefree(data->state.aptr.ref);
468
0
  if(Curl_bufref_ptr(&data->state.referer) &&
469
0
     !Curl_checkheaders(data, STRCONST("Referer")))
470
0
    data->state.aptr.ref =
471
0
      curl_maprintf("Referer: %s\r\n", Curl_bufref_ptr(&data->state.referer));
472
473
0
  p_referrer = data->state.aptr.ref;
474
475
  /*
476
   * Range Header
477
   * Only applies to PLAY, PAUSE, RECORD
478
   *
479
   * Go ahead and use the Range stuff supplied for HTTP
480
   */
481
0
  if(data->state.use_range &&
482
0
     (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) {
483
484
    /* Check to see if there is a range set in the custom headers */
485
0
    if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) {
486
0
      curlx_free(data->state.aptr.rangeline);
487
0
      data->state.aptr.rangeline = curl_maprintf("Range: %s\r\n",
488
0
                                                 data->state.range);
489
0
      p_range = data->state.aptr.rangeline;
490
0
    }
491
0
  }
492
493
  /*
494
   * Sanity check the custom headers
495
   */
496
0
  if(Curl_checkheaders(data, STRCONST("CSeq"))) {
497
0
    failf(data, "CSeq cannot be set as a custom header.");
498
0
    result = CURLE_RTSP_CSEQ_ERROR;
499
0
    goto out;
500
0
  }
501
0
  if(Curl_checkheaders(data, STRCONST("Session"))) {
502
0
    failf(data, "Session ID cannot be set as a custom header.");
503
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
504
0
    goto out;
505
0
  }
506
507
0
  result =
508
0
    curlx_dyn_addf(&req_buffer,
509
0
                   "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */
510
0
                   "CSeq: %u\r\n", /* CSeq */
511
0
                   p_request, p_stream_uri, rtsp->CSeq_sent);
512
0
  if(result)
513
0
    goto out;
514
515
  /*
516
   * Rather than do a normal alloc line, keep the session_id unformatted
517
   * to make comparison easier
518
   */
519
0
  if(p_session_id) {
520
0
    result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id);
521
0
    if(result)
522
0
      goto out;
523
0
  }
524
525
  /*
526
   * Shared HTTP-like options
527
   */
528
0
  result = curlx_dyn_addf(&req_buffer,
529
0
                          "%s" /* transport */
530
0
                          "%s" /* accept */
531
0
                          "%s" /* accept-encoding */
532
0
                          "%s" /* range */
533
0
                          "%s" /* referrer */
534
0
                          "%s" /* user-agent */
535
0
                          "%s" /* proxyuserpwd */
536
0
                          "%s" /* userpwd */
537
0
                          ,
538
0
                          p_transport ? p_transport : "",
539
0
                          p_accept ? p_accept : "",
540
0
                          p_accept_encoding ? p_accept_encoding : "",
541
0
                          p_range ? p_range : "",
542
0
                          p_referrer ? p_referrer : "",
543
0
                          p_uagent ? p_uagent : "",
544
0
                          p_proxyuserpwd ? p_proxyuserpwd : "",
545
0
                          p_userpwd ? p_userpwd : "");
546
547
  /*
548
   * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM
549
   * with basic and digest, it will be freed anyway by the next request
550
   */
551
0
  Curl_safefree(data->state.aptr.userpwd);
552
553
0
  if(result)
554
0
    goto out;
555
556
0
  if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) {
557
0
    result = Curl_add_timecondition(data, &req_buffer);
558
0
    if(result)
559
0
      goto out;
560
0
  }
561
562
0
  result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
563
0
  if(result)
564
0
    goto out;
565
566
0
  result = rtsp_setup_body(data, rtspreq, &req_buffer);
567
0
  if(result)
568
0
    goto out;
569
570
  /* Finish the request buffer */
571
0
  result = curlx_dyn_addn(&req_buffer, STRCONST("\r\n"));
572
0
  if(result)
573
0
    goto out;
574
575
0
  Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1);
576
577
  /* issue the request */
578
0
  result = Curl_req_send(data, &req_buffer, httpversion);
579
0
  if(result) {
580
0
    failf(data, "Failed sending RTSP request");
581
0
    goto out;
582
0
  }
583
584
  /* Increment the CSeq on success */
585
0
  data->state.rtsp_next_client_CSeq++;
586
587
0
  if(data->req.writebytecount) {
588
    /* if a request-body has been sent off, we make sure this progress is
589
       noted properly */
590
0
    Curl_pgrsSetUploadCounter(data, data->req.writebytecount);
591
0
    result = Curl_pgrsUpdate(data);
592
0
  }
593
0
out:
594
0
  curlx_dyn_free(&req_buffer);
595
0
  return result;
596
0
}
597
598
/**
599
 * write any BODY bytes missing to the client, ignore the rest.
600
 */
601
static CURLcode rtp_write_body_junk(struct Curl_easy *data,
602
                                    struct rtsp_conn *rtspc,
603
                                    const char *buf,
604
                                    size_t blen)
605
0
{
606
0
  curl_off_t body_remain;
607
0
  bool in_body;
608
609
0
  in_body = (data->req.headerline && !rtspc->in_header) &&
610
0
            (data->req.size >= 0) &&
611
0
            (data->req.bytecount < data->req.size);
612
0
  body_remain = in_body ? (data->req.size - data->req.bytecount) : 0;
613
0
  DEBUGASSERT(body_remain >= 0);
614
0
  if(body_remain) {
615
0
    if((curl_off_t)blen > body_remain)
616
0
      blen = (size_t)body_remain;
617
0
    return Curl_client_write(data, CLIENTWRITE_BODY, buf, blen);
618
0
  }
619
0
  return CURLE_OK;
620
0
}
621
622
static CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr,
623
                                 size_t len)
624
0
{
625
0
  size_t wrote;
626
0
  curl_write_callback writeit;
627
0
  void *user_ptr;
628
629
0
  if(len == 0) {
630
0
    failf(data, "Cannot write a 0 size RTP packet.");
631
0
    return CURLE_WRITE_ERROR;
632
0
  }
633
634
  /* If the user has configured CURLOPT_INTERLEAVEFUNCTION then use that
635
     function and any configured CURLOPT_INTERLEAVEDATA to write out the RTP
636
     data. Otherwise, use the CURLOPT_WRITEFUNCTION with the CURLOPT_WRITEDATA
637
     pointer to write out the RTP data. */
638
0
  if(data->set.fwrite_rtp) {
639
0
    writeit = data->set.fwrite_rtp;
640
0
    user_ptr = data->set.rtp_out;
641
0
  }
642
0
  else {
643
0
    writeit = data->set.fwrite_func;
644
0
    user_ptr = data->set.out;
645
0
  }
646
647
0
  Curl_set_in_callback(data, TRUE);
648
0
  wrote = writeit((char *)CURL_UNCONST(ptr), 1, len, user_ptr);
649
0
  Curl_set_in_callback(data, FALSE);
650
651
0
  if(CURL_WRITEFUNC_PAUSE == wrote) {
652
0
    failf(data, "Cannot pause RTP");
653
0
    return CURLE_WRITE_ERROR;
654
0
  }
655
656
0
  if(wrote != len) {
657
0
    failf(data, "Failed writing RTP data");
658
0
    return CURLE_WRITE_ERROR;
659
0
  }
660
661
0
  return CURLE_OK;
662
0
}
663
664
static CURLcode rtsp_filter_rtp(struct Curl_easy *data,
665
                                struct rtsp_conn *rtspc,
666
                                const char *buf,
667
                                size_t blen,
668
                                size_t *pconsumed)
669
0
{
670
0
  CURLcode result = CURLE_OK;
671
0
  size_t skip_len = 0;
672
673
0
  *pconsumed = 0;
674
0
  while(blen) {
675
0
    bool in_body = (data->req.headerline && !rtspc->in_header) &&
676
0
                   (data->req.size >= 0) &&
677
0
                   (data->req.bytecount < data->req.size);
678
0
    switch(rtspc->state) {
679
680
0
    case RTP_PARSE_SKIP: {
681
0
      DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 0);
682
0
      while(blen && buf[0] != '$') {
683
0
        if(!in_body && buf[0] == 'R' &&
684
0
           data->set.rtspreq != RTSPREQ_RECEIVE) {
685
0
          if(strncmp(buf, "RTSP/", (blen < 5) ? blen : 5) == 0) {
686
            /* This could be the next response, no consume and return */
687
0
            if(*pconsumed) {
688
0
              DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, "
689
0
                           "skipping %zd bytes of junk", *pconsumed));
690
0
            }
691
0
            rtspc->state = RTP_PARSE_SKIP;
692
0
            rtspc->in_header = TRUE;
693
0
            goto out;
694
0
          }
695
0
        }
696
        /* junk/BODY, consume without buffering */
697
0
        *pconsumed += 1;
698
0
        ++buf;
699
0
        --blen;
700
0
        ++skip_len;
701
0
      }
702
0
      if(blen && buf[0] == '$') {
703
        /* possible start of an RTP message, buffer */
704
0
        if(skip_len) {
705
          /* end of junk/BODY bytes, flush */
706
0
          result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len);
707
0
          skip_len = 0;
708
0
          if(result)
709
0
            goto out;
710
0
        }
711
0
        if(curlx_dyn_addn(&rtspc->buf, buf, 1)) {
712
0
          result = CURLE_OUT_OF_MEMORY;
713
0
          goto out;
714
0
        }
715
0
        *pconsumed += 1;
716
0
        ++buf;
717
0
        --blen;
718
0
        rtspc->state = RTP_PARSE_CHANNEL;
719
0
      }
720
0
      break;
721
0
    }
722
723
0
    case RTP_PARSE_CHANNEL: {
724
0
      int idx = ((unsigned char)buf[0]) / 8;
725
0
      int off = ((unsigned char)buf[0]) % 8;
726
0
      DEBUGASSERT(curlx_dyn_len(&rtspc->buf) == 1);
727
0
      if(!(data->state.rtp_channel_mask[idx] & (1 << off))) {
728
        /* invalid channel number, junk or BODY data */
729
0
        rtspc->state = RTP_PARSE_SKIP;
730
0
        DEBUGASSERT(skip_len == 0);
731
        /* we do not consume this byte, it is BODY data */
732
0
        DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx));
733
0
        if(*pconsumed == 0) {
734
          /* We did not consume the initial '$' in our buffer, but had
735
           * it from an earlier call. We cannot un-consume it and have
736
           * to write it directly as BODY data */
737
0
          result = rtp_write_body_junk(data, rtspc,
738
0
                                       curlx_dyn_ptr(&rtspc->buf), 1);
739
0
          if(result)
740
0
            goto out;
741
0
        }
742
0
        else {
743
          /* count the '$' as skip and continue */
744
0
          skip_len = 1;
745
0
        }
746
0
        curlx_dyn_free(&rtspc->buf);
747
0
        break;
748
0
      }
749
      /* a valid channel, so we expect this to be a real RTP message */
750
0
      rtspc->rtp_channel = (unsigned char)buf[0];
751
0
      if(curlx_dyn_addn(&rtspc->buf, buf, 1)) {
752
0
        result = CURLE_OUT_OF_MEMORY;
753
0
        goto out;
754
0
      }
755
0
      *pconsumed += 1;
756
0
      ++buf;
757
0
      --blen;
758
0
      rtspc->state = RTP_PARSE_LEN;
759
0
      break;
760
0
    }
761
762
0
    case RTP_PARSE_LEN: {
763
0
      size_t rtp_len = curlx_dyn_len(&rtspc->buf);
764
0
      const char *rtp_buf;
765
0
      DEBUGASSERT(rtp_len >= 2 && rtp_len < 4);
766
0
      if(curlx_dyn_addn(&rtspc->buf, buf, 1)) {
767
0
        result = CURLE_OUT_OF_MEMORY;
768
0
        goto out;
769
0
      }
770
0
      *pconsumed += 1;
771
0
      ++buf;
772
0
      --blen;
773
0
      if(rtp_len == 2)
774
0
        break;
775
0
      rtp_buf = curlx_dyn_ptr(&rtspc->buf);
776
0
      rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4;
777
0
      rtspc->state = RTP_PARSE_DATA;
778
0
      break;
779
0
    }
780
781
0
    case RTP_PARSE_DATA: {
782
0
      size_t rtp_len = curlx_dyn_len(&rtspc->buf);
783
0
      size_t needed;
784
0
      DEBUGASSERT(rtp_len < rtspc->rtp_len);
785
0
      needed = rtspc->rtp_len - rtp_len;
786
0
      if(needed <= blen) {
787
0
        if(curlx_dyn_addn(&rtspc->buf, buf, needed)) {
788
0
          result = CURLE_OUT_OF_MEMORY;
789
0
          goto out;
790
0
        }
791
0
        *pconsumed += needed;
792
0
        buf += needed;
793
0
        blen -= needed;
794
        /* complete RTP message in buffer */
795
0
        DEBUGF(infof(data, "RTP write channel %d rtp_len %zu",
796
0
                     rtspc->rtp_channel, rtspc->rtp_len));
797
0
        result = rtp_client_write(data, curlx_dyn_ptr(&rtspc->buf),
798
0
                                  rtspc->rtp_len);
799
0
        curlx_dyn_free(&rtspc->buf);
800
0
        rtspc->state = RTP_PARSE_SKIP;
801
0
        if(result)
802
0
          goto out;
803
0
      }
804
0
      else {
805
0
        if(curlx_dyn_addn(&rtspc->buf, buf, blen)) {
806
0
          result = CURLE_OUT_OF_MEMORY;
807
0
          goto out;
808
0
        }
809
0
        *pconsumed += blen;
810
0
        buf += blen;
811
0
        blen = 0;
812
0
      }
813
0
      break;
814
0
    }
815
816
0
    default:
817
0
      DEBUGASSERT(0);
818
0
      return CURLE_RECV_ERROR;
819
0
    }
820
0
  }
821
0
out:
822
0
  if(!result && skip_len)
823
0
    result = rtp_write_body_junk(data, rtspc, buf - skip_len, skip_len);
824
0
  return result;
825
0
}
826
827
/*
828
 * Parse and write out an RTSP response.
829
 * @param data     the transfer
830
 * @param conn     the connection
831
 * @param buf      data read from connection
832
 * @param blen     amount of data in buf
833
 * @param is_eos   TRUE iff this is the last write
834
 * @param readmore out, TRUE iff complete buf was consumed and more data
835
 *                 is needed
836
 */
837
static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data,
838
                                    const char *buf,
839
                                    size_t blen,
840
                                    bool is_eos)
841
0
{
842
0
  struct rtsp_conn *rtspc =
843
0
    Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
844
0
  CURLcode result = CURLE_OK;
845
0
  size_t consumed = 0;
846
847
0
  if(!rtspc)
848
0
    return CURLE_FAILED_INIT;
849
850
0
  if(!data->req.header)
851
0
    rtspc->in_header = FALSE;
852
0
  if(!blen) {
853
0
    goto out;
854
0
  }
855
856
0
  DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)",
857
0
               blen, rtspc->in_header, is_eos));
858
859
  /* If header parsing is not ongoing, extract RTP messages */
860
0
  if(!rtspc->in_header) {
861
0
    result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed);
862
0
    if(result)
863
0
      goto out;
864
0
    buf += consumed;
865
0
    blen -= consumed;
866
    /* either we consumed all or are at the start of header parsing */
867
0
    if(blen && !data->req.header)
868
0
      DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body",
869
0
                   blen));
870
0
  }
871
872
  /* we want to parse headers, do so */
873
0
  if(data->req.header && blen) {
874
0
    rtspc->in_header = TRUE;
875
0
    result = Curl_http_write_resp_hds(data, buf, blen, &consumed);
876
0
    if(result)
877
0
      goto out;
878
879
0
    buf += consumed;
880
0
    blen -= consumed;
881
882
0
    if(!data->req.header)
883
0
      rtspc->in_header = FALSE;
884
885
0
    if(!rtspc->in_header) {
886
      /* If header parsing is done, extract interleaved RTP messages */
887
0
      if(data->req.size <= -1) {
888
        /* Respect section 4.4 of rfc2326: If the Content-Length header is
889
           absent, a length 0 must be assumed. */
890
0
        data->req.size = 0;
891
0
        data->req.download_done = TRUE;
892
0
      }
893
0
      result = rtsp_filter_rtp(data, rtspc, buf, blen, &consumed);
894
0
      if(result)
895
0
        goto out;
896
0
      blen -= consumed;
897
0
    }
898
0
  }
899
900
0
  if(rtspc->state != RTP_PARSE_SKIP)
901
0
    data->req.done = FALSE;
902
  /* we SHOULD have consumed all bytes, unless the response is borked.
903
   * In which case we write out the left over bytes, letting the client
904
   * writer deal with it (it will report EXCESS and fail the transfer). */
905
0
  DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d "
906
0
               " rtspc->state=%d, req.size=%" FMT_OFF_T ")",
907
0
               blen, rtspc->in_header, data->req.done, rtspc->state,
908
0
               data->req.size));
909
0
  if(!result && (is_eos || blen)) {
910
0
    result = Curl_client_write(data, CLIENTWRITE_BODY |
911
0
                               (is_eos ? CLIENTWRITE_EOS : 0), buf, blen);
912
0
  }
913
914
0
out:
915
0
  if((data->set.rtspreq == RTSPREQ_RECEIVE) &&
916
0
     (rtspc->state == RTP_PARSE_SKIP)) {
917
    /* In special mode RECEIVE, we just process one chunk of network
918
     * data, so we stop the transfer here, if we have no incomplete
919
     * RTP message pending. */
920
0
    data->req.download_done = TRUE;
921
0
  }
922
0
  return result;
923
0
}
924
925
static CURLcode rtsp_rtp_write_resp_hd(struct Curl_easy *data,
926
                                       const char *buf,
927
                                       size_t blen,
928
                                       bool is_eos)
929
0
{
930
0
  return rtsp_rtp_write_resp(data, buf, blen, is_eos);
931
0
}
932
933
static CURLcode rtsp_parse_transport(struct Curl_easy *data,
934
                                     const char *transport)
935
0
{
936
  /* If we receive multiple Transport response-headers, the interleaved
937
     channels of each response header is recorded and used together for
938
     subsequent data validity checks.*/
939
  /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */
940
0
  const char *start, *end;
941
0
  start = transport;
942
0
  while(start && *start) {
943
0
    curlx_str_passblanks(&start);
944
0
    end = strchr(start, ';');
945
0
    if(checkprefix("interleaved=", start)) {
946
0
      curl_off_t chan1, chan2, chan;
947
0
      const char *p = start + 12;
948
0
      if(!curlx_str_number(&p, &chan1, 255)) {
949
0
        unsigned char *rtp_channel_mask = data->state.rtp_channel_mask;
950
0
        chan2 = chan1;
951
0
        if(!curlx_str_single(&p, '-')) {
952
0
          if(curlx_str_number(&p, &chan2, 255)) {
953
0
            infof(data, "Unable to read the interleaved parameter from "
954
0
                  "Transport header: [%s]", transport);
955
0
            chan2 = chan1;
956
0
          }
957
0
        }
958
0
        for(chan = chan1; chan <= chan2; chan++) {
959
0
          int idx = (int)chan / 8;
960
0
          int off = (int)chan % 8;
961
0
          rtp_channel_mask[idx] |= (unsigned char)(1 << off);
962
0
        }
963
0
      }
964
0
      else {
965
0
        infof(data, "Unable to read the interleaved parameter from "
966
0
              "Transport header: [%s]", transport);
967
0
      }
968
0
      break;
969
0
    }
970
    /* skip to next parameter */
971
0
    start = (!end) ? end : (end + 1);
972
0
  }
973
0
  return CURLE_OK;
974
0
}
975
976
CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header)
977
0
{
978
0
  if(checkprefix("CSeq:", header)) {
979
0
    curl_off_t CSeq = 0;
980
0
    struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
981
0
    const char *p = &header[5];
982
0
    if(!rtsp)
983
0
      return CURLE_FAILED_INIT;
984
0
    curlx_str_passblanks(&p);
985
0
    if(curlx_str_number(&p, &CSeq, UINT_MAX)) {
986
0
      failf(data, "Unable to read the CSeq header: [%s]", header);
987
0
      return CURLE_RTSP_CSEQ_ERROR;
988
0
    }
989
0
    data->state.rtsp_CSeq_recv = rtsp->CSeq_recv = (uint32_t)CSeq;
990
0
  }
991
0
  else if(checkprefix("Session:", header)) {
992
0
    const char *start, *end;
993
0
    size_t idlen;
994
995
    /* Find the first non-space letter */
996
0
    start = header + 8;
997
0
    curlx_str_passblanks(&start);
998
999
0
    if(!*start) {
1000
0
      failf(data, "Got a blank Session ID");
1001
0
      return CURLE_RTSP_SESSION_ERROR;
1002
0
    }
1003
1004
    /* Find the end of Session ID
1005
     *
1006
     * Allow any non whitespace content, up to the field separator or end of
1007
     * line. RFC 2326 is not 100% clear on the session ID and for example
1008
     * gstreamer does url-encoded session ID's not covered by the standard.
1009
     */
1010
0
    end = start;
1011
0
    while((*end > ' ') && (*end != ';'))
1012
0
      end++;
1013
0
    idlen = end - start;
1014
1015
0
    if(data->set.str[STRING_RTSP_SESSION_ID]) {
1016
1017
      /* If the Session ID is set, then compare */
1018
0
      if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen ||
1019
0
         strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) {
1020
0
        failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]",
1021
0
              start, data->set.str[STRING_RTSP_SESSION_ID]);
1022
0
        return CURLE_RTSP_SESSION_ERROR;
1023
0
      }
1024
0
    }
1025
0
    else {
1026
      /* If the Session ID is not set, and we find it in a response, then set
1027
       * it.
1028
       */
1029
1030
      /* Copy the id substring into a new buffer */
1031
0
      data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen);
1032
0
      if(!data->set.str[STRING_RTSP_SESSION_ID])
1033
0
        return CURLE_OUT_OF_MEMORY;
1034
0
    }
1035
0
  }
1036
0
  else if(checkprefix("Transport:", header)) {
1037
0
    CURLcode result;
1038
0
    result = rtsp_parse_transport(data, header + 10);
1039
0
    if(result)
1040
0
      return result;
1041
0
  }
1042
0
  return CURLE_OK;
1043
0
}
1044
1045
/*
1046
 * RTSP handler interface.
1047
 */
1048
static const struct Curl_protocol Curl_protocol_rtsp = {
1049
  rtsp_setup_connection,                /* setup_connection */
1050
  rtsp_do,                              /* do_it */
1051
  rtsp_done,                            /* done */
1052
  ZERO_NULL,                            /* do_more */
1053
  rtsp_connect,                         /* connect_it */
1054
  ZERO_NULL,                            /* connecting */
1055
  ZERO_NULL,                            /* doing */
1056
  ZERO_NULL,                            /* proto_pollset */
1057
  rtsp_do_pollset,                      /* doing_pollset */
1058
  ZERO_NULL,                            /* domore_pollset */
1059
  Curl_http_perform_pollset,            /* perform_pollset */
1060
  ZERO_NULL,                            /* disconnect */
1061
  rtsp_rtp_write_resp,                  /* write_resp */
1062
  rtsp_rtp_write_resp_hd,               /* write_resp_hd */
1063
  rtsp_conncheck,                       /* connection_check */
1064
  ZERO_NULL,                            /* attach connection */
1065
  Curl_http_follow,                     /* follow */
1066
};
1067
1068
#endif /* CURL_DISABLE_RTSP */
1069
1070
/*
1071
 * RTSP handler interface.
1072
 */
1073
const struct Curl_scheme Curl_scheme_rtsp = {
1074
  "rtsp",                               /* scheme */
1075
#ifdef CURL_DISABLE_RTSP
1076
  ZERO_NULL,
1077
#else
1078
  &Curl_protocol_rtsp,
1079
#endif
1080
  CURLPROTO_RTSP,                       /* protocol */
1081
  CURLPROTO_RTSP,                       /* family */
1082
  PROTOPT_CONN_REUSE,                   /* flags */
1083
  PORT_RTSP,                            /* defport */
1084
};