instantmessages: Support multi payload messaging

 * A new signal to receive multiple payloads
 * Send the message as both html and text
 * Parse MIME format to extract all payloads
 * Send messages with multiple payloads

WARNING: This commit break the API

To restore the old behavior, users of sendTextMessage should
use the "text/plain" MIME type.

Refs #77651

Change-Id: Ic20c0cea48ba5d7ec625e69cd87eeffc1fdbe759
parent aa782278
......@@ -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">
......
......@@ -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>
......
......@@ -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");
}
......@@ -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__
......@@ -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
......
......@@ -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
}
......
......@@ -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";
......
......@@ -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
......
......@@ -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);
......
......@@ -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
......
/*
* 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
......@@ -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
......
......@@ -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
......
......@@ -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
/**
......
......@@ -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
......
......@@ -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;
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment