Ring Daemon
Loading...
Searching...
No Matches
instant_messaging.cpp
Go to the documentation of this file.
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"
21#include "string_utils.h"
22
23#include <pjsip_ua.h>
24#include <pjsip.h>
25
26namespace jami {
27
29
39static void
40createMessageBody(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 std::string_view mimeType, parameters;
48 auto sep = payload.first.find(';');
49 if (std::string::npos == sep) {
50 mimeType = payload.first;
51 } else {
52 mimeType = std::string_view(payload.first).substr(0, sep);
53 parameters = std::string_view(payload.first).substr(sep + 1);
54 }
55
56 // split MIME type to type and subtype
57 sep = mimeType.find('/');
58 if (std::string::npos == sep) {
59 JAMI_WARNING("Bad MIME type: '{}'", mimeType);
60 throw im::InstantMessageException("Invalid MIME type");
61 }
62
63 auto type = sip_utils::CONST_PJ_STR(mimeType.substr(0, sep));
64 auto subtype = sip_utils::CONST_PJ_STR(mimeType.substr(sep + 1));
65 auto message = sip_utils::CONST_PJ_STR(payload.second);
66
67 // create part
68 *body_p = pjsip_msg_body_create(pool, &type, &subtype, &message);
69
70 if (not parameters.size())
71 return;
72
73 // now attempt to add parameters one by one
74 do {
75 sep = parameters.find(';');
76 auto paramPair = parameters.substr(0, sep);
77 if (paramPair.empty())
78 break;
79
80 // split paramPair into arg and value by '='
81 auto paramSplit = paramPair.find('=');
82 if (std::string::npos == paramSplit) {
83 JAMI_WARNING("Bad parameter: '{}'", paramPair);
84 throw im::InstantMessageException("Invalid parameter");
85 }
86
88 auto value = sip_utils::CONST_PJ_STR(paramPair.substr(paramSplit + 1));
90 pj_strtrim(&value);
93 param->name = *pj_strdup(pool, &arg_pj, &arg);
94 param->value = *pj_strdup(pool, &value_pj, &value);
95 pj_list_push_back(&(*body_p)->content_type.param, param);
96
97 // next parameter?
98 if (std::string::npos != sep)
99 parameters = parameters.substr(sep + 1);
100 } while (std::string::npos != sep);
101}
102
103void
104im::fillPJSIPMessageBody(pjsip_tx_data& tdata, const std::map<std::string, std::string>& payloads)
105{
106 // multi-part body?
107 if (payloads.size() == 1) {
108 createMessageBody(tdata.pool, *payloads.begin(), &tdata.msg->body);
109 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 tdata.msg->body = pjsip_multipart_create(tdata.pool, nullptr, nullptr);
119
120 for (const auto& pair : payloads) {
122 if (not part) {
123 JAMI_ERR("pjsip_multipart_create_part failed: not enough memory");
124 throw InstantMessageException("Internal SIP error");
125 }
126
127 createMessageBody(tdata.pool, pair, &part->body);
128
129 auto status = pjsip_multipart_add_part(tdata.pool, tdata.msg->body, part);
130 if (status != PJ_SUCCESS) {
131 JAMI_ERR("pjsip_multipart_add_part failed: %s", sip_utils::sip_strerror(status).c_str());
132 throw InstantMessageException("Internal SIP error");
133 }
134 }
135}
136
137void
138im::sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::string>& payloads)
139{
140 if (payloads.empty()) {
141 JAMI_WARN("The payloads argument is empty; ignoring message");
142 return;
143 }
144
146
147 {
148 auto* dialog = session->dlg;
150
151 pjsip_tx_data* tdata = nullptr;
152 auto status = pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata);
153 if (status != PJ_SUCCESS) {
154 JAMI_ERR("pjsip_dlg_create_request failed: %s", sip_utils::sip_strerror(status).c_str());
155 throw InstantMessageException("Internal SIP error");
156 }
157
158 fillPJSIPMessageBody(*tdata, payloads);
159
160 status = pjsip_dlg_send_request(dialog, tdata, -1, nullptr);
161 if (status != PJ_SUCCESS) {
162 JAMI_ERR("pjsip_dlg_send_request failed: %s", sip_utils::sip_strerror(status).c_str());
163 throw InstantMessageException("Internal SIP error");
164 }
165 }
166}
167
176static std::pair<std::string, std::string>
178{
179 std::string header = sip_utils::as_view(body->content_type.type) + "/"
180 + sip_utils::as_view(body->content_type.subtype);
181
182 // iterate over parameters
183 auto* param = body->content_type.param.next;
184 while (param != &body->content_type.param) {
185 header += ";" + sip_utils::as_view(param->name) + "=" + sip_utils::as_view(param->value);
186 param = param->next;
187 }
188
189 // get the payload, assume we can interpret it as chars
190 return {std::move(header), std::string(static_cast<char*>(body->data), (size_t) body->len)};
191}
192
201std::map<std::string, std::string>
203{
204 std::map<std::string, std::string> ret;
205
206 if (!msg->body) {
207 JAMI_WARN("Message body is empty");
208 return ret;
209 }
210
211 // check if its a multipart message
212 constexpr pj_str_t typeMultipart {CONST_PJ_STR("multipart")};
213
214 if (pj_strcmp(&typeMultipart, &msg->body->content_type.type) != 0) {
215 // treat as single content type message
216 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 */
222 while (part != nullptr) {
223 ret.emplace(parseMessageBody(part->body));
225 }
226 }
227
228 return ret;
229}
230
231} // namespace jami
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
void fillPJSIPMessageBody(pjsip_tx_data &tdata, const std::map< std::string, std::string > &payloads)
std::map< std::string, std::string > parseSipMessage(const pjsip_msg *msg)
Parses given SIP message into a map where the key is the contents of the Content-Type header (along w...
void sendSipMessage(pjsip_inv_session *session, const std::map< std::string, std::string > &payloads)
Constructs and sends a SIP message.
constexpr std::string_view MESSAGE
Definition sip_utils.h:43
std::string sip_strerror(pj_status_t code)
constexpr std::string_view as_view(const pj_str_t &str) noexcept
Definition sip_utils.h:105
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
Definition sip_utils.h:87
static void createMessageBody(pj_pool_t *pool, const std::pair< std::string, std::string > &payload, pjsip_msg_body **body_p)
the pair<string, string> we receive is expected to be in the format <MIME type, payload> the MIME typ...
void emitSignal(Args... args)
Definition jami_signal.h:64
static std::pair< std::string, std::string > parseMessageBody(const pjsip_msg_body *body)
Creates std::pair with the Content-Type header contents as the first value and the message payload as...