/* * RTSP Message Utilities. This file is part of Shairport Sync. * Copyright (c) Mike Brady 2026 * All rights reserved. * * 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. */ #include "rtsp_message_utilities.h" #include "../common.h" #include "../rtsp.h" #include #include #include #ifdef CONFIG_AIRPLAY_2 #include #ifdef HAVE_LIBPLIST_GE_2_3_0 #define plist_from_memory(plist_data, length, plist) \ plist_from_memory((plist_data), (length), (plist), NULL) #endif #endif // every time we want to retain or release a reference count, lock it with this // if a reference count is read as zero, it means the it's being deallocated. static pthread_mutex_t reference_counter_lock = PTHREAD_MUTEX_INITIALIZER; static int msg_indexes = 1; void msg_retain(rtsp_message *msg) { int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4); if (rc) debug(1, "Error %d locking reference counter lock", rc); if (msg > (rtsp_message *)0x00010000) { msg->referenceCount++; debug(4, "msg_free increment reference counter message %d to %d.", msg->index_number, msg->referenceCount); // debug(1,"msg_retain -- item %d reference count %d.", msg->index_number, msg->referenceCount); rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) debug(1, "Error %d unlocking reference counter lock", rc); } else { debug(1, "invalid rtsp_message pointer 0x%" PRIxPTR " passed to retain", (uintptr_t)msg); } } rtsp_message *msg_init(void) { // no thread cancellation points here int rc = debug_mutex_lock(&reference_counter_lock, 500000, 4); if (rc) debug(1, "Error %d locking reference counter lock", rc); rtsp_message *msg = malloc(sizeof(rtsp_message)); if (msg) { memset(msg, 0, sizeof(rtsp_message)); msg->referenceCount = 1; // from now on, any access to this must be protected with the lock msg->index_number = msg_indexes++; debug(4, "msg_init message %d", msg->index_number); } else { die("msg_init -- can not allocate memory for rtsp_message %d.", msg_indexes); } // debug(1,"msg_init -- create item %d.", msg->index_number); rc = pthread_mutex_unlock(&reference_counter_lock); if (rc) debug(1, "Error %d unlocking reference counter lock", rc); return msg; } int msg_add_header(rtsp_message *msg, char *name, char *value) { if (msg->nheaders >= sizeof(msg->name) / sizeof(char *)) { warn("too many headers?!"); return 1; } msg->name[msg->nheaders] = strdup(name); msg->value[msg->nheaders] = strdup(value); msg->nheaders++; return 0; } char *msg_get_header(rtsp_message *msg, char *name) { unsigned int i; for (i = 0; i < msg->nheaders; i++) if (!strcasecmp(msg->name[i], name)) return msg->value[i]; return NULL; } void _debug_print_msg_headers(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, rtsp_message *msg) { unsigned int i; if (conn != NULL) { if (msg->respcode != 0) _debug(filename, linenumber, level, "Connection: %d, Response Code: %d.", conn->connection_number, msg->respcode); for (i = 0; i < msg->nheaders; i++) { _debug(filename, linenumber, level, "Connection: %d, Type: \"%s\", content: \"%s\"", conn->connection_number, msg->name[i], msg->value[i]); } } else { if (msg->respcode != 0) _debug(filename, linenumber, level, " Response Code: %d.", msg->respcode); for (i = 0; i < msg->nheaders; i++) { _debug(filename, linenumber, level, " Type: \"%s\", content: \"%s\"", msg->name[i], msg->value[i]); } } } void msg_free(rtsp_message **msgh) { debug_mutex_lock(&reference_counter_lock, 1000, 0); if (*msgh > (rtsp_message *)0x00010000) { rtsp_message *msg = *msgh; msg->referenceCount--; if (msg->referenceCount) debug(4, "msg_free decrement reference counter message %d to %d", msg->index_number, msg->referenceCount); if (msg->referenceCount == 0) { unsigned int i; for (i = 0; i < msg->nheaders; i++) { free(msg->name[i]); free(msg->value[i]); } if (msg->content) free(msg->content); // debug(1,"msg_free item %d -- free.",msg->index_number); uintptr_t index = (msg->index_number) & 0xFFFF; if (index == 0) index = 0x10000; // ensure it doesn't fold to zero. *msgh = (rtsp_message *)(index); // put a version of the index number of the freed message in here debug(4, "msg_free freed message %d", msg->index_number); free(msg); } else { // debug(1,"msg_free item %d -- decrement reference to // %d.",msg->index_number,msg->referenceCount); } } else if (*msgh != NULL) { debug(1, "msg_free: error attempting to free an allocated but already-freed rtsp_message, number " "%" PRIxPTR ".", (uintptr_t)*msgh); } debug_mutex_unlock(&reference_counter_lock, 0); } int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; if (!msg) { msg = msg_init(); *pmsg = msg; char *sp, *p; sp = NULL; // this is to quieten a compiler warning debug(4, "RTSP/HTTP Message Received: \"%s\".", line); p = strtok_r(line, " ", &sp); if (!p) goto fail; strncpy(msg->method, p, sizeof(msg->method) - 1); p = strtok_r(NULL, " ", &sp); if (!p) goto fail; strncpy(msg->path, p, sizeof(msg->path) - 1); p = strtok_r(NULL, " ", &sp); if (!p) goto fail; if ((strcmp(p, "RTSP/1.0") != 0) && (strcmp(p, "HTTP/1.1") != 0)) { debug(1, "Problem with Message: \"%s\"", p); goto fail; } return -1; } if (strlen(line)) { char *p; p = strstr(line, ": "); if (!p) { warn("bad header: >>%s<<", line); goto fail; } *p = 0; p += 2; msg_add_header(msg, line, p); debug(4, " %s: %s.", line, p); return -1; } else { char *cl = msg_get_header(msg, "Content-Length"); if (cl) return atoi(cl); else return 0; } fail: debug(1, "msg_handle_line fail"); msg_free(pmsg); *pmsg = NULL; return 0; } #ifdef CONFIG_AIRPLAY_2 int rtsp_message_contains_plist(rtsp_message *message) { int reply = 0; // assume there is no plist in the message if ((message->contentlength >= strlen("bplist00")) && (strstr(message->content, "bplist00") == message->content)) reply = 1; return reply; } plist_t plist_from_rtsp_content(rtsp_message *message) { plist_t the_plist = NULL; if (rtsp_message_contains_plist(message)) { plist_from_memory(message->content, message->contentlength, &the_plist); } return the_plist; } char *plist_as_xml_text(plist_t the_plist) { // caller must free the returned character buffer // convert it to xml format uint32_t size; char *plist_out = NULL; plist_to_xml(the_plist, &plist_out, &size); // put it into a NUL-terminated string char *reply = malloc(size + 1); if (reply) { memcpy(reply, plist_out, size); reply[size] = '\0'; } if (plist_out) free(plist_out); return reply; } // caller must free the returned character buffer char *rtsp_plist_content(rtsp_message *message) { char *reply = NULL; // first, check if it has binary plist content if (rtsp_message_contains_plist(message)) reply = plist_as_xml_text(plist_from_rtsp_content(message)); return reply; } #endif void _debug_log_rtsp_message(rtsp_conn_info *conn, const char *filename, const int linenumber, int level, char *prompt, rtsp_message *message) { if (level > debug_level()) return; if ((prompt) && (*prompt != '\0')) // okay to pass NULL or an empty list... _debug(filename, linenumber, level, "%s", prompt); _debug_print_msg_headers(conn, filename, linenumber, level, message); #ifdef CONFIG_AIRPLAY_2 char *plist_content = rtsp_plist_content(message); if (plist_content) { _debug(filename, linenumber, level, " Content length: %u. Content Plist (as XML):\n--\n%s--", message->contentlength, plist_content); free(plist_content); } else #endif { _debug(filename, linenumber, level, " Content length: %u.", message->contentlength); if (message->contentlength > 0) { _debug_print_buffer(filename, linenumber, level, message->content, message->contentlength); } } }