From fdd5a656328e5099f94433c30a176a77b2308d46 Mon Sep 17 00:00:00 2001 From: Peter Kjellerstedt Date: Wed, 10 Jun 2009 11:43:31 +0200 Subject: [PATCH] rtsp: Improved parsing of messages. Do not abort message parsing as soon as there is an error. Instead parse as much as possible to allow a server to return as meaningful an error as possible. --- gst-libs/gst/rtsp/gstrtspconnection.c | 313 ++++++++++++++++++++-------------- 1 file changed, 181 insertions(+), 132 deletions(-) diff --git a/gst-libs/gst/rtsp/gstrtspconnection.c b/gst-libs/gst/rtsp/gstrtspconnection.c index b9cf863..762d3ec 100644 --- a/gst-libs/gst/rtsp/gstrtspconnection.c +++ b/gst-libs/gst/rtsp/gstrtspconnection.c @@ -222,6 +222,7 @@ enum typedef struct { gint state; + GstRTSPResult status; guint8 buffer[4096]; guint offset; @@ -1511,9 +1512,53 @@ parse_key (gchar * dest, gint size, gchar ** src) } static GstRTSPResult +parse_protocol_version (gchar * protocol, GstRTSPMsgType * type, + GstRTSPVersion * version) +{ + GstRTSPResult res = GST_RTSP_OK; + gchar *ver; + + if (G_LIKELY ((ver = strchr (protocol, '/')) != NULL)) { + guint major; + guint minor; + gchar dummychar; + + *ver++ = '\0'; + + /* the version number must be formatted as X.Y with nothing following */ + if (sscanf (ver, "%u.%u%c", &major, &minor, &dummychar) != 2) + res = GST_RTSP_EPARSE; + + if (g_ascii_strcasecmp (protocol, "RTSP") == 0) { + if (major != 1 || minor != 0) { + *version = GST_RTSP_VERSION_INVALID; + res = GST_RTSP_ERROR; + } + } else if (g_ascii_strcasecmp (protocol, "HTTP") == 0) { + if (*type == GST_RTSP_MESSAGE_REQUEST) + *type = GST_RTSP_MESSAGE_HTTP_REQUEST; + else if (*type == GST_RTSP_MESSAGE_RESPONSE) + *type = GST_RTSP_MESSAGE_HTTP_RESPONSE; + + if (major == 1 && minor == 1) { + *version = GST_RTSP_VERSION_1_1; + } else if (major != 1 || minor != 0) { + *version = GST_RTSP_VERSION_INVALID; + res = GST_RTSP_ERROR; + } + } else + res = GST_RTSP_EPARSE; + } else + res = GST_RTSP_EPARSE; + + return res; +} + +static GstRTSPResult parse_response_status (guint8 * buffer, GstRTSPMessage * msg) { - GstRTSPResult res; + GstRTSPResult res = GST_RTSP_OK; + GstRTSPResult res2; gchar versionstr[20]; gchar codestr[4]; gint code; @@ -1522,102 +1567,80 @@ parse_response_status (guint8 * buffer, GstRTSPMessage * msg) bptr = (gchar *) buffer; parse_string (versionstr, sizeof (versionstr), &bptr); + parse_string (codestr, sizeof (codestr), &bptr); code = atoi (codestr); + if (G_UNLIKELY (*codestr == '\0' || code < 0 || code >= 600)) + res = GST_RTSP_EPARSE; while (g_ascii_isspace (*bptr)) bptr++; - if (strcmp (versionstr, "RTSP/1.0") == 0) - GST_RTSP_CHECK (gst_rtsp_message_init_response (msg, code, bptr, NULL), - parse_error); - else if (strncmp (versionstr, "RTSP/", 5) == 0) { - GST_RTSP_CHECK (gst_rtsp_message_init_response (msg, code, bptr, NULL), - parse_error); - msg->type_data.response.version = GST_RTSP_VERSION_INVALID; - } else - goto parse_error; + if (G_UNLIKELY (gst_rtsp_message_init_response (msg, code, bptr, + NULL) != GST_RTSP_OK)) + res = GST_RTSP_EPARSE; - return GST_RTSP_OK; + res2 = parse_protocol_version (versionstr, &msg->type, + &msg->type_data.response.version); + if (G_LIKELY (res == GST_RTSP_OK)) + res = res2; -parse_error: - { - return GST_RTSP_EPARSE; - } + return res; } static GstRTSPResult -parse_request_line (GstRTSPConnection * conn, guint8 * buffer, - GstRTSPMessage * msg) +parse_request_line (guint8 * buffer, GstRTSPMessage * msg) { GstRTSPResult res = GST_RTSP_OK; + GstRTSPResult res2; gchar versionstr[20]; gchar methodstr[20]; gchar urlstr[4096]; gchar *bptr; GstRTSPMethod method; - GstRTSPTunnelState tstate = TUNNEL_STATE_NONE; bptr = (gchar *) buffer; parse_string (methodstr, sizeof (methodstr), &bptr); method = gst_rtsp_find_method (methodstr); - if (method == GST_RTSP_INVALID) { - /* a tunnel request is allowed when we don't have one yet */ - if (conn->tstate != TUNNEL_STATE_NONE) - goto invalid_method; - /* we need GET or POST for a valid tunnel request */ - if (!strcmp (methodstr, "GET")) - tstate = TUNNEL_STATE_GET; - else if (!strcmp (methodstr, "POST")) - tstate = TUNNEL_STATE_POST; - else - goto invalid_method; - } parse_string (urlstr, sizeof (urlstr), &bptr); if (G_UNLIKELY (*urlstr == '\0')) - goto invalid_url; + res = GST_RTSP_EPARSE; parse_string (versionstr, sizeof (versionstr), &bptr); if (G_UNLIKELY (*bptr != '\0')) - goto invalid_version; - - if (strcmp (versionstr, "RTSP/1.0") == 0) { - res = gst_rtsp_message_init_request (msg, method, urlstr); - } else if (strncmp (versionstr, "RTSP/", 5) == 0) { - res = gst_rtsp_message_init_request (msg, method, urlstr); - msg->type_data.request.version = GST_RTSP_VERSION_INVALID; - } else if (strcmp (versionstr, "HTTP/1.0") == 0) { - /* tunnel request, we need a tunnel method */ - if (tstate == TUNNEL_STATE_NONE) { - res = GST_RTSP_EPARSE; - } else { - conn->tstate = tstate; - } - } else { res = GST_RTSP_EPARSE; - } - return res; + if (G_UNLIKELY (gst_rtsp_message_init_request (msg, method, + urlstr) != GST_RTSP_OK)) + res = GST_RTSP_EPARSE; - /* ERRORS */ -invalid_method: - { - GST_ERROR ("invalid method %s", methodstr); - return GST_RTSP_EPARSE; - } -invalid_url: - { - GST_ERROR ("invalid url %s", urlstr); - return GST_RTSP_EPARSE; - } -invalid_version: - { - GST_ERROR ("invalid version"); - return GST_RTSP_EPARSE; + res2 = parse_protocol_version (versionstr, &msg->type, + &msg->type_data.request.version); + if (G_LIKELY (res == GST_RTSP_OK)) + res = res2; + + if (G_LIKELY (msg->type == GST_RTSP_MESSAGE_REQUEST)) { + /* GET and POST are not allowed as RTSP methods */ + if (msg->type_data.request.method == GST_RTSP_GET || + msg->type_data.request.method == GST_RTSP_POST) { + msg->type_data.request.method = GST_RTSP_INVALID; + if (res == GST_RTSP_OK) + res = GST_RTSP_ERROR; + } + } else if (msg->type == GST_RTSP_MESSAGE_HTTP_REQUEST) { + /* only GET and POST are allowed as HTTP methods */ + if (msg->type_data.request.method != GST_RTSP_GET && + msg->type_data.request.method != GST_RTSP_POST) { + msg->type_data.request.method = GST_RTSP_INVALID; + if (res == GST_RTSP_OK) + res = GST_RTSP_ERROR; + } } + + return res; } static GstRTSPResult @@ -1649,7 +1672,7 @@ no_column: /* parsing lines means reading a Key: Value pair */ static GstRTSPResult -parse_line (GstRTSPConnection * conn, guint8 * buffer, GstRTSPMessage * msg) +parse_line (guint8 * buffer, GstRTSPMessage * msg) { GstRTSPResult res; gchar key[32]; @@ -1660,18 +1683,9 @@ parse_line (GstRTSPConnection * conn, guint8 * buffer, GstRTSPMessage * msg) if (G_UNLIKELY (res != GST_RTSP_OK)) goto parse_error; - if (conn->tstate == TUNNEL_STATE_GET || conn->tstate == TUNNEL_STATE_POST) { - /* save the tunnel session in the connection */ - if (!strcmp (key, "x-sessioncookie")) { - strncpy (conn->tunnelid, value, TUNNELID_LEN); - conn->tunnelid[TUNNELID_LEN - 1] = '\0'; - conn->tunneled = TRUE; - } - } else { - field = gst_rtsp_find_header_field (key); - if (field != GST_RTSP_HDR_INVALID) - gst_rtsp_message_add_header (msg, field, value); - } + field = gst_rtsp_find_header_field (key); + if (field != GST_RTSP_HDR_INVALID) + gst_rtsp_message_add_header (msg, field, value); return GST_RTSP_OK; @@ -1781,17 +1795,17 @@ build_next (GstRTSPBuilder * builder, GstRTSPMessage * message, /* we have a line */ if (builder->line == 0) { /* first line, check for response status */ - if (memcmp (builder->buffer, "RTSP", 4) == 0) { - res = parse_response_status (builder->buffer, message); + if (memcmp (builder->buffer, "RTSP", 4) == 0 || + memcmp (builder->buffer, "HTTP", 4) == 0) { + builder->status = parse_response_status (builder->buffer, message); } else { - res = parse_request_line (conn, builder->buffer, message); + builder->status = parse_request_line (builder->buffer, message); } - /* the first line must parse without errors */ - if (res != GST_RTSP_OK) - goto done; } else { - /* else just parse the line, ignore errors */ - parse_line (conn, builder->buffer, message); + /* else just parse the line */ + res = parse_line (builder->buffer, message); + if (res != GST_RTSP_OK) + builder->status = res; } builder->line++; builder->offset = 0; @@ -1799,22 +1813,26 @@ build_next (GstRTSPBuilder * builder, GstRTSPMessage * message, } case STATE_END: { + gchar *session_cookie; gchar *session_id; - if (conn->tstate == TUNNEL_STATE_GET) { - res = GST_RTSP_ETGET; - goto done; - } else if (conn->tstate == TUNNEL_STATE_POST) { - res = GST_RTSP_ETPOST; - goto done; - } - if (message->type == GST_RTSP_MESSAGE_DATA) { /* data messages don't have headers */ res = GST_RTSP_OK; goto done; } + /* save the tunnel session in the connection */ + if (message->type == GST_RTSP_MESSAGE_HTTP_REQUEST && + !conn->manual_http && + conn->tstate == TUNNEL_STATE_NONE && + gst_rtsp_message_get_header (message, GST_RTSP_HDR_X_SESSIONCOOKIE, + &session_cookie, 0) == GST_RTSP_OK) { + strncpy (conn->tunnelid, session_cookie, TUNNELID_LEN); + conn->tunnelid[TUNNELID_LEN - 1] = '\0'; + conn->tunneled = TRUE; + } + /* save session id in the connection for further use */ if (message->type == GST_RTSP_MESSAGE_RESPONSE && gst_rtsp_message_get_header (message, GST_RTSP_HDR_SESSION, @@ -1846,7 +1864,7 @@ build_next (GstRTSPBuilder * builder, GstRTSPMessage * message, strncpy (conn->session_id, session_id, maxlen); conn->session_id[maxlen] = '\0'; } - res = GST_RTSP_OK; + res = builder->status; goto done; } default: @@ -2021,20 +2039,36 @@ gst_rtsp_connection_receive (GstRTSPConnection * conn, GstRTSPMessage * message, res = build_next (&builder, message, conn); if (G_UNLIKELY (res == GST_RTSP_EEOF)) goto eof; - if (G_LIKELY (res == GST_RTSP_OK)) - break; - if (res == GST_RTSP_ETGET) { - GString *str; - - /* tunnel GET request, we can reply now */ - str = gen_tunnel_reply (conn, GST_RTSP_STS_OK); - res = - gst_rtsp_connection_write (conn, (guint8 *) str->str, str->len, - timeout); - g_string_free (str, TRUE); - } else if (res == GST_RTSP_ETPOST) { - /* tunnel POST request, return the value, the caller now has to link the - * two connections. */ + else if (G_LIKELY (res == GST_RTSP_OK)) { + if (message->type == GST_RTSP_MESSAGE_HTTP_REQUEST) { + if (conn->tstate == TUNNEL_STATE_NONE && + message->type_data.request.method == GST_RTSP_GET) { + GString *str; + + conn->tstate = TUNNEL_STATE_GET; + + /* tunnel GET request, we can reply now */ + str = gen_tunnel_reply (conn, GST_RTSP_STS_OK); + res = + gst_rtsp_connection_write (conn, (guint8 *) str->str, str->len, + timeout); + g_string_free (str, TRUE); + res = GST_RTSP_ETGET; + goto cleanup; + } else if (conn->tstate == TUNNEL_STATE_NONE && + message->type_data.request.method == GST_RTSP_POST) { + conn->tstate = TUNNEL_STATE_POST; + + /* tunnel POST request, the caller now has to link the two + * connections. */ + res = GST_RTSP_ETPOST; + goto cleanup; + } else { + res = GST_RTSP_EPARSE; + goto cleanup; + } + } + break; } else if (G_UNLIKELY (res != GST_RTSP_EINTR)) goto read_error; @@ -2836,38 +2870,53 @@ gst_rtsp_source_dispatch (GSource * source, GSourceFunc callback G_GNUC_UNUSED, res = build_next (&watch->builder, &watch->message, watch->conn); if (res == GST_RTSP_EINTR) break; - if (G_UNLIKELY (res == GST_RTSP_EEOF)) + else if (G_UNLIKELY (res == GST_RTSP_EEOF)) goto eof; - if (res == GST_RTSP_ETGET) { - GString *str; - GstRTSPStatusCode code; - guint size; - - if (watch->funcs.tunnel_start) - code = watch->funcs.tunnel_start (watch, watch->user_data); - else - code = GST_RTSP_STS_OK; - - /* queue the response string */ - str = gen_tunnel_reply (watch->conn, code); - size = str->len; - gst_rtsp_watch_queue_data (watch, (guint8 *) g_string_free (str, FALSE), - size); - } else if (res == GST_RTSP_ETPOST) { - /* in the callback the connection should be tunneled with the - * GET connection */ - if (watch->funcs.tunnel_complete) - watch->funcs.tunnel_complete (watch, watch->user_data); - } else if (G_UNLIKELY (res != GST_RTSP_OK)) - goto error; + else if (G_LIKELY (res == GST_RTSP_OK)) { + if (watch->message.type == GST_RTSP_MESSAGE_HTTP_REQUEST) { + if (watch->conn->tstate == TUNNEL_STATE_NONE && + watch->message.type_data.request.method == GST_RTSP_GET) { + GString *str; + GstRTSPStatusCode code; + guint size; + + watch->conn->tstate = TUNNEL_STATE_GET; + + if (watch->funcs.tunnel_start) + code = watch->funcs.tunnel_start (watch, watch->user_data); + else + code = GST_RTSP_STS_OK; + + /* queue the response string */ + str = gen_tunnel_reply (watch->conn, code); + size = str->len; + gst_rtsp_watch_queue_data (watch, (guint8 *) g_string_free (str, + FALSE), size); + goto read_done; + } else if (watch->conn->tstate == TUNNEL_STATE_NONE && + watch->message.type_data.request.method == GST_RTSP_POST) { + watch->conn->tstate = TUNNEL_STATE_POST; + + /* in the callback the connection should be tunneled with the + * GET connection */ + if (watch->funcs.tunnel_complete) + watch->funcs.tunnel_complete (watch, watch->user_data); + goto read_done; + } else { + res = GST_RTSP_ERROR; + } + } + } if (G_LIKELY (res == GST_RTSP_OK)) { if (watch->funcs.message_received) watch->funcs.message_received (watch, &watch->message, watch->user_data); + } else + goto error; - gst_rtsp_message_unset (&watch->message); - } + read_done: + gst_rtsp_message_unset (&watch->message); build_reset (&watch->builder); } while (FALSE); } -- 2.7.4