diff --git a/bin/dbus/callmanager-introspec.xml b/bin/dbus/callmanager-introspec.xml index 66cb318e86b61245f4600a399b3adc3a60cf7e6a..32a6fe25abf76ba88776891a01852a002588aab2 100644 --- a/bin/dbus/callmanager-introspec.xml +++ b/bin/dbus/callmanager-introspec.xml @@ -445,7 +445,8 @@ Send a text message to the specified call </tp:docstring> <arg type="s" name="callID" direction="in"/> - <arg type="s" name="message" direction="in"/> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="MapStringString"/> + <arg type="a{ss}" name="message" direction="in"/> </method> <signal name="newCallCreated" tp:name-for-bindings="newCallCreated"> @@ -498,11 +499,13 @@ <signal name="incomingMessage" tp:name-for-bindings="incomingMessage"> <tp:docstring> - Notify clients that a new text message has been received. + Notify clients that new messages have been received. The key is + the mime type and the value the mime payload. </tp:docstring> <arg type="s" name="callID" /> <arg type="s" name="from" /> - <arg type="s" name="message" /> + <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="MapStringString"/> + <arg type="a{ss}" name="messages" /> </signal> <signal name="callStateChanged" tp:name-for-bindings="callStateChanged"> diff --git a/bin/dbus/configurationmanager-introspec.xml b/bin/dbus/configurationmanager-introspec.xml index bb0003f2c1d532d85ceabaa1dc2d71ae96695d62..c3b09f665be1c0a5c64906daa2501c25d86dcd85 100644 --- a/bin/dbus/configurationmanager-introspec.xml +++ b/bin/dbus/configurationmanager-introspec.xml @@ -237,7 +237,6 @@ </method> <method name="sendTextMessage" tp:name-for-bindings="sendTextMessage"> - <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> <arg type="s" name="accountID" direction="in"> </arg> <arg type="s" name="to" direction="in"> @@ -247,7 +246,7 @@ </method> <signal name="incomingAccountMessage" tp:name-for-bindings="incomingAccountMessage"> - <tp:added version="2.2.0"/> + <tp:added version="2.2.0"/> <tp:docstring> Notify clients that a new text message has been received at the account level. </tp:docstring> diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp index 4eb4cf30c70cbbf048698ac4535811baea7ff31c..afe237609d9ea9ad87746fd2015f8d5cb9fe1c02 100644 --- a/bin/dbus/dbuscallmanager.cpp +++ b/bin/dbus/dbuscallmanager.cpp @@ -289,7 +289,7 @@ DBusCallManager::acceptEnrollment(const std::string& callID, const bool& accepte } void -DBusCallManager::sendTextMessage(const std::string& callID, const std::string& message) +DBusCallManager::sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages) { - DRing::sendTextMessage(callID, message); + DRing::sendTextMessage(callID, messages, "Me"); } diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h index 6d338f33c979c0318b1abfedc4ddf3f441e7d457..43c8f87aaaf18e2c1ade9449b5112bd6b38b96ee 100644 --- a/bin/dbus/dbuscallmanager.h +++ b/bin/dbus/dbuscallmanager.h @@ -106,7 +106,7 @@ class DBusCallManager : void setConfirmGoClear(const std::string& callID); void requestGoClear(const std::string& callID); void acceptEnrollment(const std::string& callID, const bool& accepted); - void sendTextMessage(const std::string& callID, const std::string& message); + void sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages); }; #endif // __RING_CALLMANAGER_H__ diff --git a/src/call.h b/src/call.h index 78a1f98f4dbd6623d206aa9ae9c829ac7ef41b8a..ee432543ae94c02e9d5d96ccecb5fe2dbe509acd 100644 --- a/src/call.h +++ b/src/call.h @@ -301,10 +301,10 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> { /** * Send a message to a call identified by its callid * - * @param The actual message to be transmitted + * @param A list of mimetype/payload pairs * @param The sender of this message (could be another participant of a conference) */ - virtual void sendTextMessage(const std::string &message, + virtual void sendTextMessage(const std::map<std::string, std::string>& messages, const std::string &from) = 0; #endif diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp index 625748df23962717048e5b53e0ffb8cb5a29af90..b90e689a6b007a62d82ed0019388b2e74f28c788 100644 --- a/src/client/callmanager.cpp +++ b/src/client/callmanager.cpp @@ -332,20 +332,10 @@ acceptEnrollment(const std::string& /*callID*/, bool /*accepted*/) } void -sendTextMessage(const std::string& callID, const std::string& message, const std::string& from) +sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from) { #if HAVE_INSTANT_MESSAGING - ring::Manager::instance().sendCallTextMessage(callID, message, from); -#endif -} - -void -sendTextMessage(const std::string& callID, const std::string& message) -{ -#if HAVE_INSTANT_MESSAGING - ring::Manager::instance().sendCallTextMessage(callID, message, "Me"); -#else - RING_ERR("Could not send \"%s\" text message to %s since Ring daemon does not support it, please recompile with instant messaging support", message.c_str(), callID.c_str()); + ring::Manager::instance().sendCallTextMessage(callID, messages, from); #endif } diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h index b3cfcb6d4fd352d1e8393864a165b2d181c727d8..397eb5e3a73c3b4a2fc36428ebe55783006acf29 100644 --- a/src/dring/callmanager_interface.h +++ b/src/dring/callmanager_interface.h @@ -102,8 +102,7 @@ void requestGoClear(const std::string& callID); void acceptEnrollment(const std::string& callID, bool accepted); /* Instant messaging */ -void sendTextMessage(const std::string& callID, const std::string& message); -void sendTextMessage(const std::string& callID, const std::string& message, const std::string& from); +void sendTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from); // Call signal type definitions struct CallSignal { @@ -129,7 +128,7 @@ struct CallSignal { }; struct IncomingMessage { constexpr static const char* name = "IncomingMessage"; - using cb_type = void(const std::string&, const std::string&, const std::string&); + using cb_type = void(const std::string&, const std::string&, const std::map<std::string, std::string>&); }; struct IncomingCall { constexpr static const char* name = "IncomingCall"; diff --git a/src/iax/iaxcall.cpp b/src/iax/iaxcall.cpp index 38fb846ba40a6b8e712add3c1e4e4416cf4863b2..c7243eb3160fc1867029594246d1002b5074ed8b 100644 --- a/src/iax/iaxcall.cpp +++ b/src/iax/iaxcall.cpp @@ -241,11 +241,13 @@ IAXCall::carryingDTMFdigits(char code) } #if HAVE_INSTANT_MESSAGING -void -IAXCall::sendTextMessage(const std::string& message, const std::string& /*from*/) + +void IAXCall::sendTextMessage(const std::map<std::string, std::string>& messages, + const std::string &from) { std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); - InstantMessaging::send_iax_message(session, getCallId(), message.c_str()); + for (const auto &message : messages) + InstantMessaging::send_iax_message(session, getCallId(), message.second.c_str()); } #endif diff --git a/src/iax/iaxcall.h b/src/iax/iaxcall.h index d4eb50ca32a82b2a74d3a2dfb59dd775c7bb621e..f02f4b2255b56ebd84e1434cdc9b6df9eba1b302 100644 --- a/src/iax/iaxcall.h +++ b/src/iax/iaxcall.h @@ -142,8 +142,9 @@ class IAXCall : public Call void carryingDTMFdigits(char code); #if HAVE_INSTANT_MESSAGING - void sendTextMessage(const std::string& message, - const std::string& from); + + virtual void sendTextMessage(const std::map<std::string, std::string>& messages, + const std::string &from); #endif void putAudioData(AudioBuffer& buf); diff --git a/src/iax/iaxvoiplink.cpp b/src/iax/iaxvoiplink.cpp index be32070173b95cda64acbc002070aa4a127dd7a7..d72ec527415a56e8927e0c9be845d5a20eea662e 100644 --- a/src/iax/iaxvoiplink.cpp +++ b/src/iax/iaxvoiplink.cpp @@ -261,8 +261,8 @@ IAXVoIPLink::handleBusy(IAXCall& call) void IAXVoIPLink::handleMessage(iax_event* event, IAXCall& call) { - Manager::instance().incomingMessage(call.getCallId(), call.getPeerNumber(), - std::string((const char*) event->data)); + Manager::instance().incomingMessage(call.getCallId(), call.getPeerNumber() ,std::map<std::string, std::string> + {{"text/plain", std::string((const char*) event->data)}}); } #endif diff --git a/src/im/instant_messaging.cpp b/src/im/instant_messaging.cpp index 718a7ddb6a5990b92bf373e01d0bc27901d4b50a..6436a3965db43df4272fb8c44dbe90e6ae4f9011 100644 --- a/src/im/instant_messaging.cpp +++ b/src/im/instant_messaging.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Emmanuel Lepage <elv1313@gmail.com> * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify @@ -160,19 +161,37 @@ InstantMessaging::parseXmlUriList(const std::string &urilist) return list; } -std::string InstantMessaging::appendUriList(const std::string &text, UriList& list) +///See rfc2046#section-5.1.4 +static std::string buildMimeMultipartPart(const std::string &type, const std::string &dispo, const std::string &content) { - return "--boundary Content-Type: text/plain" + text + - "--boundary Content-Type: application/resource-lists+xml" + - "Content-Disposition: recipient-list" + generateXmlUriList(list) + - "--boundary--"; + return + "--boundary\n" + "Content-Type: " + type + "\n" + + (!dispo.empty() ? "Content-Disposition: "+ dispo + "\n" : "") + + "\n" + + content + "\n"; +} + +std::string InstantMessaging::appendMimePayloads(const std::map<std::string,std::string> payloads, UriList& list) +{ + std::string ret; + + for (const auto pair : payloads) { + ret += buildMimeMultipartPart(pair.first, {}, pair.second); + } + + if (!list.empty()) + ret += buildMimeMultipartPart("application/resource-lists+xml", "recipient-list", generateXmlUriList(list)); + + ret += "--boundary--"; + return ret; } std::string InstantMessaging::findTextUriList(const std::string &text) { - const std::string ctype("Content-Type: application/resource-lists+xml"); - const std::string cdispo("Content-Disposition: recipient-list"); - const std::string boundary("--boundary--"); + static const std::string ctype("Content-Type: application/resource-lists+xml"); + static const std::string cdispo("Content-Disposition: recipient-list"); + static const std::string boundary("--boundary--"); // init position pointer size_t pos = 0; @@ -185,8 +204,12 @@ std::string InstantMessaging::findTextUriList(const std::string &text) if ((pos = text.find(cdispo, pos)) == std::string::npos) throw InstantMessageException("Could not find Content-Disposition tag while parsing sip message for recipient-list"); - // xml content start after content disposition tag (plus \n\n) - const size_t begin = pos + cdispo.size(); + // xml content start after content disposition tag + size_t begin = pos + cdispo.size(); + + //Remove arbitrary number of empty lines, otherwise XML_Parse will return XML_STATUS_ERROR + while (text[begin] == '\n' || text[begin] == '\r') + begin++; // find final boundary size_t end; @@ -196,20 +219,98 @@ std::string InstantMessaging::findTextUriList(const std::string &text) return text.substr(begin, end - begin); } -std::string InstantMessaging::findTextMessage(const std::string &text) +/* + * From RFC2046: + * + * MIME-Version: 1.0 + * Content-Type: multipart/alternative; boundary=boundary42 + * + * --boundary42 + * Content-Type: text/plain; charset=us-ascii + * + * ... plain text version of message goes here ... + * + * --boundary42 + * Content-Type: text/html + * + * ... RFC 1896 text/enriched version of same message + * goes here ... + * + * --boundary42 + * Content-Type: application/x-whatever + * + * ... fanciest version of same message goes here ... + * + * --boundary42-- + */ + +std::string InstantMessaging::findMimePayload(const std::string &encodedPayloads, const std::string &mime) { - std::string ctype = "Content-Type: text/plain"; - const size_t pos = text.find(ctype); + const std::string ctype = "Content-Type: " + mime; + const size_t pos = encodedPayloads.find(ctype); if (pos == std::string::npos) - throw InstantMessageException("Could not find Content-Type tag while parsing sip message for text"); - + return {}; const size_t begin = pos + ctype.size(); - const size_t end = text.find("--boundary", begin); - if (end == std::string::npos) - throw InstantMessageException("Could not find end of text \"boundary\" while parsing sip message for text"); + const size_t end = encodedPayloads.find("--boundary", begin); + if (end == std::string::npos) { + RING_DBG("Could not find end of text \"boundary\" while parsing sip message for text"); + return {}; + } - return text.substr(begin, end - begin); + return encodedPayloads.substr(begin, end - begin); +} + +std::map< std::string, std::string > InstantMessaging::parsePayloads(const std::string &encodedPayloads) +{ + //Constants + static const std::string boud = "--boundary" ; + static const std::string type = "Content-Type: " ; + static const std::string dispo = "Content-Disposition: "; + const size_t end = encodedPayloads.find("--boundary--"); + + size_t next_start = encodedPayloads.find(boud); + + std::map< std::string, std::string > ret; + + do { + size_t currentStart = next_start; + + next_start = encodedPayloads.find(boud, currentStart+1); + + //Get the mime type + size_t context_pos = encodedPayloads.find(type, currentStart+1); + if (context_pos == std::string::npos) + break; + else if (context_pos >= next_start) + continue; + + context_pos += type.size(); + + size_t mimeTypeEnd = encodedPayloads.find('\n', context_pos+1); + if (encodedPayloads[context_pos-1] == '\r') + mimeTypeEnd--; + + std::string mimeType = encodedPayloads.substr(context_pos, mimeTypeEnd - context_pos); + currentStart = mimeTypeEnd+1; + + //Remove the disposition + const size_t dispoPos = encodedPayloads.find(dispo, currentStart); + if (dispoPos != std::string::npos && dispoPos < next_start) { + currentStart = encodedPayloads.find('\n', dispoPos); + while (encodedPayloads[currentStart] == '\n' || encodedPayloads[currentStart] == '\r') + currentStart++; + } + + //Get the payload + std::string payload = encodedPayloads.substr(currentStart, next_start - currentStart); + + //WARNING assume only one message per payload exist + ret[mimeType] = payload; + + } while(next_start < end); + + return ret; } } // namespace ring diff --git a/src/im/instant_messaging.h b/src/im/instant_messaging.h index 415857c52891582b20c0d9fc15e73dd3da3b7832..8170d2b344af98c9cb4e99a6f0137d3688997e1b 100644 --- a/src/im/instant_messaging.h +++ b/src/im/instant_messaging.h @@ -123,7 +123,7 @@ UriList parseXmlUriList(const std::string &urilist); * * @return formated text stored into a string to be included in sip MESSAGE */ -std::string appendUriList(const std::string &text, UriList &list); +std::string appendMimePayloads(const std::map<std::string,std::string> payloads, UriList& list); /** * Retreive the xml formated uri list in formated text data according to RFC 5365 @@ -135,13 +135,22 @@ std::string appendUriList(const std::string &text, UriList &list); std::string findTextUriList(const std::string &text); /** - * Retrive the plain text message in formated text data according to RFC 5365 + * Retrieve a MIME payload from the SIP container RFC5365 * - * @param text The formated text message as retreived in the SIP message + * @param mime the mime type + * + * @param encodedPayloads a MIME encoded set of payloads * * @return A string containing the actual message */ -std::string findTextMessage(const std::string &text); +std::string findMimePayload(const std::string &encodedPayloads, const std::string &mime = "text/plain"); + +/** + * Retrieve all MIME payloads from encodedPayloads + * + * @param encodedPayloads a MIME encoded set of payloads + */ +std::map< std::string, std::string > parsePayloads(const std::string &encodedPayloads); }} // namespace ring::InstantMessaging diff --git a/src/manager.cpp b/src/manager.cpp index 3c986fcd6e3d65342423d3768dd648759402b750..f766f40a7dfb245ae46e23edcdd73dcfcbaaecec 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -1629,10 +1629,11 @@ Manager::incomingCall(Call &call, const std::string& accountId) //THREAD=VoIP #if HAVE_INSTANT_MESSAGING + void Manager::incomingMessage(const std::string& callID, const std::string& from, - const std::string& message) + const std::map<std::string, std::string>& messages) { if (isConferenceParticipant(callID)) { auto conf = getConferenceFromCallID(callID); @@ -1647,7 +1648,7 @@ Manager::incomingMessage(const std::string& callID, RING_DBG("Send message to %s", item_p.c_str()); if (auto call = getCallFromCallID(item_p)) { - call->sendTextMessage(message, from); + call->sendTextMessage(messages, from); } else { RING_ERR("Failed to get call while sending instant message"); return; @@ -1655,16 +1656,16 @@ Manager::incomingMessage(const std::string& callID, } // in case of a conference we must notify client using conference id - emitSignal<DRing::CallSignal::IncomingMessage>(conf->getConfID(), from, message); + emitSignal<DRing::CallSignal::IncomingMessage>(conf->getConfID(), from, messages); } else - emitSignal<DRing::CallSignal::IncomingMessage>(callID, from, message); + emitSignal<DRing::CallSignal::IncomingMessage>(callID, from, messages); } //THREAD=VoIP bool Manager::sendCallTextMessage(const std::string& callID, - const std::string& message, - const std::string& from) + const std::map<std::string, std::string>& messages, + const std::string& from) { if (isConference(callID)) { RING_DBG("Is a conference, send instant message to everyone"); @@ -1683,7 +1684,7 @@ Manager::sendCallTextMessage(const std::string& callID, for (const auto &participant_id : participants) { if (auto call = getCallFromCallID(participant_id)) { - call->sendTextMessage(message, from); + call->sendTextMessage(messages, from); } else { RING_ERR("Failed to get call while sending instant message"); return false; @@ -1705,7 +1706,7 @@ Manager::sendCallTextMessage(const std::string& callID, for (const auto &participant_id : participants) { if (auto call = getCallFromCallID(participant_id)) { - call->sendTextMessage(message, from); + call->sendTextMessage(messages, from); } else { RING_ERR("Failed to get call while sending instant message"); return false; @@ -1713,7 +1714,7 @@ Manager::sendCallTextMessage(const std::string& callID, } } else { if (auto call = getCallFromCallID(callID)) { - call->sendTextMessage(message, from); + call->sendTextMessage(messages, from); } else { RING_ERR("Failed to get call while sending instant message"); return false; @@ -1721,6 +1722,7 @@ Manager::sendCallTextMessage(const std::string& callID, } return true; } + #endif // HAVE_INSTANT_MESSAGING //THREAD=VoIP CALL=Outgoing diff --git a/src/manager.h b/src/manager.h index 23917ccbd5f48a9543a1e10d9e580ea677937bff..d586f02966f0376715092a7fe5e5a022a8a8158b 100644 --- a/src/manager.h +++ b/src/manager.h @@ -388,19 +388,20 @@ class Manager { #if HAVE_INSTANT_MESSAGING /** * Notify the client with an incoming message - * @param accountId The account identifier - * @param message The content of the message + * @param accountId The account identifier + * @param messages A map if mime type as key and mime payload as value */ - void incomingMessage(const std::string& callID, const std::string& from, const std::string& message); - + void incomingMessage(const std::string& callID, + const std::string& from, + const std::map<std::string, std::string>& messages); /** * Send a new text message to the call, if participate to a conference, send to all participant. - * @param callID The call to send the message - * @param message The content of the message - * @param from The sender of this message (could be another participant of a conference) + * @param callID The call to send the message + * @param message A list of pair of mime types and payloads + * @param from The sender of this message (could be another participant of a conference) */ - bool sendCallTextMessage(const std::string& callID, const std::string& message, const std::string& from); + bool sendCallTextMessage(const std::string& callID, const std::map<std::string, std::string>& messages, const std::string& from); #endif // HAVE_INSTANT_MESSAGING /** diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index 1950e76446bedd0fab5ae13f22ecbe3f4cfb78f7..9f9ec612c5e39af11f554e8818182ef7e697d100 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -681,8 +681,9 @@ SIPCall::carryingDTMFdigits(char code) } #if HAVE_INSTANT_MESSAGING -void -SIPCall::sendTextMessage(const std::string &message, const std::string &from) + +void SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, + const std::string &from) { if (not inv) throw VoipLinkException("No invite session for this call"); @@ -692,7 +693,7 @@ SIPCall::sendTextMessage(const std::string &message, const std::string &from) InstantMessaging::UriEntry entry; entry[InstantMessaging::IM_XML_URI] = std::string("\"" + from + "\""); // add double quotes for xml formating list.push_front(entry); - auto msg = InstantMessaging::appendUriList(message, list); + auto msg = InstantMessaging::appendMimePayloads(messages, list); InstantMessaging::send_sip_message(inv.get(), getCallId(), msg); } #endif // HAVE_INSTANT_MESSAGING diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 1b2389e42a4ff194f66d4865308be1ca5313c063..f099c2d811b3197a061bf79b2aee42b01c9e5b97 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -163,8 +163,8 @@ class SIPCall : public Call void carryingDTMFdigits(char code); #if HAVE_INSTANT_MESSAGING - void sendTextMessage(const std::string& message, - const std::string& from); + virtual void sendTextMessage(const std::map<std::string, std::string>& messages, + const std::string &from); #endif SIPAccountBase& getSIPAccount() const; diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp index 57070c5b6a58eef2b2aacf83629812b15b29aa46..5890328e3e54b1ee612ec1f7c7ddeaccab52143b 100644 --- a/src/sip/sipvoiplink.cpp +++ b/src/sip/sipvoiplink.cpp @@ -1166,8 +1166,7 @@ transaction_state_changed_cb(pjsip_inv_session * inv, pjsip_transaction *tsx, if (from[0] == '<' && from[from.size() - 1] == '>') from = from.substr(1, from.size() - 2); - Manager::instance().incomingMessage(call->getCallId(), from, - InstantMessaging::findTextMessage(formattedMessage)); + Manager::instance().incomingMessage(call->getCallId(), from, InstantMessaging::parsePayloads(formattedMessage)); // Respond with a 200/OK sendOK(inv->dlg, r_data, tsx);