LCOV - code coverage report
Current view: top level - src/connectivity - sip_utils.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 67.3 % 199 134
Test Date: 2026-11-09 10:41:14 Functions: 81.5 % 27 22

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : 
      18              : #include "connectivity/sip_utils.h"
      19              : #include <charconv>
      20              : #include "logger.h"
      21              : #include "connectivity/utf8_utils.h"
      22              : 
      23              : #include <pjsip.h>
      24              : #include <pjsip_ua.h>
      25              : #include <pjlib-util.h>
      26              : #include <pjnath.h>
      27              : #include <pjnath/stun_config.h>
      28              : #include <pj/string.h>
      29              : #include <pjsip/sip_types.h>
      30              : #include <pjsip/sip_uri.h>
      31              : #include <pj/list.h>
      32              : 
      33              : #ifndef _WIN32
      34              : #include <netdb.h>
      35              : #include <sys/socket.h>
      36              : #include <netinet/in.h>
      37              : #include <arpa/inet.h>
      38              : #endif
      39              : 
      40              : #include <vector>
      41              : 
      42              : using namespace std::literals;
      43              : 
      44              : namespace jami {
      45              : namespace sip_utils {
      46              : 
      47              : constexpr pj_str_t USER_AGENT_STR = CONST_PJ_STR("User-Agent");
      48              : constexpr pj_str_t CALL_HANDOVER_STR = CONST_PJ_STR("X-Jami-Call-Handover");
      49              : constexpr pj_str_t CALL_HANDOVER_VALUE = CONST_PJ_STR("1");
      50              : constexpr pj_str_t HANDOVER_GENERATION_STR = CONST_PJ_STR("X-Jami-Handover-Generation");
      51              : constexpr pj_str_t HANDOVER_REQUEST_STR = CONST_PJ_STR("X-Jami-Handover-Request");
      52              : 
      53              : std::string
      54            0 : PjsipErrorCategory::message(int condition) const
      55              : {
      56            0 :     std::string err_msg;
      57            0 :     err_msg.reserve(PJ_ERR_MSG_SIZE);
      58            0 :     err_msg.resize(pj_strerror(condition, &err_msg[0], err_msg.capacity()).slen);
      59            0 :     return err_msg;
      60            0 : }
      61              : 
      62              : std::string
      63            0 : fetchHeaderValue(pjsip_msg* msg, const std::string& field)
      64              : {
      65            0 :     pj_str_t name = pj_str((char*) field.c_str());
      66            0 :     pjsip_generic_string_hdr* hdr = static_cast<pjsip_generic_string_hdr*>(pjsip_msg_find_hdr_by_name(msg, &name, NULL));
      67              : 
      68            0 :     if (!hdr)
      69            0 :         return "";
      70              : 
      71            0 :     std::string value(hdr->hvalue.ptr, hdr->hvalue.slen);
      72              : 
      73            0 :     size_t pos = value.find('\n');
      74              : 
      75            0 :     if (pos != std::string::npos)
      76            0 :         return value.substr(0, pos);
      77              :     else
      78            0 :         return "";
      79            0 : }
      80              : 
      81              : pjsip_route_hdr*
      82            0 : createRouteSet(const std::string& route, pj_pool_t* hdr_pool)
      83              : {
      84            0 :     pjsip_route_hdr* route_set = pjsip_route_hdr_create(hdr_pool);
      85              : 
      86            0 :     std::string host;
      87            0 :     int port = 0;
      88            0 :     size_t found = route.find(':');
      89            0 :     if (found != std::string::npos) {
      90            0 :         host = route.substr(0, found);
      91            0 :         port = atoi(route.substr(found + 1, route.length() - found).c_str());
      92              :     } else
      93            0 :         host = route;
      94              : 
      95            0 :     pjsip_route_hdr* routing = pjsip_route_hdr_create(hdr_pool);
      96            0 :     pjsip_sip_uri* url = pjsip_sip_uri_create(hdr_pool, 0);
      97            0 :     url->lr_param = 1;
      98            0 :     routing->name_addr.uri = (pjsip_uri*) url;
      99            0 :     pj_strdup2(hdr_pool, &url->host, host.c_str());
     100            0 :     url->port = port;
     101              : 
     102            0 :     JAMI_LOG("Adding route {}", host);
     103            0 :     pj_list_push_back(route_set, pjsip_hdr_clone(hdr_pool, routing));
     104              : 
     105            0 :     return route_set;
     106            0 : }
     107              : 
     108              : std::string
     109          656 : quoteDisplayName(std::string_view displayName)
     110              : {
     111          656 :     std::string quoted;
     112          656 :     quoted.reserve(displayName.size() + 2);
     113          656 :     quoted += '"';
     114         3742 :     for (char c : displayName) {
     115         3086 :         if (c == '\0')
     116            0 :             continue;
     117              :         // CR and LF cannot appear in a quoted-string, even escaped
     118         3086 :         if (c == '\r' or c == '\n')
     119            1 :             c = ' ';
     120         3085 :         else if (c == '"' or c == '\\')
     121            5 :             quoted += '\\';
     122         3086 :         quoted += c;
     123              :     }
     124          656 :     quoted += '"';
     125          656 :     return quoted;
     126            0 : }
     127              : 
     128              : std::string
     129          118 : parseDisplayName(const pjsip_name_addr* sip_name_addr)
     130              : {
     131          118 :     if (not sip_name_addr->display.ptr or not sip_name_addr->display.slen)
     132            0 :         return {};
     133              : 
     134              :     // pjsip strips the surrounding quotes but keeps quoted-pairs escaped
     135          118 :     auto raw = as_view(sip_name_addr->display);
     136          118 :     std::string displayName;
     137          118 :     displayName.reserve(raw.size());
     138          712 :     for (size_t i = 0; i < raw.size(); ++i) {
     139          594 :         if (raw[i] == '\\' and i + 1 < raw.size())
     140            5 :             ++i;
     141          594 :         displayName += raw[i];
     142              :     }
     143              : 
     144              :     // Filter out invalid UTF-8 characters to avoid getting kicked from D-Bus
     145          118 :     if (not utf8_validate(displayName))
     146            0 :         return utf8_make_valid(displayName);
     147              : 
     148          118 :     return displayName;
     149          118 : }
     150              : 
     151              : std::string
     152          113 : parseDisplayName(const pjsip_from_hdr* header)
     153              : {
     154              :     // PJSIP return a pjsip_name_addr for To, From and Contact headers
     155          113 :     return parseDisplayName(reinterpret_cast<pjsip_name_addr*>(header->uri));
     156              : }
     157              : 
     158              : std::string
     159            0 : parseDisplayName(const pjsip_contact_hdr* header)
     160              : {
     161              :     // PJSIP return a pjsip_name_addr for To, From and Contact headers
     162            0 :     return parseDisplayName(reinterpret_cast<pjsip_name_addr*>(header->uri));
     163              : }
     164              : 
     165              : std::string_view
     166            1 : stripSipUriPrefix(std::string_view sipUri)
     167              : {
     168              :     // Remove sip: prefix
     169              :     static constexpr auto SIP_PREFIX = "sip:"sv;
     170            1 :     size_t found = sipUri.find(SIP_PREFIX);
     171              : 
     172            1 :     if (found != std::string_view::npos)
     173            1 :         sipUri = sipUri.substr(found + SIP_PREFIX.size());
     174              : 
     175              :     // URI may or may not be between brackets
     176            1 :     found = sipUri.find('<');
     177            1 :     if (found != std::string_view::npos)
     178            0 :         sipUri = sipUri.substr(found + 1);
     179              : 
     180            1 :     found = sipUri.find('@');
     181            1 :     if (found != std::string_view::npos)
     182            1 :         sipUri = sipUri.substr(0, found);
     183              : 
     184            1 :     found = sipUri.find('>');
     185            1 :     if (found != std::string_view::npos)
     186            0 :         sipUri = sipUri.substr(0, found);
     187              : 
     188            1 :     return sipUri;
     189              : }
     190              : 
     191              : std::string_view
     192            0 : getHostFromUri(std::string_view uri)
     193              : {
     194            0 :     auto found = uri.find('@');
     195            0 :     if (found != std::string_view::npos)
     196            0 :         uri = uri.substr(found + 1);
     197              : 
     198            0 :     found = uri.find('>');
     199            0 :     if (found != std::string_view::npos)
     200            0 :         uri = uri.substr(0, found);
     201              : 
     202            0 :     return uri;
     203              : }
     204              : 
     205              : void
     206          358 : addContactHeader(const std::string& contactHdr, pjsip_tx_data* tdata)
     207              : {
     208          358 :     if (contactHdr.empty()) {
     209            0 :         JAMI_WARNING("Contact header is unable to be added (empty string)");
     210            0 :         return;
     211              :     }
     212              : 
     213              :     /*
     214              :      * Duplicate contact header because tdata->msg keep a reference to it and
     215              :      * can be used in a callback after destruction of the contact header in
     216              :      * Jami.  Bind lifetime of the duplicated string to the pool allocator of
     217              :      * tdata.
     218              :      */
     219              :     // The pjsip scanner requires a NUL-terminated buffer
     220              :     pj_str_t pjContact;
     221          358 :     pj_strdup2_with_null(tdata->pool, &pjContact, contactHdr.c_str());
     222              : 
     223          358 :     auto* uri = pjsip_parse_uri(tdata->pool, pjContact.ptr, pjContact.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
     224          358 :     if (!uri) {
     225              :         // A NULL uri would crash pjsip_msg_print(); keep the dialog's default Contact instead
     226            0 :         JAMI_ERROR("Unable to parse contact header, not adding it: {}", contactHdr);
     227            0 :         return;
     228              :     }
     229              : 
     230          358 :     pjsip_contact_hdr* contact = pjsip_contact_hdr_create(tdata->pool);
     231          358 :     contact->uri = uri;
     232              :     // remove old contact header (if present)
     233          358 :     pjsip_msg_find_remove_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
     234          358 :     pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) contact);
     235              : }
     236              : 
     237              : void
     238          513 : addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata)
     239              : {
     240          513 :     if (tdata == nullptr or userAgent.empty())
     241            0 :         return;
     242              : 
     243          513 :     auto pjUserAgent = CONST_PJ_STR(userAgent);
     244              : 
     245              :     // Do nothing if user-agent header is present.
     246          513 :     if (pjsip_msg_find_hdr_by_name(tdata->msg, &USER_AGENT_STR, nullptr) != nullptr) {
     247          107 :         return;
     248              :     }
     249              : 
     250              :     // Add Header
     251          406 :     auto* hdr = reinterpret_cast<pjsip_hdr*>(pjsip_user_agent_hdr_create(tdata->pool, &USER_AGENT_STR, &pjUserAgent));
     252              : 
     253          406 :     if (hdr != nullptr) {
     254          406 :         pjsip_msg_add_hdr(tdata->msg, hdr);
     255              :     }
     256              : }
     257              : 
     258              : void
     259          197 : addCallHandoverHeader(pjsip_tx_data* tdata)
     260              : {
     261          197 :     auto* header = pjsip_generic_string_hdr_create(tdata->pool, &CALL_HANDOVER_STR, &CALL_HANDOVER_VALUE);
     262          197 :     pjsip_msg_add_hdr(tdata->msg, reinterpret_cast<pjsip_hdr*>(header));
     263          197 : }
     264              : 
     265              : bool
     266          527 : supportsCallHandover(const pjsip_rx_data* rdata)
     267              : {
     268          527 :     if (not rdata or not rdata->msg_info.msg)
     269            0 :         return false;
     270              :     auto* header = static_cast<const pjsip_generic_string_hdr*>(
     271          527 :         pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &CALL_HANDOVER_STR, nullptr));
     272          527 :     return header and as_view(header->hvalue) == "1";
     273              : }
     274              : 
     275              : static void
     276           46 : addHandoverNumber(pjsip_tx_data* tdata, const pj_str_t& name, std::uint64_t number)
     277              : {
     278           46 :     auto value = std::to_string(number);
     279           46 :     auto text = CONST_PJ_STR(value);
     280           46 :     auto* header = pjsip_generic_string_hdr_create(tdata->pool, &name, &text);
     281           46 :     pjsip_msg_add_hdr(tdata->msg, reinterpret_cast<pjsip_hdr*>(header));
     282           46 : }
     283              : 
     284              : static std::optional<std::uint64_t>
     285           94 : getHandoverNumber(const pjsip_msg* msg, const pj_str_t& name)
     286              : {
     287           94 :     if (not msg)
     288            0 :         return std::nullopt;
     289           94 :     auto* header = static_cast<const pjsip_generic_string_hdr*>(pjsip_msg_find_hdr_by_name(msg, &name, nullptr));
     290           94 :     if (not header or pjsip_msg_find_hdr_by_name(msg, &name, header->next))
     291            2 :         return std::nullopt;
     292           92 :     auto text = as_view(header->hvalue);
     293           92 :     if (text.empty())
     294            0 :         return std::nullopt;
     295              :     std::uint64_t value;
     296           92 :     auto result = std::from_chars(text.data(), text.data() + text.size(), value);
     297           92 :     if (result.ec != std::errc {} or result.ptr != text.data() + text.size())
     298            0 :         return std::nullopt;
     299           92 :     return value;
     300              : }
     301              : 
     302              : void
     303           40 : addHandoverGeneration(pjsip_tx_data* tdata, std::uint64_t generation)
     304              : {
     305           40 :     addHandoverNumber(tdata, HANDOVER_GENERATION_STR, generation);
     306           40 : }
     307              : 
     308              : void
     309            6 : addHandoverRequest(pjsip_tx_data* tdata, std::uint64_t request)
     310              : {
     311            6 :     addHandoverNumber(tdata, HANDOVER_REQUEST_STR, request);
     312            6 : }
     313              : 
     314              : std::optional<std::uint64_t>
     315           82 : getHandoverGeneration(const pjsip_msg* msg)
     316              : {
     317           82 :     return getHandoverNumber(msg, HANDOVER_GENERATION_STR);
     318              : }
     319              : 
     320              : std::optional<std::uint64_t>
     321           82 : getHandoverGeneration(const pjsip_rx_data* rdata)
     322              : {
     323           82 :     return getHandoverGeneration(rdata ? rdata->msg_info.msg : nullptr);
     324              : }
     325              : 
     326              : std::optional<std::uint64_t>
     327           12 : getHandoverRequest(const pjsip_rx_data* rdata)
     328              : {
     329           12 :     return getHandoverNumber(rdata ? rdata->msg_info.msg : nullptr, HANDOVER_REQUEST_STR);
     330              : }
     331              : 
     332              : bool
     333           34 : hasHandoverGeneration(const pjsip_rx_data* rdata)
     334              : {
     335           34 :     return rdata and rdata->msg_info.msg
     336           68 :            and pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &HANDOVER_GENERATION_STR, nullptr);
     337              : }
     338              : 
     339              : std::string_view
     340          537 : getPeerUserAgent(const pjsip_rx_data* rdata)
     341              : {
     342          537 :     if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
     343            0 :         JAMI_ERROR("Unexpected null pointer!");
     344            0 :         return {};
     345              :     }
     346              : 
     347          537 :     if (auto* uaHdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
     348              :                                                                              &USER_AGENT_STR,
     349              :                                                                              nullptr)) {
     350          435 :         return as_view(uaHdr->hvalue);
     351              :     }
     352          102 :     return {};
     353              : }
     354              : 
     355              : std::vector<std::string>
     356          537 : getPeerAllowMethods(const pjsip_rx_data* rdata)
     357              : {
     358          537 :     if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
     359            0 :         JAMI_ERROR("Unexpected null pointer!");
     360            0 :         return {};
     361              :     }
     362              : 
     363          537 :     std::vector<std::string> methods;
     364              : 
     365              :     pjsip_allow_hdr* allow = static_cast<pjsip_allow_hdr*>(
     366          537 :         pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ALLOW, nullptr));
     367              : 
     368          537 :     if (allow != nullptr) {
     369          332 :         methods.reserve(allow->count);
     370         4315 :         for (unsigned i = 0; i < allow->count; i++) {
     371         3983 :             methods.emplace_back(allow->values[i].ptr, allow->values[i].slen);
     372              :         }
     373              :     }
     374              : 
     375          537 :     return methods;
     376          537 : }
     377              : 
     378              : void
     379          113 : logMessageHeaders(const pjsip_hdr* hdr_list)
     380              : {
     381          113 :     const pjsip_hdr* hdr = hdr_list->next;
     382          113 :     const pjsip_hdr* end = hdr_list;
     383          113 :     std::string msgHdrStr("Message headers:\n");
     384         1685 :     for (; hdr != end; hdr = hdr->next) {
     385              :         char buf[1024];
     386         1572 :         int size = pjsip_hdr_print_on((void*) hdr, buf, sizeof(buf));
     387         1572 :         if (size > 0) {
     388         1572 :             msgHdrStr.append(buf, size);
     389         1572 :             msgHdrStr.push_back('\n');
     390              :         }
     391              :     }
     392              : 
     393          113 :     JAMI_LOG("{}", msgHdrStr);
     394          113 : }
     395              : 
     396              : std::string
     397            1 : sip_strerror(pj_status_t code)
     398              : {
     399              :     char err_msg[PJ_ERR_MSG_SIZE];
     400            1 :     auto ret = pj_strerror(code, err_msg, sizeof err_msg);
     401            3 :     return std::string {ret.ptr, ret.ptr + ret.slen};
     402              : }
     403              : 
     404              : std::string
     405          859 : streamId(const std::string& callId, std::string_view label)
     406              : {
     407          859 :     if (callId.empty())
     408          170 :         return fmt::format("host_{}", label);
     409         1548 :     return fmt::format("{}_{}", callId, label);
     410              : }
     411              : 
     412              : void
     413          432 : sockaddr_to_host_port(pj_pool_t* pool, pjsip_host_port* host_port, const pj_sockaddr* addr)
     414              : {
     415          432 :     host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN + 4);
     416          432 :     pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN + 4, 0);
     417          432 :     host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
     418          432 :     host_port->port = pj_sockaddr_get_port(addr);
     419          432 : }
     420              : 
     421              : } // namespace sip_utils
     422              : } // namespace jami
        

Generated by: LCOV version 2.0-1