rtsp: refactor method handling and improve error checks

- convert the method switch() to a simple table

- avoid converting the methods from external to internal numbers, they were
  the same anyway so keep the external ones, just use the old defines.

- fix range check. It wrongly used the method numbers as bitmask, which made
  the check not work previously. Also error on OOM.

- Dropped the session-id check. It too wrongly did a bitmask check which was
  wrong and never worked. When fixed, it broke test cases so I dropped the
  entire check.

- split out rtsp_setup_request() from rtsp_do()

- replace the httpversion variable with a define

Closes #22505
This commit is contained in:
Daniel Stenberg 2026-08-05 23:40:18 +02:00
parent a478393759
commit 26b9f3aa9b
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
5 changed files with 167 additions and 204 deletions

View file

@ -195,7 +195,7 @@ static CURLcode rtsp_done(struct Curl_easy *data,
}
static CURLcode rtsp_setup_body(struct Curl_easy *data,
Curl_RtspReq rtspreq,
unsigned char rtspreq,
struct dynbuf *reqp)
{
CURLcode result;
@ -270,154 +270,115 @@ static CURLcode rtsp_setup_body(struct Curl_easy *data,
return result;
}
static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
struct rtspselect {
const char *method;
bool no_body;
};
static CURLcode pick_method(struct Curl_easy *data,
const unsigned char rtspreq,
const char **p)
{
static const struct rtspselect req[] = {
{ "OPTIONS", TRUE },
{ "DESCRIBE", FALSE },
{ "ANNOUNCE", TRUE },
{ "SETUP", TRUE },
{ "PLAY", TRUE },
{ "PAUSE", TRUE },
{ "TEARDOWN", TRUE },
{ "GET_PARAMETER", FALSE },
{ "SET_PARAMETER", TRUE },
{ "RECORD", TRUE },
{ "", FALSE }, /* RECEIVE: treat interleaved RTP as body */
};
/* this is verified already in setopt, this is just added precaution */
DEBUGASSERT((rtspreq > RTSPREQ_NONE) && (rtspreq < RTSPREQ_LAST));
if((rtspreq <= RTSPREQ_NONE) || (rtspreq >= RTSPREQ_LAST))
return CURLE_BAD_FUNCTION_ARGUMENT;
*p = req[rtspreq - 1].method;
data->req.no_body = req[rtspreq - 1].no_body;
return CURLE_OK;
}
/* Allocate and store a header string. */
static CURLcode rtsp_header_alloc(const char *header_name,
const char *value,
char **target)
{
if(!value)
return CURLE_OK;
curlx_free(*target);
*target = curl_maprintf("%s: %s\r\n", header_name, value);
if(!*target)
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
struct rtsp_blocks {
const char *request;
const char *session_id;
const char *accept;
const char *accept_encoding;
const char *range;
const char *referrer;
const char *stream_uri;
const char *transport;
const char *uagent;
const char *hd_proxy_auth;
const char *hd_auth;
};
static CURLcode rtsp_setup_request(struct Curl_easy *data,
struct rtsp_blocks *b,
const unsigned char rtspreq)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
const Curl_RtspReq rtspreq = data->set.rtspreq;
struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
struct dynbuf req_buffer;
const unsigned char httpversion = 11; /* RTSP is close to HTTP/1.1, sort
of... */
const char *p_request = NULL;
const char *p_session_id = NULL;
const char *p_accept = NULL;
const char *p_accept_encoding = NULL;
const char *p_range = NULL;
const char *p_referrer = NULL;
const char *p_stream_uri = NULL;
const char *p_transport = NULL;
const char *p_uagent = NULL;
const char *p_hd_proxy_auth = NULL;
const char *p_hd_auth = NULL;
struct connectdata *conn = data->conn;
*done = TRUE;
if(!rtsp)
return CURLE_FAILED_INIT;
/* Initialize a dynamic send buffer */
curlx_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER);
rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq;
rtsp->CSeq_recv = 0;
/* Setup the 'p_request' pointer to the proper p_request string
* Since all RTSP requests are included here, there is no need to
* support custom requests like HTTP.
**/
data->req.no_body = TRUE; /* most requests do not contain a body */
switch(rtspreq) {
default:
failf(data, "Got invalid RTSP request");
return CURLE_BAD_FUNCTION_ARGUMENT;
case RTSPREQ_OPTIONS:
p_request = "OPTIONS";
break;
case RTSPREQ_DESCRIBE:
p_request = "DESCRIBE";
data->req.no_body = FALSE;
break;
case RTSPREQ_ANNOUNCE:
p_request = "ANNOUNCE";
break;
case RTSPREQ_SETUP:
p_request = "SETUP";
break;
case RTSPREQ_PLAY:
p_request = "PLAY";
break;
case RTSPREQ_PAUSE:
p_request = "PAUSE";
break;
case RTSPREQ_TEARDOWN:
p_request = "TEARDOWN";
break;
case RTSPREQ_GET_PARAMETER:
/* GET_PARAMETER's no_body status is determined later */
p_request = "GET_PARAMETER";
data->req.no_body = FALSE;
break;
case RTSPREQ_SET_PARAMETER:
p_request = "SET_PARAMETER";
break;
case RTSPREQ_RECORD:
p_request = "RECORD";
break;
case RTSPREQ_RECEIVE:
p_request = "";
/* Treat interleaved RTP as body */
data->req.no_body = FALSE;
break;
case RTSPREQ_LAST:
failf(data, "Got invalid RTSP request: RTSPREQ_LAST");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(rtspreq == RTSPREQ_RECEIVE) {
Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
goto out;
}
p_session_id = data->set.str[STRING_RTSP_SESSION_ID];
if(!p_session_id &&
(rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS |
RTSPREQ_DESCRIBE |
RTSPREQ_SETUP))) {
failf(data, "Refusing to issue an RTSP request [%s] without a session ID.",
p_request);
result = CURLE_BAD_FUNCTION_ARGUMENT;
goto out;
}
b->session_id = data->set.str[STRING_RTSP_SESSION_ID];
/* Stream URI. Default to server '*' if not specified */
if(data->set.str[STRING_RTSP_STREAM_URI]) {
p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI];
}
else {
p_stream_uri = "*";
}
if(data->set.str[STRING_RTSP_STREAM_URI])
b->stream_uri = data->set.str[STRING_RTSP_STREAM_URI];
else
b->stream_uri = "*";
/* Transport Header for SETUP requests */
p_transport = Curl_checkheaders(data, STRCONST("Transport"));
if(rtspreq == RTSPREQ_SETUP && !p_transport) {
b->transport = Curl_checkheaders(data, STRCONST("Transport"));
if(rtspreq == RTSPREQ_SETUP && !b->transport) {
/* New Transport: setting? */
if(data->set.str[STRING_RTSP_TRANSPORT]) {
curlx_free(data->state.aptr.rtsp_transport);
data->state.aptr.rtsp_transport =
curl_maprintf("Transport: %s\r\n",
data->set.str[STRING_RTSP_TRANSPORT]);
if(!data->state.aptr.rtsp_transport)
return CURLE_OUT_OF_MEMORY;
result = rtsp_header_alloc("Transport",
data->set.str[STRING_RTSP_TRANSPORT],
&data->state.aptr.rtsp_transport);
if(result)
return result;
}
else {
failf(data,
"Refusing to issue an RTSP SETUP without a Transport: header.");
result = CURLE_BAD_FUNCTION_ARGUMENT;
goto out;
return CURLE_BAD_FUNCTION_ARGUMENT;
}
p_transport = data->state.aptr.rtsp_transport;
b->transport = data->state.aptr.rtsp_transport;
}
/* Accept Headers for DESCRIBE requests */
if(rtspreq == RTSPREQ_DESCRIBE) {
/* Accept Header */
p_accept = Curl_checkheaders(data, STRCONST("Accept")) ?
b->accept = Curl_checkheaders(data, STRCONST("Accept")) ?
NULL : "Accept: application/sdp\r\n";
/* Accept-Encoding header */
if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
data->set.str[STRING_ENCODING]) {
curlx_free(data->state.aptr.accept_encoding);
data->state.aptr.accept_encoding =
curl_maprintf("Accept-Encoding: %s\r\n",
data->set.str[STRING_ENCODING]);
if(!data->state.aptr.accept_encoding) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
p_accept_encoding = data->state.aptr.accept_encoding;
result = rtsp_header_alloc("Accept-Encoding",
data->set.str[STRING_ENCODING],
&data->state.aptr.accept_encoding);
if(result)
return result;
b->accept_encoding = data->state.aptr.accept_encoding;
}
}
@ -431,19 +392,19 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
}
else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
data->set.str[STRING_USERAGENT]) {
p_uagent = data->state.aptr.uagent;
b->uagent = data->state.aptr.uagent;
}
/* setup the authentication headers */
result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET,
p_stream_uri, NULL, FALSE);
result = Curl_http_output_auth(data, conn, b->request, HTTPREQ_GET,
b->stream_uri, NULL, FALSE);
if(result)
goto out;
return result;
#ifndef CURL_DISABLE_PROXY
p_hd_proxy_auth = data->req.hd_proxy_auth;
b->hd_proxy_auth = data->req.hd_proxy_auth;
#endif
p_hd_auth = data->req.hd_auth;
b->hd_auth = data->req.hd_auth;
/* Referrer */
curlx_safefree(data->state.aptr.ref);
@ -452,7 +413,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
data->state.aptr.ref =
curl_maprintf("Referer: %s\r\n", Curl_bufref_ptr(&data->state.referer));
p_referrer = data->state.aptr.ref;
b->referrer = data->state.aptr.ref;
/*
* Range Header
@ -461,17 +422,56 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
* Go ahead and use the Range stuff supplied for HTTP
*/
if(data->state.use_range &&
(rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) {
((rtspreq == RTSPREQ_PLAY) ||
(rtspreq == RTSPREQ_PAUSE) ||
(rtspreq == RTSPREQ_RECORD))) {
/* Check to see if there is a range set in the custom headers */
if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) {
curlx_free(data->state.aptr.rangeline);
data->state.aptr.rangeline = curl_maprintf("Range: %s\r\n",
data->state.range);
p_range = data->state.aptr.rangeline;
result = rtsp_header_alloc("Range",
data->state.range,
&data->state.aptr.rangeline);
if(!result)
b->range = data->state.aptr.rangeline;
}
}
return result;
}
#define HTTPVERSION 11 /* RTSP is close to HTTP/1.1, sort of... */
static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
{
CURLcode result = CURLE_OK;
const unsigned char rtspreq = data->set.rtspreq;
struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
struct dynbuf req_buffer;
struct rtsp_blocks block;
memset(&block, 0, sizeof(block));
*done = TRUE;
if(!rtsp)
return CURLE_FAILED_INIT;
/* Initialize a dynamic send buffer */
curlx_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER);
rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq;
rtsp->CSeq_recv = 0;
/* Setup the 'p_request' pointer to the proper method. */
result = pick_method(data, rtspreq, &block.request);
if(result)
goto out;
if(rtspreq == RTSPREQ_RECEIVE) {
Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
goto out;
}
result = rtsp_setup_request(data, &block, rtspreq);
if(result)
goto out;
/*
* Sanity check the custom headers
*/
@ -490,7 +490,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
curlx_dyn_addf(&req_buffer,
"%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */
"CSeq: %u\r\n", /* CSeq */
p_request, p_stream_uri, rtsp->CSeq_sent);
block.request, block.stream_uri, rtsp->CSeq_sent);
if(result)
goto out;
@ -498,8 +498,8 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
* Rather than do a normal alloc line, keep the session_id unformatted
* to make comparison easier
*/
if(p_session_id) {
result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id);
if(block.session_id) {
result = curlx_dyn_addf(&req_buffer, "Session: %s\r\n", block.session_id);
if(result)
goto out;
}
@ -517,14 +517,14 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
"%s" /* hd_proxy_auth */
"%s" /* hd_auth */
,
p_transport ? p_transport : "",
p_accept ? p_accept : "",
p_accept_encoding ? p_accept_encoding : "",
p_range ? p_range : "",
p_referrer ? p_referrer : "",
p_uagent ? p_uagent : "",
p_hd_proxy_auth ? p_hd_proxy_auth : "",
p_hd_auth ? p_hd_auth : "");
block.transport ? block.transport : "",
block.accept ? block.accept : "",
block.accept_encoding ? block.accept_encoding : "",
block.range ? block.range : "",
block.referrer ? block.referrer : "",
block.uagent ? block.uagent : "",
block.hd_proxy_auth ? block.hd_proxy_auth : "",
block.hd_auth ? block.hd_auth : "");
if(result)
goto out;
@ -535,7 +535,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
goto out;
}
result = Curl_add_custom_headers(data, FALSE, httpversion, &req_buffer);
result = Curl_add_custom_headers(data, FALSE, HTTPVERSION, &req_buffer);
if(result)
goto out;
@ -551,7 +551,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1);
/* issue the request */
result = Curl_req_send(data, &req_buffer, httpversion);
result = Curl_req_send(data, &req_buffer, HTTPVERSION);
if(result) {
failf(data, "Failed sending RTSP request");
goto out;

View file

@ -30,4 +30,18 @@ extern const struct Curl_protocol Curl_protocol_rtsp;
#define Curl_rtsp_parseheader(x, y) CURLE_NOT_BUILT_IN
#endif
#define RTSPREQ_NONE CURL_RTSPREQ_NONE
#define RTSPREQ_OPTIONS CURL_RTSPREQ_OPTIONS
#define RTSPREQ_DESCRIBE CURL_RTSPREQ_DESCRIBE
#define RTSPREQ_ANNOUNCE CURL_RTSPREQ_ANNOUNCE
#define RTSPREQ_SETUP CURL_RTSPREQ_SETUP
#define RTSPREQ_PLAY CURL_RTSPREQ_PLAY
#define RTSPREQ_PAUSE CURL_RTSPREQ_PAUSE
#define RTSPREQ_TEARDOWN CURL_RTSPREQ_TEARDOWN
#define RTSPREQ_GET_PARAMETER CURL_RTSPREQ_GET_PARAMETER
#define RTSPREQ_SET_PARAMETER CURL_RTSPREQ_SET_PARAMETER
#define RTSPREQ_RECORD CURL_RTSPREQ_RECORD
#define RTSPREQ_RECEIVE CURL_RTSPREQ_RECEIVE
#define RTSPREQ_LAST CURL_RTSPREQ_LAST
#endif /* HEADER_CURL_RTSP_H */

View file

@ -355,46 +355,10 @@ static CURLcode setopt_RTSP_REQUEST(struct Curl_easy *data, long arg)
* Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) Would this be
* better if the RTSPREQ_* were moved into here?
*/
Curl_RtspReq rtspreq = RTSPREQ_NONE;
switch(arg) {
case CURL_RTSPREQ_OPTIONS:
rtspreq = RTSPREQ_OPTIONS;
break;
case CURL_RTSPREQ_DESCRIBE:
rtspreq = RTSPREQ_DESCRIBE;
break;
case CURL_RTSPREQ_ANNOUNCE:
rtspreq = RTSPREQ_ANNOUNCE;
break;
case CURL_RTSPREQ_SETUP:
rtspreq = RTSPREQ_SETUP;
break;
case CURL_RTSPREQ_PLAY:
rtspreq = RTSPREQ_PLAY;
break;
case CURL_RTSPREQ_PAUSE:
rtspreq = RTSPREQ_PAUSE;
break;
case CURL_RTSPREQ_TEARDOWN:
rtspreq = RTSPREQ_TEARDOWN;
break;
case CURL_RTSPREQ_GET_PARAMETER:
rtspreq = RTSPREQ_GET_PARAMETER;
break;
case CURL_RTSPREQ_SET_PARAMETER:
rtspreq = RTSPREQ_SET_PARAMETER;
break;
case CURL_RTSPREQ_RECORD:
rtspreq = RTSPREQ_RECORD;
break;
case CURL_RTSPREQ_RECEIVE:
rtspreq = RTSPREQ_RECEIVE;
break;
default:
if((arg <= CURL_RTSPREQ_NONE) || (arg >= CURL_RTSPREQ_LAST))
return CURLE_BAD_FUNCTION_ARGUMENT;
}
data->set.rtspreq = rtspreq;
data->set.rtspreq = (unsigned char)arg;
return CURLE_OK;
}
#endif /* !CURL_DISABLE_RTSP */

View file

@ -71,6 +71,7 @@
#include "setopt.h"
#include "headers.h"
#include "bufref.h"
#include "rtsp.h"
#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \
!defined(CURL_DISABLE_IMAP)

View file

@ -477,22 +477,6 @@ struct Progress {
BIT(is_t_startransfer_set);
};
typedef enum {
RTSPREQ_NONE, /* first in list */
RTSPREQ_OPTIONS,
RTSPREQ_DESCRIBE,
RTSPREQ_ANNOUNCE,
RTSPREQ_SETUP,
RTSPREQ_PLAY,
RTSPREQ_PAUSE,
RTSPREQ_TEARDOWN,
RTSPREQ_GET_PARAMETER,
RTSPREQ_SET_PARAMETER,
RTSPREQ_RECORD,
RTSPREQ_RECEIVE,
RTSPREQ_LAST /* last in list */
} Curl_RtspReq;
struct auth {
uint32_t want; /* Bitmask set to the authentication methods wanted by app
(with CURLOPT_HTTPAUTH or CURLOPT_PROXYAUTH). */
@ -1002,7 +986,7 @@ struct UserDefined {
#ifndef CURL_DISABLE_RTSP
void *rtp_out; /* write RTP to this if non-NULL */
/* Common RTSP header options */
Curl_RtspReq rtspreq; /* RTSP request type */
unsigned char rtspreq; /* RTSP request type */
#endif
#ifndef CURL_DISABLE_FTP
curl_chunk_bgn_callback chunk_bgn; /* called before part of transfer