LCOV - code coverage report
Current view: top level - src/im - instant_messaging.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 45 92 48.9 %
Date: 2024-03-28 08:00:27 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
       5             :  *  Author: Emmanuel Lepage <elv1313@gmail.com>
       6             :  *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
       7             :  *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
       8             :  *
       9             :  *  This program is free software; you can redistribute it and/or modify
      10             :  *  it under the terms of the GNU General Public License as published by
      11             :  *  the Free Software Foundation; either version 3 of the License, or
      12             :  *  (at your option) any later version.
      13             :  *
      14             :  *  This program is distributed in the hope that it will be useful,
      15             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :  *  GNU General Public License for more details.
      18             :  *
      19             :  *  You should have received a copy of the GNU General Public License
      20             :  *  along with this program; if not, write to the Free Software
      21             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
      22             :  */
      23             : #include "instant_messaging.h"
      24             : 
      25             : #include "logger.h"
      26             : #include "connectivity/sip_utils.h"
      27             : 
      28             : #include <pjsip_ua.h>
      29             : #include <pjsip.h>
      30             : 
      31             : namespace jami {
      32             : 
      33             : using sip_utils::CONST_PJ_STR;
      34             : 
      35             : /**
      36             :  * the pair<string, string> we receive is expected to be in the format <mime type, payload>
      37             :  * the mime type is in the format "type/subtype"
      38             :  * in the header it will be presented as "Content-Type: type/subtype"
      39             :  * following the RFC spec, this header line can also contain other parameters in the format:
      40             :  *     Content-Type: type/subtype; arg=value; arg=value; ...
      41             :  * thus we also accept the key of the map to be in such a format:
      42             :  *     type/subtype; arg=value; arg=value; ...
      43             :  */
      44             : static void
      45       28368 : createMessageBody(pj_pool_t* pool,
      46             :                   const std::pair<std::string, std::string>& payload,
      47             :                   pjsip_msg_body** body_p)
      48             : {
      49             :     /* parse the key:
      50             :      * 1. split by ';'
      51             :      * 2. parse the first result by spliting by '/' into a type and subtype
      52             :      * 3. parse any following strings into arg=value by splitting by '='
      53             :      */
      54       28368 :     std::string_view mimeType, parameters;
      55       28368 :     auto sep = payload.first.find(';');
      56       28368 :     if (std::string::npos == sep) {
      57       28368 :         mimeType = payload.first;
      58             :     } else {
      59           0 :         mimeType = std::string_view(payload.first).substr(0, sep);
      60           0 :         parameters = std::string_view(payload.first).substr(sep + 1);
      61             :     }
      62             : 
      63             :     // split mime type to type and subtype
      64       28368 :     sep = mimeType.find('/');
      65       28368 :     if (std::string::npos == sep) {
      66           0 :         JAMI_DBG("bad mime type: '%.*s'", (int) mimeType.size(), mimeType.data());
      67           0 :         throw im::InstantMessageException("invalid mime type");
      68             :     }
      69             : 
      70       28368 :     auto type = sip_utils::CONST_PJ_STR(mimeType.substr(0, sep));
      71       28368 :     auto subtype = sip_utils::CONST_PJ_STR(mimeType.substr(sep + 1));
      72       28368 :     auto message = sip_utils::CONST_PJ_STR(payload.second);
      73             : 
      74             :     // create part
      75       28368 :     *body_p = pjsip_msg_body_create(pool, &type, &subtype, &message);
      76             : 
      77       28368 :     if (not parameters.size())
      78       28368 :         return;
      79             : 
      80             :     // now try to add parameters one by one
      81             :     do {
      82           0 :         sep = parameters.find(';');
      83           0 :         auto paramPair = parameters.substr(0, sep);
      84           0 :         if (paramPair.empty())
      85           0 :             break;
      86             : 
      87             :         // split paramPair into arg and value by '='
      88           0 :         auto paramSplit = paramPair.find('=');
      89           0 :         if (std::string::npos == paramSplit) {
      90           0 :             JAMI_DBG("bad parameter: '%.*s'", (int) paramPair.size(), paramPair.data());
      91           0 :             throw im::InstantMessageException("invalid parameter");
      92             :         }
      93             : 
      94           0 :         auto arg = sip_utils::CONST_PJ_STR(paramPair.substr(0, paramSplit));
      95           0 :         auto value = sip_utils::CONST_PJ_STR(paramPair.substr(paramSplit + 1));
      96           0 :         pj_strtrim(&arg);
      97           0 :         pj_strtrim(&value);
      98             :         pj_str_t arg_pj, value_pj;
      99           0 :         pjsip_param* param = PJ_POOL_ALLOC_T(pool, pjsip_param);
     100           0 :         param->name = *pj_strdup(pool, &arg_pj, &arg);
     101           0 :         param->value = *pj_strdup(pool, &value_pj, &value);
     102           0 :         pj_list_push_back(&(*body_p)->content_type.param, param);
     103             : 
     104             :         // next parameter?
     105           0 :         if (std::string::npos != sep)
     106           0 :             parameters = parameters.substr(sep + 1);
     107           0 :     } while (std::string::npos != sep);
     108             : }
     109             : 
     110             : void
     111       28368 : im::fillPJSIPMessageBody(pjsip_tx_data& tdata, const std::map<std::string, std::string>& payloads)
     112             : {
     113             :     // multi-part body?
     114       28368 :     if (payloads.size() == 1) {
     115       28368 :         createMessageBody(tdata.pool, *payloads.begin(), &tdata.msg->body);
     116       28368 :         return;
     117             :     }
     118             : 
     119             :     /* if ctype is not specified "multipart/mixed" will be used
     120             :      * if the boundary is not specified, a random one will be generateAudioPort
     121             :      * FIXME: generate boundary and check that none of the message parts contain it before
     122             :      *        calling this function; however the probability of this happenings if quite low as
     123             :      *        the randomly generated string is fairly long
     124             :      */
     125           0 :     tdata.msg->body = pjsip_multipart_create(tdata.pool, nullptr, nullptr);
     126             : 
     127           0 :     for (const auto& pair : payloads) {
     128           0 :         auto part = pjsip_multipart_create_part(tdata.pool);
     129           0 :         if (not part) {
     130           0 :             JAMI_ERR("pjsip_multipart_create_part failed: not enough memory");
     131           0 :             throw InstantMessageException("Internal SIP error");
     132             :         }
     133             : 
     134           0 :         createMessageBody(tdata.pool, pair, &part->body);
     135             : 
     136           0 :         auto status = pjsip_multipart_add_part(tdata.pool, tdata.msg->body, part);
     137           0 :         if (status != PJ_SUCCESS) {
     138           0 :             JAMI_ERR("pjsip_multipart_add_part failed: %s", sip_utils::sip_strerror(status).c_str());
     139           0 :             throw InstantMessageException("Internal SIP error");
     140             :         }
     141             :     }
     142             : }
     143             : 
     144             : void
     145         192 : im::sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::string>& payloads)
     146             : {
     147         192 :     if (payloads.empty()) {
     148           0 :         JAMI_WARN("the payloads argument is empty; ignoring message");
     149           0 :         return;
     150             :     }
     151             : 
     152         188 :     constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
     153             :                                          CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
     154             : 
     155             :     {
     156         188 :         auto dialog = session->dlg;
     157         188 :         sip_utils::PJDialogLock dialog_lock {dialog};
     158             : 
     159         191 :         pjsip_tx_data* tdata = nullptr;
     160         191 :         auto status = pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata);
     161         192 :         if (status != PJ_SUCCESS) {
     162           0 :             JAMI_ERR("pjsip_dlg_create_request failed: %s", sip_utils::sip_strerror(status).c_str());
     163           0 :             throw InstantMessageException("Internal SIP error");
     164             :         }
     165             : 
     166         192 :         fillPJSIPMessageBody(*tdata, payloads);
     167             : 
     168         192 :         status = pjsip_dlg_send_request(dialog, tdata, -1, nullptr);
     169         192 :         if (status != PJ_SUCCESS) {
     170           2 :             JAMI_ERR("pjsip_dlg_send_request failed: %s", sip_utils::sip_strerror(status).c_str());
     171           2 :             throw InstantMessageException("Internal SIP error");
     172             :         }
     173         192 :     }
     174             : }
     175             : 
     176             : /**
     177             :  * Creates std::pair with the Content-Type header contents as the first value and the message
     178             :  * payload as the second value.
     179             :  *
     180             :  * The format of the first value will be:
     181             :  *     type/subtype[; *[; arg=value]]
     182             :  *     eg: "text/plain;id=1234;part=2;of=1001"
     183             :  */
     184             : static std::pair<std::string, std::string>
     185       28352 : parseMessageBody(const pjsip_msg_body* body)
     186             : {
     187       56701 :     std::string header = sip_utils::as_view(body->content_type.type) + "/"
     188       56701 :                          + sip_utils::as_view(body->content_type.subtype);
     189             : 
     190             :     // iterate over parameters
     191       28351 :     auto param = body->content_type.param.next;
     192       28351 :     while (param != &body->content_type.param) {
     193           0 :         header += ";" + sip_utils::as_view(param->name) + "=" + sip_utils::as_view(param->value);
     194           0 :         param = param->next;
     195             :     }
     196             : 
     197             :     // get the payload, assume we can interpret it as chars
     198       56699 :     return {std::move(header), std::string(static_cast<char*>(body->data), (size_t) body->len)};
     199       28348 : }
     200             : 
     201             : /**
     202             :  * Parses given SIP message into a map where the key is the contents of the Content-Type header
     203             :  * (along with any parameters) and the value is the message payload.
     204             :  *
     205             :  * @param msg received SIP message
     206             :  *
     207             :  * @return map of content types and message payloads
     208             :  */
     209             : std::map<std::string, std::string>
     210       28352 : im::parseSipMessage(const pjsip_msg* msg)
     211             : {
     212       28352 :     std::map<std::string, std::string> ret;
     213             : 
     214       28352 :     if (!msg->body) {
     215           0 :         JAMI_WARN("message body is empty");
     216           0 :         return ret;
     217             :     }
     218             : 
     219             :     // check if its a multipart message
     220       28352 :     constexpr pj_str_t typeMultipart {CONST_PJ_STR("multipart")};
     221             : 
     222       28352 :     if (pj_strcmp(&typeMultipart, &msg->body->content_type.type) != 0) {
     223             :         // treat as single content type message
     224       28352 :         ret.emplace(parseMessageBody(msg->body));
     225             :     } else {
     226             :         /* multipart type message, we will treat it as multipart/mixed even if the subtype is
     227             :          * something else, eg: related
     228             :          */
     229           0 :         auto part = pjsip_multipart_get_first_part(msg->body);
     230           0 :         while (part != nullptr) {
     231           0 :             ret.emplace(parseMessageBody(part->body));
     232           0 :             part = pjsip_multipart_get_next_part(msg->body, part);
     233             :         }
     234             :     }
     235             : 
     236       28345 :     return ret;
     237           0 : }
     238             : 
     239             : } // namespace jami

Generated by: LCOV version 1.14