LCOV - code coverage report
Current view: top level - src/im - instant_messaging.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 46.7 % 92 43
Test Date: 2026-06-13 09:18:46 Functions: 23.8 % 21 5

            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              : #include "instant_messaging.h"
      18              : 
      19              : #include "logger.h"
      20              : #include "connectivity/sip_utils.h"
      21              : #include "string_utils.h"
      22              : 
      23              : #include <pjsip_ua.h>
      24              : #include <pjsip.h>
      25              : 
      26              : namespace jami {
      27              : 
      28              : using sip_utils::CONST_PJ_STR;
      29              : 
      30              : /**
      31              :  * the pair<string, string> we receive is expected to be in the format <MIME type, payload>
      32              :  * the MIME type is in the format "type/subtype"
      33              :  * in the header it will be presented as "Content-Type: type/subtype"
      34              :  * following the RFC spec, this header line can also contain other parameters in the format:
      35              :  *     Content-Type: type/subtype; arg=value; arg=value; …
      36              :  * thus we also accept the key of the map to be in such a format:
      37              :  *     type/subtype; arg=value; arg=value; …
      38              :  */
      39              : static void
      40          247 : createMessageBody(pj_pool_t* pool, const std::pair<std::string, std::string>& payload, pjsip_msg_body** body_p)
      41              : {
      42              :     /* parse the key:
      43              :      * 1. split by ';'
      44              :      * 2. parse the first result by spliting by '/' into a type and subtype
      45              :      * 3. parse any following strings into arg=value by splitting by '='
      46              :      */
      47          247 :     std::string_view mimeType, parameters;
      48          247 :     auto sep = payload.first.find(';');
      49          246 :     if (std::string::npos == sep) {
      50          246 :         mimeType = payload.first;
      51              :     } else {
      52            0 :         mimeType = std::string_view(payload.first).substr(0, sep);
      53            0 :         parameters = std::string_view(payload.first).substr(sep + 1);
      54              :     }
      55              : 
      56              :     // split MIME type to type and subtype
      57          247 :     sep = mimeType.find('/');
      58          247 :     if (std::string::npos == sep) {
      59            0 :         JAMI_WARNING("Bad MIME type: '{}'", mimeType);
      60            0 :         throw im::InstantMessageException("Invalid MIME type");
      61              :     }
      62              : 
      63          247 :     auto type = sip_utils::CONST_PJ_STR(mimeType.substr(0, sep));
      64          246 :     auto subtype = sip_utils::CONST_PJ_STR(mimeType.substr(sep + 1));
      65          246 :     auto message = sip_utils::CONST_PJ_STR(payload.second);
      66              : 
      67              :     // create part
      68          245 :     *body_p = pjsip_msg_body_create(pool, &type, &subtype, &message);
      69              : 
      70          246 :     if (not parameters.size())
      71          247 :         return;
      72              : 
      73              :     // now attempt to add parameters one by one
      74              :     do {
      75            0 :         sep = parameters.find(';');
      76            0 :         auto paramPair = parameters.substr(0, sep);
      77            0 :         if (paramPair.empty())
      78            0 :             break;
      79              : 
      80              :         // split paramPair into arg and value by '='
      81            0 :         auto paramSplit = paramPair.find('=');
      82            0 :         if (std::string::npos == paramSplit) {
      83            0 :             JAMI_WARNING("Bad parameter: '{}'", paramPair);
      84            0 :             throw im::InstantMessageException("Invalid parameter");
      85              :         }
      86              : 
      87            0 :         auto arg = sip_utils::CONST_PJ_STR(paramPair.substr(0, paramSplit));
      88            0 :         auto value = sip_utils::CONST_PJ_STR(paramPair.substr(paramSplit + 1));
      89            0 :         pj_strtrim(&arg);
      90            0 :         pj_strtrim(&value);
      91              :         pj_str_t arg_pj, value_pj;
      92            0 :         pjsip_param* param = PJ_POOL_ALLOC_T(pool, pjsip_param);
      93            0 :         param->name = *pj_strdup(pool, &arg_pj, &arg);
      94            0 :         param->value = *pj_strdup(pool, &value_pj, &value);
      95            0 :         pj_list_push_back(&(*body_p)->content_type.param, param);
      96              : 
      97              :         // next parameter?
      98            0 :         if (std::string::npos != sep)
      99            0 :             parameters = parameters.substr(sep + 1);
     100            0 :     } while (std::string::npos != sep);
     101              : }
     102              : 
     103              : void
     104          247 : im::fillPJSIPMessageBody(pjsip_tx_data& tdata, const std::map<std::string, std::string>& payloads)
     105              : {
     106              :     // multi-part body?
     107          247 :     if (payloads.size() == 1) {
     108          247 :         createMessageBody(tdata.pool, *payloads.begin(), &tdata.msg->body);
     109          246 :         return;
     110              :     }
     111              : 
     112              :     /* if Ctype is not specified "multipart/mixed" will be used
     113              :      * if the boundary is not specified, a random one will be generateAudioPort
     114              :      * FIXME: generate boundary and check that none of the message parts contain it before
     115              :      *        calling this function; however the probability of this happenings if quite low as
     116              :      *        the randomly generated string is fairly long
     117              :      */
     118            0 :     tdata.msg->body = pjsip_multipart_create(tdata.pool, nullptr, nullptr);
     119              : 
     120            0 :     for (const auto& pair : payloads) {
     121            0 :         auto* part = pjsip_multipart_create_part(tdata.pool);
     122            0 :         if (not part) {
     123            0 :             JAMI_ERROR("pjsip_multipart_create_part failed: not enough memory");
     124            0 :             throw InstantMessageException("Internal SIP error");
     125              :         }
     126              : 
     127            0 :         createMessageBody(tdata.pool, pair, &part->body);
     128              : 
     129            0 :         auto status = pjsip_multipart_add_part(tdata.pool, tdata.msg->body, part);
     130            0 :         if (status != PJ_SUCCESS) {
     131            0 :             JAMI_ERROR("pjsip_multipart_add_part failed: {}", sip_utils::sip_strerror(status));
     132            0 :             throw InstantMessageException("Internal SIP error");
     133              :         }
     134              :     }
     135              : }
     136              : 
     137              : void
     138          247 : im::sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::string>& payloads)
     139              : {
     140          247 :     if (payloads.empty()) {
     141            0 :         JAMI_WARNING("The payloads argument is empty; ignoring message");
     142            0 :         return;
     143              :     }
     144              : 
     145          245 :     constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD, CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
     146              : 
     147              :     {
     148          245 :         auto* dialog = session->dlg;
     149          245 :         sip_utils::PJDialogLock dialog_lock {dialog};
     150              : 
     151          247 :         pjsip_tx_data* tdata = nullptr;
     152          247 :         auto status = pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata);
     153          247 :         if (status != PJ_SUCCESS) {
     154            0 :             JAMI_ERROR("pjsip_dlg_create_request failed: {}", sip_utils::sip_strerror(status));
     155            0 :             throw InstantMessageException("Internal SIP error");
     156              :         }
     157              : 
     158          247 :         fillPJSIPMessageBody(*tdata, payloads);
     159              : 
     160          247 :         status = pjsip_dlg_send_request(dialog, tdata, -1, nullptr);
     161          247 :         if (status != PJ_SUCCESS) {
     162            0 :             JAMI_ERROR("pjsip_dlg_send_request failed: {}", sip_utils::sip_strerror(status));
     163            0 :             throw InstantMessageException("Internal SIP error");
     164              :         }
     165          247 :     }
     166              : }
     167              : 
     168              : /**
     169              :  * Creates std::pair with the Content-Type header contents as the first value and the message
     170              :  * payload as the second value.
     171              :  *
     172              :  * The format of the first value will be:
     173              :  *     type/subtype[; *[; arg=value]]
     174              :  *     eg: "text/plain;id=1234;part=2;of=1001"
     175              :  */
     176              : static std::pair<std::string, std::string>
     177          243 : parseMessageBody(const pjsip_msg_body* body)
     178              : {
     179          486 :     std::string header = sip_utils::as_view(body->content_type.type) + "/"
     180          486 :                          + sip_utils::as_view(body->content_type.subtype);
     181              : 
     182              :     // iterate over parameters
     183          243 :     auto* param = body->content_type.param.next;
     184          243 :     while (param != &body->content_type.param) {
     185            0 :         header += ";" + sip_utils::as_view(param->name) + "=" + sip_utils::as_view(param->value);
     186            0 :         param = param->next;
     187              :     }
     188              : 
     189              :     // get the payload, assume we can interpret it as chars
     190          729 :     return {std::move(header), std::string(static_cast<char*>(body->data), (size_t) body->len)};
     191          243 : }
     192              : 
     193              : /**
     194              :  * Parses given SIP message into a map where the key is the contents of the Content-Type header
     195              :  * (along with any parameters) and the value is the message payload.
     196              :  *
     197              :  * @param msg received SIP message
     198              :  *
     199              :  * @return map of content types and message payloads
     200              :  */
     201              : std::map<std::string, std::string>
     202          243 : im::parseSipMessage(const pjsip_msg* msg)
     203              : {
     204          243 :     std::map<std::string, std::string> ret;
     205              : 
     206          243 :     if (!msg->body) {
     207            0 :         JAMI_WARNING("Message body is empty");
     208            0 :         return ret;
     209              :     }
     210              : 
     211              :     // check if its a multipart message
     212          243 :     constexpr pj_str_t typeMultipart {CONST_PJ_STR("multipart")};
     213              : 
     214          243 :     if (pj_strcmp(&typeMultipart, &msg->body->content_type.type) != 0) {
     215              :         // treat as single content type message
     216          243 :         ret.emplace(parseMessageBody(msg->body));
     217              :     } else {
     218              :         /* multipart type message, we will treat it as multipart/mixed even if the subtype is
     219              :          * something else, eg: related
     220              :          */
     221            0 :         auto* part = pjsip_multipart_get_first_part(msg->body);
     222            0 :         while (part != nullptr) {
     223            0 :             ret.emplace(parseMessageBody(part->body));
     224            0 :             part = pjsip_multipart_get_next_part(msg->body, part);
     225              :         }
     226              :     }
     227              : 
     228          243 :     return ret;
     229            0 : }
     230              : 
     231              : } // namespace jami
        

Generated by: LCOV version 2.0-1