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
|