Ring Daemon 16.0.0
Loading...
Searching...
No Matches
instant_messaging.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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
22#include <pjsip_ua.h>
23#include <pjsip.h>
24
25namespace jami {
26
28
38static void
40 const std::pair<std::string, std::string>& payload,
42{
43 /* parse the key:
44 * 1. split by ';'
45 * 2. parse the first result by spliting by '/' into a type and subtype
46 * 3. parse any following strings into arg=value by splitting by '='
47 */
48 std::string_view mimeType, parameters;
49 auto sep = payload.first.find(';');
50 if (std::string::npos == sep) {
51 mimeType = payload.first;
52 } else {
53 mimeType = std::string_view(payload.first).substr(0, sep);
54 parameters = std::string_view(payload.first).substr(sep + 1);
55 }
56
57 // split MIME type to type and subtype
58 sep = mimeType.find('/');
59 if (std::string::npos == sep) {
60 JAMI_WARNING("Bad MIME type: '{}'", mimeType);
61 throw im::InstantMessageException("Invalid MIME type");
62 }
63
64 auto type = sip_utils::CONST_PJ_STR(mimeType.substr(0, sep));
65 auto subtype = sip_utils::CONST_PJ_STR(mimeType.substr(sep + 1));
66 auto message = sip_utils::CONST_PJ_STR(payload.second);
67
68 // create part
69 *body_p = pjsip_msg_body_create(pool, &type, &subtype, &message);
70
71 if (not parameters.size())
72 return;
73
74 // now attempt to add parameters one by one
75 do {
76 sep = parameters.find(';');
77 auto paramPair = parameters.substr(0, sep);
78 if (paramPair.empty())
79 break;
80
81 // split paramPair into arg and value by '='
82 auto paramSplit = paramPair.find('=');
83 if (std::string::npos == paramSplit) {
84 JAMI_WARNING("Bad parameter: '{}'", paramPair);
85 throw im::InstantMessageException("Invalid parameter");
86 }
87
94 param->name = *pj_strdup(pool, &arg_pj, &arg);
95 param->value = *pj_strdup(pool, &value_pj, &value);
96 pj_list_push_back(&(*body_p)->content_type.param, param);
97
98 // next parameter?
99 if (std::string::npos != sep)
100 parameters = parameters.substr(sep + 1);
101 } while (std::string::npos != sep);
102}
103
104void
105im::fillPJSIPMessageBody(pjsip_tx_data& tdata, const std::map<std::string, std::string>& payloads)
106{
107 // multi-part body?
108 if (payloads.size() == 1) {
109 createMessageBody(tdata.pool, *payloads.begin(), &tdata.msg->body);
110 return;
111 }
112
113 /* if Ctype is not specified "multipart/mixed" will be used
114 * if the boundary is not specified, a random one will be generateAudioPort
115 * FIXME: generate boundary and check that none of the message parts contain it before
116 * calling this function; however the probability of this happenings if quite low as
117 * the randomly generated string is fairly long
118 */
119 tdata.msg->body = pjsip_multipart_create(tdata.pool, nullptr, nullptr);
120
121 for (const auto& pair : payloads) {
123 if (not part) {
124 JAMI_ERR("pjsip_multipart_create_part failed: not enough memory");
125 throw InstantMessageException("Internal SIP error");
126 }
127
128 createMessageBody(tdata.pool, pair, &part->body);
129
130 auto status = pjsip_multipart_add_part(tdata.pool, tdata.msg->body, part);
131 if (status != PJ_SUCCESS) {
132 JAMI_ERR("pjsip_multipart_add_part failed: %s", sip_utils::sip_strerror(status).c_str());
133 throw InstantMessageException("Internal SIP error");
134 }
135 }
136}
137
138void
139im::sendSipMessage(pjsip_inv_session* session, const std::map<std::string, std::string>& payloads)
140{
141 if (payloads.empty()) {
142 JAMI_WARN("The payloads argument is empty; ignoring message");
143 return;
144 }
145
147 CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
148
149 {
150 auto dialog = session->dlg;
152
153 pjsip_tx_data* tdata = nullptr;
154 auto status = pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata);
155 if (status != PJ_SUCCESS) {
156 JAMI_ERR("pjsip_dlg_create_request failed: %s", sip_utils::sip_strerror(status).c_str());
157 throw InstantMessageException("Internal SIP error");
158 }
159
160 fillPJSIPMessageBody(*tdata, payloads);
161
162 status = pjsip_dlg_send_request(dialog, tdata, -1, nullptr);
163 if (status != PJ_SUCCESS) {
164 JAMI_ERR("pjsip_dlg_send_request failed: %s", sip_utils::sip_strerror(status).c_str());
165 throw InstantMessageException("Internal SIP error");
166 }
167 }
168}
169
178static std::pair<std::string, std::string>
180{
181 std::string header = sip_utils::as_view(body->content_type.type) + "/"
182 + sip_utils::as_view(body->content_type.subtype);
183
184 // iterate over parameters
185 auto param = body->content_type.param.next;
186 while (param != &body->content_type.param) {
187 header += ";" + sip_utils::as_view(param->name) + "=" + sip_utils::as_view(param->value);
188 param = param->next;
189 }
190
191 // get the payload, assume we can interpret it as chars
192 return {std::move(header), std::string(static_cast<char*>(body->data), (size_t) body->len)};
193}
194
203std::map<std::string, std::string>
205{
206 std::map<std::string, std::string> ret;
207
208 if (!msg->body) {
209 JAMI_WARN("Message body is empty");
210 return ret;
211 }
212
213 // check if its a multipart message
214 constexpr pj_str_t typeMultipart {CONST_PJ_STR("multipart")};
215
216 if (pj_strcmp(&typeMultipart, &msg->body->content_type.type) != 0) {
217 // treat as single content type message
218 ret.emplace(parseMessageBody(msg->body));
219 } else {
220 /* multipart type message, we will treat it as multipart/mixed even if the subtype is
221 * something else, eg: related
222 */
224 while (part != nullptr) {
225 ret.emplace(parseMessageBody(part->body));
227 }
228 }
229
230 return ret;
231}
232
233} // namespace jami
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_WARN(...)
Definition logger.h:217
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
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:45
std::string sip_strerror(pj_status_t code)
constexpr std::string_view as_view(const pj_str_t &str) noexcept
Definition sip_utils.h:107
constexpr const pj_str_t CONST_PJ_STR(T(&a)[N]) noexcept
Definition sip_utils.h:89
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 ring_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...