RetroShare/libresapi/src/api/ChatHandler.cpp
2017-04-28 13:26:31 +02:00

1272 lines
46 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* libresapi
* Copyright (C) 2015 electron128 <electron128@yahoo.com>
* Copyright (C) 2017 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ChatHandler.h"
#include "Pagination.h"
#include "Operators.h"
#include "GxsResponseTask.h"
#include <retroshare/rspeers.h>
#include <retroshare/rsidentity.h>
#include <retroshare/rshistory.h>
#include <algorithm>
#include <limits>
#include <sstream>
#include <time.h>
namespace resource_api
{
std::string id(const ChatHandler::Msg& m)
{
std::stringstream ss;
ss << m.id;
return ss.str();
}
StreamBase& operator << (StreamBase& left, ChatHandler::Msg& m)
{
left << makeKeyValueReference("incoming", m.incoming)
<< makeKeyValueReference("was_send", m.was_send)
<< makeKeyValueReference("author_id", m.author_id)
<< makeKeyValueReference("author_name", m.author_name)
<< makeKeyValueReference("msg", m.msg)
<< makeKeyValueReference("recv_time", m.recv_time)
<< makeKeyValueReference("send_time", m.send_time)
<< makeKeyValueReference("id", m.id);
StreamBase& ls = left.getStreamToMember("links");
ls.getStreamToMember();
for(std::vector<ChatHandler::Triple>::iterator vit = m.links.begin(); vit != m.links.end(); ++vit)
{
ls.getStreamToMember() << makeKeyValueReference("first", vit->first)
<< makeKeyValueReference("second", vit->second)
<< makeKeyValueReference("third", vit->third);
}
return left;
}
bool compare_lobby_id(const ChatHandler::Lobby& l1, const ChatHandler::Lobby& l2)
{
if(l1.auto_subscribe && !l2.auto_subscribe) return true;
if(!l1.auto_subscribe && l2.auto_subscribe) return false;
if(l1.is_private && !l2.is_private) return true;
if(!l1.is_private && l2.is_private) return false;
if(l1.subscribed && !l2.subscribed) return true;
if(!l1.subscribed && l2.subscribed) return false;
return l1.id < l2.id;
}
StreamBase& operator <<(StreamBase& left, KeyValueReference<ChatLobbyId> kv)
{
if(left.serialise())
{
std::stringstream ss;
ss << kv.value;
left << makeKeyValue(kv.key, ss.str());
}
else
{
std::string val;
left << makeKeyValueReference(kv.key, val);
std::stringstream ss(val);
ss >> kv.value;
}
return left;
}
StreamBase& operator << (StreamBase& left, ChatHandler::Lobby& l)
{
ChatId chatId(l.id);
if (l.is_broadcast)
chatId = ChatId::makeBroadcastId();
left << makeKeyValueReference("id", l.id)
<< makeKeyValue("chat_id", chatId.toStdString())
<< makeKeyValueReference("name",l.name)
<< makeKeyValueReference("topic", l.topic)
<< makeKeyValueReference("subscribed", l.subscribed)
<< makeKeyValueReference("auto_subscribe", l.auto_subscribe)
<< makeKeyValueReference("is_private", l.is_private)
<< makeKeyValueReference("is_broadcast", l.is_broadcast)
<< makeKeyValueReference("gxs_id", l.gxs_id);
return left;
}
StreamBase& operator << (StreamBase& left, ChatHandler::ChatInfo& info)
{
left << makeKeyValueReference("remote_author_id", info.remote_author_id)
<< makeKeyValueReference("remote_author_name", info.remote_author_name)
<< makeKeyValueReference("is_broadcast", info.is_broadcast)
<< makeKeyValueReference("is_distant_chat_id", info.is_distant_chat_id)
<< makeKeyValueReference("is_lobby", info.is_lobby)
<< makeKeyValueReference("is_peer", info.is_peer);
return left;
}
class SendLobbyParticipantsTask: public GxsResponseTask
{
public:
SendLobbyParticipantsTask(RsIdentity* idservice, ChatHandler::LobbyParticipantsInfo pi):
GxsResponseTask(idservice, 0), mParticipantsInfo(pi)
{
const std::map<RsGxsId, time_t>& map = mParticipantsInfo.participants;
for(std::map<RsGxsId, time_t>::const_iterator mit = map.begin(); mit != map.end(); ++mit)
{
requestGxsId(mit->first);
}
}
private:
ChatHandler::LobbyParticipantsInfo mParticipantsInfo;
protected:
virtual void gxsDoWork(Request &/*req*/, Response &resp)
{
resp.mDataStream.getStreamToMember();
const std::map<RsGxsId, time_t>& map = mParticipantsInfo.participants;
for(std::map<RsGxsId, time_t>::const_iterator mit = map.begin(); mit != map.end(); ++mit)
{
StreamBase& stream = resp.mDataStream.getStreamToMember();
double last_active = mit->second;
stream << makeKeyValueReference("last_active", last_active);
streamGxsId(mit->first, stream.getStreamToMember("identity"));
}
resp.mStateToken = mParticipantsInfo.state_token;
resp.setOk();
done();
}
};
ChatHandler::ChatHandler(StateTokenServer *sts, RsNotify *notify, RsMsgs *msgs, RsPeers* peers, RsIdentity* identity, UnreadMsgNotify* unread):
mStateTokenServer(sts), mNotify(notify), mRsMsgs(msgs), mRsPeers(peers), mRsIdentity(identity), mUnreadMsgNotify(unread), mMtx("ChatHandler::mMtx")
{
mNotify->registerNotifyClient(this);
mStateTokenServer->registerTickClient(this);
mMsgStateToken = mStateTokenServer->getNewToken();
mLobbiesStateToken = mStateTokenServer->getNewToken();
mUnreadMsgsStateToken = mStateTokenServer->getNewToken();
addResourceHandler("*", this, &ChatHandler::handleWildcard);
addResourceHandler("lobbies", this, &ChatHandler::handleLobbies);
addResourceHandler("create_lobby", this, &ChatHandler::handleCreateLobby);
addResourceHandler("subscribe_lobby", this, &ChatHandler::handleSubscribeLobby);
addResourceHandler("unsubscribe_lobby", this, &ChatHandler::handleUnsubscribeLobby);
addResourceHandler("autosubscribe_lobby", this, &ChatHandler::handleAutoSubsribeLobby);
addResourceHandler("clear_lobby", this, &ChatHandler::handleClearLobby);
addResourceHandler("lobby_participants", this, &ChatHandler::handleLobbyParticipants);
addResourceHandler("messages", this, &ChatHandler::handleMessages);
addResourceHandler("send_message", this, &ChatHandler::handleSendMessage);
addResourceHandler("mark_chat_as_read", this, &ChatHandler::handleMarkChatAsRead);
addResourceHandler("info", this, &ChatHandler::handleInfo);
addResourceHandler("receive_status", this, &ChatHandler::handleReceiveStatus);
addResourceHandler("send_status", this, &ChatHandler::handleSendStatus);
addResourceHandler("unread_msgs", this, &ChatHandler::handleUnreadMsgs);
addResourceHandler("initiate_distant_chat", this, &ChatHandler::handleInitiateDistantChatConnexion);
addResourceHandler("distant_chat_status", this, &ChatHandler::handleDistantChatStatus);
addResourceHandler("close_distant_chat", this, &ChatHandler::handleCloseDistantChatConnexion);
}
ChatHandler::~ChatHandler()
{
mNotify->unregisterNotifyClient(this);
mStateTokenServer->unregisterTickClient(this);
}
void ChatHandler::notifyChatMessage(const ChatMessage &msg)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
mRawMsgs.push_back(msg);
}
void ChatHandler::notifyChatCleared(const ChatId &chat_id)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
//Remove processed messages
std::list<Msg>& msgs = mMsgs[chat_id];
msgs.clear();
//Remove unprocessed messages
for(std::list<ChatMessage>::iterator lit = mRawMsgs.begin(); lit != mRawMsgs.end();)
{
ChatMessage& msg = *lit;
if (msg.chat_id == chat_id)
{
lit = mRawMsgs.erase(lit);
} else {
++lit;
}
}
}
void ChatHandler::notifyChatStatus(const ChatId &chat_id, const std::string &status)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
locked_storeTypingInfo(chat_id, status);
}
void ChatHandler::notifyChatLobbyEvent(uint64_t lobby_id, uint32_t event_type,
const RsGxsId &nickname, const std::string& any_string)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
if(event_type == RS_CHAT_LOBBY_EVENT_PEER_STATUS)
{
locked_storeTypingInfo(ChatId(lobby_id), any_string, nickname);
}
}
void ChatHandler::tick()
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
// first fetch lobbies
std::vector<Lobby> lobbies;
std::list<ChatLobbyId> subscribed_ids;
mRsMsgs->getChatLobbyList(subscribed_ids);
for(std::list<ChatLobbyId>::iterator lit = subscribed_ids.begin(); lit != subscribed_ids.end(); ++lit)
{
ChatLobbyInfo info;
if(mRsMsgs->getChatLobbyInfo(*lit,info))
{
Lobby l;
l.id = *lit;
l.name = info.lobby_name;
l.topic = info.lobby_topic;
l.subscribed = true;
l.auto_subscribe = info.lobby_flags & RS_CHAT_LOBBY_FLAGS_AUTO_SUBSCRIBE;
l.is_private = !(info.lobby_flags & RS_CHAT_LOBBY_FLAGS_PUBLIC);
l.is_broadcast = false;
l.gxs_id = info.gxs_id;
lobbies.push_back(l);
// update the lobby participants list
// maybe it causes to much traffic to do this in every tick,
// because the client would get the whole list every time a message was received
// we could reduce the checking frequency
std::map<ChatLobbyId, LobbyParticipantsInfo>::iterator mit = mLobbyParticipantsInfos.find(*lit);
if(mit == mLobbyParticipantsInfos.end())
{
mLobbyParticipantsInfos[*lit].participants = info.gxs_ids;
mLobbyParticipantsInfos[*lit].state_token = mStateTokenServer->getNewToken();
}
else
{
LobbyParticipantsInfo& pi = mit->second;
if(!std::equal(pi.participants.begin(), pi.participants.end(), info.gxs_ids.begin()))
{
pi.participants = info.gxs_ids;
mStateTokenServer->replaceToken(pi.state_token);
}
}
}
}
// remove participants info of old lobbies
std::vector<ChatLobbyId> participants_info_to_delete;
for(std::map<ChatLobbyId, LobbyParticipantsInfo>::iterator mit = mLobbyParticipantsInfos.begin();
mit != mLobbyParticipantsInfos.end(); ++mit)
{
if(std::find(subscribed_ids.begin(), subscribed_ids.end(), mit->first) == subscribed_ids.end())
{
participants_info_to_delete.push_back(mit->first);
}
}
for(std::vector<ChatLobbyId>::iterator vit = participants_info_to_delete.begin(); vit != participants_info_to_delete.end(); ++vit)
{
LobbyParticipantsInfo& pi = mLobbyParticipantsInfos[*vit];
mStateTokenServer->discardToken(pi.state_token);
mLobbyParticipantsInfos.erase(*vit);
}
{
Lobby l;
l.name = "BroadCast";
l.topic = "Retroshare broadcast chat: messages are sent to all connected friends.";
l.subscribed = true;
l.auto_subscribe = false;
l.is_private = false;
l.is_broadcast = true;
lobbies.push_back(l);
}
std::vector<VisibleChatLobbyRecord> unsubscribed_lobbies;
mRsMsgs->getListOfNearbyChatLobbies(unsubscribed_lobbies);
for(std::vector<VisibleChatLobbyRecord>::iterator vit = unsubscribed_lobbies.begin(); vit != unsubscribed_lobbies.end(); ++vit)
{
const VisibleChatLobbyRecord& info = *vit;
if(std::find(subscribed_ids.begin(), subscribed_ids.end(), info.lobby_id) == subscribed_ids.end())
{
Lobby l;
l.id = info.lobby_id;
l.name = info.lobby_name;
l.topic = info.lobby_topic;
l.subscribed = false;
l.auto_subscribe = info.lobby_flags & RS_CHAT_LOBBY_FLAGS_AUTO_SUBSCRIBE;
l.is_private = !(info.lobby_flags & RS_CHAT_LOBBY_FLAGS_PUBLIC);
l.is_broadcast = false;
l.gxs_id = RsGxsId();
lobbies.push_back(l);
}
}
// process new messages
bool changed = false;
bool lobby_unread_count_changed = false;
std::vector<std::list<ChatMessage>::iterator> done;
std::vector<RsPeerId> peers_changed;
bool gxs_id_failed = false; // to prevent asking for multiple failing gxs ids in one tick, to not flush the cache
for(std::list<ChatMessage>::iterator lit = mRawMsgs.begin(); lit != mRawMsgs.end(); ++lit)
{
ChatMessage& msg = *lit;
std::string author_id;
std::string author_name;
if(msg.chat_id.isBroadcast())
{
author_id = msg.broadcast_peer_id.toStdString();
author_name = mRsPeers->getPeerName(msg.broadcast_peer_id);
}
else if(msg.chat_id.isDistantChatId())
{
DistantChatPeerInfo dcpinfo ;
if(!rsMsgs->getDistantChatStatus(msg.chat_id.toDistantChatId(),dcpinfo))
{
std::cerr << "(EE) cannot get info for distant chat peer " << msg.chat_id.toDistantChatId() << std::endl;
continue ;
}
RsIdentityDetails details;
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.incoming? dcpinfo.to_id: dcpinfo.own_id, details))
{
author_id = details.mId.toStdString();
author_name = details.mNickname;
}
else
{
gxs_id_failed = true;
continue;
}
}
else if(msg.chat_id.isLobbyId())
{
author_id = msg.lobby_peer_gxs_id.toStdString();
RsIdentityDetails details;
if(!gxs_id_failed && mRsIdentity->getIdDetails(msg.lobby_peer_gxs_id, details))
{
author_name = details.mNickname;
lobby_unread_count_changed = true;
}
else
{
gxs_id_failed = true;
continue;
}
}
else if(msg.chat_id.isPeerId())
{
RsPeerId id;
if(msg.incoming)
id = msg.chat_id.toPeerId();
else
id = mRsPeers->getOwnId();
author_id = id.toStdString();
author_name = mRsPeers->getPeerName(id);
if(std::find(peers_changed.begin(), peers_changed.end(), msg.chat_id.toPeerId()) == peers_changed.end())
peers_changed.push_back(msg.chat_id.toPeerId());
}
else
{
std::cerr << "Error in ChatHandler::tick(): unhandled chat_id=" << msg.chat_id.toStdString() << std::endl;
// remove from queue, so msgs with wrong ids to not pile up
done.push_back(lit);
continue;
}
if(mChatInfo.find(msg.chat_id) == mChatInfo.end())
{
ChatInfo info;
info.is_broadcast = msg.chat_id.isBroadcast();
info.is_distant_chat_id = msg.chat_id.isDistantChatId();
info.is_lobby = msg.chat_id.isLobbyId();
info.is_peer = msg.chat_id.isPeerId();
if(msg.chat_id.isLobbyId())
{
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
{
if(vit->id == msg.chat_id.toLobbyId())
info.remote_author_name = vit->name;
}
}
else if(msg.chat_id.isDistantChatId())
{
RsIdentityDetails details;
DistantChatPeerInfo dcpinfo ;
if(!gxs_id_failed && rsMsgs->getDistantChatStatus(msg.chat_id.toDistantChatId(),dcpinfo)
&& mRsIdentity->getIdDetails(msg.incoming? dcpinfo.to_id: dcpinfo.own_id, details))
{
info.remote_author_id = details.mId.toStdString();
info.remote_author_name = details.mNickname;
}
else
{
gxs_id_failed = true;
continue;
}
}
else if(msg.chat_id.isPeerId())
{
info.remote_author_id = msg.chat_id.toPeerId().toStdString();
info.remote_author_name = mRsPeers->getPeerName(msg.chat_id.toPeerId());
}
mChatInfo[msg.chat_id] = info;
}
Msg m;
m.read = !msg.incoming;
m.incoming = msg.incoming;
m.was_send = msg.online;
m.author_id = author_id;
m.author_name = author_name;
// remove html tags from chat message
// extract links form href
getPlainText(msg.msg, m.msg, m.links);
m.recv_time = msg.recvTime;
m.send_time = msg.sendTime;
m.id = RSRandom::random_u32();
mMsgs[msg.chat_id].push_back(m);
done.push_back(lit);
changed = true;
}
for(std::vector<std::list<ChatMessage>::iterator>::iterator vit = done.begin(); vit != done.end(); ++vit)
mRawMsgs.erase(*vit);
// send changes
if(changed)
{
mStateTokenServer->replaceToken(mMsgStateToken);
mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
}
for(std::vector<RsPeerId>::iterator vit = peers_changed.begin(); vit != peers_changed.end(); ++vit)
{
const std::list<Msg>& msgs = mMsgs[ChatId(*vit)];
uint32_t count = 0;
for(std::list<Msg>::const_iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
if(!lit->read)
count++;
if(mUnreadMsgNotify)
mUnreadMsgNotify->notifyUnreadMsgCountChanged(*vit, count);
}
std::sort(lobbies.begin(), lobbies.end(), &compare_lobby_id);
if(lobby_unread_count_changed || mLobbies != lobbies)
{
mStateTokenServer->replaceToken(mLobbiesStateToken);
mLobbies = lobbies;
}
}
static void writeUTF8 ( std::ostream & Out, unsigned int Ch )
/* writes Ch in UTF-8 encoding to Out. Note this version only deals
with characters up to 16 bits.
From: http://www.codecodex.com/wiki/Unescape_HTML_special_characters_from_a_String*/
{
if (Ch >= 0x800)
{
Out.put(0xE0 | ((Ch >> 12) & 0x0F));
Out.put(0x80 | ((Ch >> 6) & 0x3F));
Out.put(0x80 | ((Ch ) & 0x3F));
}
else if (Ch >= 0x80)
{
Out.put(0xC0 | ((Ch >> 6) & 0x1F));
Out.put(0x80 | ((Ch ) & 0x3F));
}
else
{
Out.put(Ch);
}
}
static unsigned int stringToDecUInt ( std::string str)
{
unsigned int out = 0;
unsigned int max = std::numeric_limits<int>::max() / 10;
int lenght = str.length();
for (int curs = 0; curs < lenght; ++curs) {
char c = str[curs];
if ( (c >= '0')
&& (c <= '9')
&& (out < max)
) {
out *= 10;
out += (int)( c - '0');
} else return 0;
}
return out;
}
void ChatHandler::getPlainText(const std::string& in, std::string &out, std::vector<Triple> &links)
{
if (in.size() == 0)
return;
if (in[0] != '<' || in[in.size() - 1] != '>')
{
// It's a plain text message without HTML
out = in;
return;
}
bool ignore = false;
bool keep_link = false;
std::string last_six_chars;
unsigned int tag_start_index = 0;
Triple current_link;
bool onEscapeChar = false;
unsigned int escapeCharIndexStart = -1;
for(unsigned int i = 0; i < in.size(); ++i)
{
if(keep_link && in[i] == '"')
{
keep_link = false;
current_link.second = out.size();
}
if(last_six_chars == "href=\"")
{
keep_link = true;
current_link.first = out.size();
}
// "rising edge" sets mode to ignore
if(in[i] == '<')
{
tag_start_index = i;
ignore = true;
}
if(!ignore || keep_link)
out += in[i];
// "falling edge" resets mode to keep
if(in[i] == '>' && ignore) {
// leave ignore mode on, if it's a style tag
if (tag_start_index == 0 || tag_start_index + 6 > i || in.substr(tag_start_index, 6) != "<style") {
ignore = false;
tag_start_index = 0;
}
}
last_six_chars += in[i];
if(last_six_chars.size() > 6)
last_six_chars = last_six_chars.substr(1);
std::string a = "</a>";
if( current_link.first != -1
&& last_six_chars.size() >= a.size()
&& last_six_chars.substr(last_six_chars.size()-a.size()) == a)
{
// only allow these protocols
// we don't want for example javascript:alert(0)
std::string http = "http://";
std::string https = "https://";
std::string retroshare = "retroshare://";
if( out.substr(current_link.first, http.size()) == http
|| out.substr(current_link.first, https.size()) == https
|| out.substr(current_link.first, retroshare.size()) == retroshare)
{
current_link.third = out.size();
links.push_back(current_link);
}
current_link = Triple();
}
std::string br = "<br/>";
if(last_six_chars.size() >= br.size()
&& last_six_chars.substr(last_six_chars.size()-br.size()) == br)
{
out += "\n";
}
if (in[i] == '&') {
onEscapeChar = true;
escapeCharIndexStart = out.length();
}
if (ignore || keep_link) {
onEscapeChar = false;
escapeCharIndexStart = -1;
}
if ((in[i] == ';') && onEscapeChar) {
onEscapeChar = false;
bool escapeFound = true;
std::string escapeReplace = "";
//Keep only escape value to replace it
std::string escapeCharValue = out.substr(escapeCharIndexStart,
out.length() - escapeCharIndexStart - 1);
if (escapeCharValue[0] == '#') {
int escapedCharUTF8 = stringToDecUInt(escapeCharValue.substr(1));
std::ostringstream escapedSStream;
writeUTF8( escapedSStream, escapedCharUTF8);
escapeReplace = escapedSStream.str();
} else if (escapeCharValue == "euro") {
escapeReplace = "";
} else if (escapeCharValue == "nbsp") {
escapeReplace = " ";
} else if (escapeCharValue == "quot") {
escapeReplace = "\"";
} else if (escapeCharValue == "amp") {
escapeReplace = "&";
} else if (escapeCharValue == "lt") {
escapeReplace = "<";
} else if (escapeCharValue == "gt") {
escapeReplace = ">";
} else if (escapeCharValue == "iexcl") {
escapeReplace = "¡";
} else if (escapeCharValue == "cent") {
escapeReplace = "¢";
} else if (escapeCharValue == "pound") {
escapeReplace = "£";
} else if (escapeCharValue == "curren") {
escapeReplace = "¤";
} else if (escapeCharValue == "yen") {
escapeReplace = "¥";
} else if (escapeCharValue == "brvbar") {
escapeReplace = "¦";
} else if (escapeCharValue == "sect") {
escapeReplace = "§";
} else if (escapeCharValue == "uml") {
escapeReplace = "¨";
} else if (escapeCharValue == "copy") {
escapeReplace = "©";
} else if (escapeCharValue == "ordf") {
escapeReplace = "ª";
} else if (escapeCharValue == "not") {
escapeReplace = "¬";
} else if (escapeCharValue == "shy") {
escapeReplace = " ";//?
} else if (escapeCharValue == "reg") {
escapeReplace = "®";
} else if (escapeCharValue == "macr") {
escapeReplace = "¯";
} else if (escapeCharValue == "deg") {
escapeReplace = "°";
} else if (escapeCharValue == "plusmn") {
escapeReplace = "±";
} else if (escapeCharValue == "sup2") {
escapeReplace = "²";
} else if (escapeCharValue == "sup3") {
escapeReplace = "³";
} else if (escapeCharValue == "acute") {
escapeReplace = "´";
} else if (escapeCharValue == "micro") {
escapeReplace = "µ";
} else if (escapeCharValue == "para") {
escapeReplace = "";
} else if (escapeCharValue == "middot") {
escapeReplace = "·";
} else if (escapeCharValue == "cedil") {
escapeReplace = "¸";
} else if (escapeCharValue == "sup1") {
escapeReplace = "¹";
} else if (escapeCharValue == "ordm") {
escapeReplace = "º";
} else if (escapeCharValue == "raquo") {
escapeReplace = "»";
} else if (escapeCharValue == "frac14") {
escapeReplace = "¼";
} else if (escapeCharValue == "frac12") {
escapeReplace = "½";
} else if (escapeCharValue == "frac34") {
escapeReplace = "¾";
} else if (escapeCharValue == "iquest") {
escapeReplace = "¿";
} else if (escapeCharValue == "Agrave") {
escapeReplace = "À";
} else if (escapeCharValue == "Aacute") {
escapeReplace = "Á";
} else if (escapeCharValue == "Acirc") {
escapeReplace = "Â";
} else if (escapeCharValue == "Atilde") {
escapeReplace = "Ã";
} else if (escapeCharValue == "Auml") {
escapeReplace = "Ä";
} else if (escapeCharValue == "Aring") {
escapeReplace = "Å";
} else if (escapeCharValue == "AElig") {
escapeReplace = "Æ";
} else if (escapeCharValue == "Ccedil") {
escapeReplace = "Ç";
} else if (escapeCharValue == "Egrave") {
escapeReplace = "È";
} else if (escapeCharValue == "Eacute") {
escapeReplace = "É";
} else if (escapeCharValue == "Ecirc") {
escapeReplace = "Ê";
} else if (escapeCharValue == "Euml") {
escapeReplace = "Ë";
} else if (escapeCharValue == "Igrave") {
escapeReplace = "Ì";
} else if (escapeCharValue == "Iacute") {
escapeReplace = "Í";
} else if (escapeCharValue == "Icirc") {
escapeReplace = "Î";
} else if (escapeCharValue == "Iuml") {
escapeReplace = "Ï";
} else if (escapeCharValue == "ETH") {
escapeReplace = "Ð";
} else if (escapeCharValue == "Ntilde") {
escapeReplace = "Ñ";
} else if (escapeCharValue == "Ograve") {
escapeReplace = "Ò";
} else if (escapeCharValue == "Oacute") {
escapeReplace = "Ó";
} else if (escapeCharValue == "Ocirc") {
escapeReplace = "Ô";
} else if (escapeCharValue == "Otilde") {
escapeReplace = "Õ";
} else if (escapeCharValue == "Ouml") {
escapeReplace = "Ö";
} else if (escapeCharValue == "times") {
escapeReplace = "×";
} else if (escapeCharValue == "Oslash") {
escapeReplace = "Ø";
} else if (escapeCharValue == "Ugrave") {
escapeReplace = "Ù";
} else if (escapeCharValue == "Uacute") {
escapeReplace = "Ú";
} else if (escapeCharValue == "Ucirc") {
escapeReplace = "Û";
} else if (escapeCharValue == "Uuml") {
escapeReplace = "Ü";
} else if (escapeCharValue == "Yacute") {
escapeReplace = "Ý";
} else if (escapeCharValue == "THORN") {
escapeReplace = "Þ";
} else if (escapeCharValue == "szlig") {
escapeReplace = "ß";
} else if (escapeCharValue == "agrave") {
escapeReplace = "à";
} else if (escapeCharValue == "aacute") {
escapeReplace = "á";
} else if (escapeCharValue == "acirc") {
escapeReplace = "â";
} else if (escapeCharValue == "atilde") {
escapeReplace = "ã";
} else if (escapeCharValue == "auml") {
escapeReplace = "ä";
} else if (escapeCharValue == "aring") {
escapeReplace = "å";
} else if (escapeCharValue == "aelig") {
escapeReplace = "æ";
} else if (escapeCharValue == "ccedil") {
escapeReplace = "ç";
} else if (escapeCharValue == "egrave") {
escapeReplace = "è";
} else if (escapeCharValue == "eacute") {
escapeReplace = "é";
} else if (escapeCharValue == "ecirc") {
escapeReplace = "ê";
} else if (escapeCharValue == "euml") {
escapeReplace = "ë";
} else if (escapeCharValue == "igrave") {
escapeReplace = "ì";
} else if (escapeCharValue == "iacute") {
escapeReplace = "í";
} else if (escapeCharValue == "icirc") {
escapeReplace = "î";
} else if (escapeCharValue == "iuml") {
escapeReplace = "ï";
} else if (escapeCharValue == "eth") {
escapeReplace = "ð";
} else if (escapeCharValue == "ntilde") {
escapeReplace = "ñ";
} else if (escapeCharValue == "ograve") {
escapeReplace = "ò";
} else if (escapeCharValue == "oacute") {
escapeReplace = "ó";
} else if (escapeCharValue == "ocirc") {
escapeReplace = "ô";
} else if (escapeCharValue == "otilde") {
escapeReplace = "õ";
} else if (escapeCharValue == "ouml") {
escapeReplace = "ö";
} else if (escapeCharValue == "divide") {
escapeReplace = "÷";
} else if (escapeCharValue == "oslash") {
escapeReplace = "ø";
} else if (escapeCharValue == "ugrave") {
escapeReplace = "ù";
} else if (escapeCharValue == "uacute") {
escapeReplace = "ú";
} else if (escapeCharValue == "ucirc") {
escapeReplace = "û";
} else if (escapeCharValue == "uuml") {
escapeReplace = "ü";
} else if (escapeCharValue == "yacute") {
escapeReplace = "ý";
} else if (escapeCharValue == "thorn") {
escapeReplace = "þ";
} else {
escapeFound = false;
}
if (escapeFound) {
out = out.substr(0, escapeCharIndexStart-1);
out += escapeReplace;
}
}
}
}
void ChatHandler::locked_storeTypingInfo(const ChatId &chat_id, std::string status, RsGxsId lobby_gxs_id)
{
TypingLabelInfo& info = mTypingLabelInfo[chat_id];
info.timestamp = time(0);
info.status = status;
mStateTokenServer->replaceToken(info.state_token);
info.author_id = lobby_gxs_id;
}
void ChatHandler::handleWildcard(Request &/*req*/, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
for(std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.begin(); mit != mMsgs.end(); ++mit)
{
resp.mDataStream.getStreamToMember() << makeValue(mit->first.toStdString());
}
resp.setOk();
}
void ChatHandler::handleLobbies(Request &/*req*/, Response &resp)
{
tick();
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
{
uint32_t unread_msgs = 0;
ChatId chat_id(vit->id);
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(chat_id);
if(mit != mMsgs.end())
{
std::list<Msg>& msgs = mit->second;
for(std::list<Msg>::iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
if(!lit->read)
unread_msgs++;
}
resp.mDataStream.getStreamToMember() << *vit << makeKeyValueReference("unread_msg_count", unread_msgs);
}
resp.mStateToken = mLobbiesStateToken;
}
resp.setOk();
}
void ChatHandler::handleSubscribeLobby(Request &req, Response &resp)
{
ChatLobbyId id = 0;
RsGxsId gxs_id;
req.mStream << makeKeyValueReference("id", id) << makeKeyValueReference("gxs_id", gxs_id);
if(id == 0)
{
resp.setFail("Error: id must not be null");
return;
}
if(gxs_id.isNull())
{
resp.setFail("Error: gxs_id must not be null");
return;
}
if(mRsMsgs->joinVisibleChatLobby(id, gxs_id))
resp.setOk();
else
resp.setFail("lobby join failed. (See console for more info)");
}
void ChatHandler::handleUnsubscribeLobby(Request &req, Response &resp)
{
ChatLobbyId id = 0;
req.mStream << makeKeyValueReference("id", id);
mRsMsgs->unsubscribeChatLobby(id);
resp.setOk();
}
void ChatHandler::handleAutoSubsribeLobby(Request& req, Response& resp)
{
ChatLobbyId chatId = 0;
bool autosubsribe;
req.mStream << makeKeyValueReference("chatid", chatId) << makeKeyValueReference("autosubsribe", autosubsribe);
mRsMsgs->setLobbyAutoSubscribe(chatId, autosubsribe);
resp.setOk();
}
void ChatHandler::handleClearLobby(Request &req, Response &resp)
{
ChatLobbyId id = 0;
req.mStream << makeKeyValueReference("id", id);
if (id !=0) {
notifyChatCleared(ChatId(id));
} else {
//Is BroadCast
notifyChatCleared(ChatId("B"));
}
resp.setOk();
}
ResponseTask* ChatHandler::handleLobbyParticipants(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(!id.isLobbyId())
{
resp.setFail("Path element \""+req.mPath.top()+"\" is not a ChatLobbyId.");
return 0;
}
std::map<ChatLobbyId, LobbyParticipantsInfo>::const_iterator mit = mLobbyParticipantsInfos.find(id.toLobbyId());
if(mit == mLobbyParticipantsInfos.end())
{
resp.setFail("lobby not found");
return 0;
}
return new SendLobbyParticipantsTask(mRsIdentity, mit->second);
}
void ChatHandler::handleMessages(Request &req, Response &resp)
{
/* G10h4ck: Whithout this the request processing won't happen, copied from
* ChatHandler::handleLobbies, is this a work around or is the right way of
* doing it? */
tick();
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
// make response a list
resp.mDataStream.getStreamToMember();
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return;
}
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(id);
if(mit == mMsgs.end())
{
/* set state token, even if not found yet, maybe later messages arrive
* and then the chat id will be found */
resp.mStateToken = mMsgStateToken;
resp.setFail("chat with id=\""+req.mPath.top()+"\" not found");
return;
}
resp.mStateToken = mMsgStateToken;
handlePaginationRequest(req, resp, mit->second);
}
}
void ChatHandler::handleSendMessage(Request &req, Response &resp)
{
std::string chat_id;
std::string msg;
req.mStream << makeKeyValueReference("chat_id", chat_id)
<< makeKeyValueReference("msg", msg);
ChatId id(chat_id);
if(id.isNotSet())
{
resp.setFail("chat_id is invalid");
return;
}
if(mRsMsgs->sendChat(id, msg))
resp.setOk();
else
resp.setFail("failed to send message");
}
void ChatHandler::handleMarkChatAsRead(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return;
}
std::map<ChatId, std::list<Msg> >::iterator mit = mMsgs.find(id);
if(mit == mMsgs.end())
{
resp.setFail("chat not found. Maybe this chat does not have messages yet?");
return;
}
std::list<Msg>& msgs = mit->second;
for(std::list<Msg>::iterator lit = msgs.begin(); lit != msgs.end(); ++lit)
{
lit->read = true;
}
// lobby list contains unread msgs, so update it
if(id.isLobbyId())
mStateTokenServer->replaceToken(mLobbiesStateToken);
if(id.isPeerId() && mUnreadMsgNotify)
mUnreadMsgNotify->notifyUnreadMsgCountChanged(id.toPeerId(), 0);
mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
}
void ChatHandler::handleInfo(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return;
}
std::map<ChatId, ChatInfo>::iterator mit = mChatInfo.find(id);
if(mit == mChatInfo.end())
{
resp.setFail("chat not found.");
return;
}
resp.mDataStream << mit->second;
resp.setOk();
}
class SendTypingLabelInfo: public GxsResponseTask
{
public:
SendTypingLabelInfo(RsIdentity* identity, RsPeers* peers, ChatId id, const ChatHandler::TypingLabelInfo& info):
GxsResponseTask(identity), mState(BEGIN), mPeers(peers),mId(id), mInfo(info) {}
private:
enum State {BEGIN, WAITING_ID};
State mState;
RsPeers* mPeers;
ChatId mId;
ChatHandler::TypingLabelInfo mInfo;
protected:
void gxsDoWork(Request& /*req*/, Response& resp)
{
if(mState == BEGIN)
{
// lobby and distant require to fetch a gxs_id
if(mId.isLobbyId())
{
requestGxsId(mInfo.author_id);
}
else if(mId.isDistantChatId())
{
DistantChatPeerInfo dcpinfo ;
rsMsgs->getDistantChatStatus(mId.toDistantChatId(), dcpinfo);
requestGxsId(dcpinfo.to_id);
}
mState = WAITING_ID;
}
else
{
std::string name = "BUG: case not handled in SendTypingLabelInfo";
if(mId.isPeerId())
{
name = mPeers->getPeerName(mId.toPeerId());
}
else if(mId.isDistantChatId())
{
DistantChatPeerInfo dcpinfo ;
rsMsgs->getDistantChatStatus(mId.toDistantChatId(), dcpinfo);
name = getName(dcpinfo.to_id);
}
else if(mId.isLobbyId())
{
name = getName(mInfo.author_id);
}
else if(mId.isBroadcast())
{
name = mPeers->getPeerName(mId.broadcast_status_peer_id);
}
uint32_t ts = mInfo.timestamp;
resp.mDataStream << makeKeyValueReference("author_name", name)
<< makeKeyValueReference("timestamp", ts)
<< makeKeyValueReference("status_string", mInfo.status);
resp.mStateToken = mInfo.state_token;
resp.setOk();
done();
}
}
};
ResponseTask* ChatHandler::handleReceiveStatus(Request &req, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
ChatId id(req.mPath.top());
if(id.isNotSet())
{
resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
return 0;
}
std::map<ChatId, TypingLabelInfo>::iterator mit = mTypingLabelInfo.find(id);
if(mit == mTypingLabelInfo.end())
{
locked_storeTypingInfo(id, "");
mit = mTypingLabelInfo.find(id);
}
return new SendTypingLabelInfo(mRsIdentity, mRsPeers, id, mit->second);
}
void ChatHandler::handleSendStatus(Request &req, Response &resp)
{
std::string chat_id;
std::string status;
req.mStream << makeKeyValueReference("chat_id", chat_id)
<< makeKeyValueReference("status", status);
ChatId id(chat_id);
if(id.isNotSet())
{
resp.setFail("chat_id is invalid");
return;
}
mRsMsgs->sendStatusString(id, status);
resp.setOk();
}
void ChatHandler::handleUnreadMsgs(Request &/*req*/, Response &resp)
{
RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
resp.mDataStream.getStreamToMember();
for( std::map<ChatId, std::list<Msg> >::const_iterator mit = mMsgs.begin();
mit != mMsgs.end(); ++mit )
{
uint32_t count = 0;
for( std::list<Msg>::const_iterator lit = mit->second.begin();
lit != mit->second.end(); ++lit ) if(!lit->read) ++count;
std::map<ChatId, ChatInfo>::iterator mit2 = mChatInfo.find(mit->first);
if(mit2 == mChatInfo.end())
std::cerr << "Error in ChatHandler::handleUnreadMsgs(): ChatInfo not found. It is weird if this happens. Normally it should not happen." << std::endl;
if(count && (mit2 != mChatInfo.end()))
{
resp.mDataStream.getStreamToMember()
#warning @deprecated using "id" as key can cause problems in some JS based \
languages like Qml @see chat_id instead
<< makeKeyValue("id", mit->first.toStdString())
<< makeKeyValue("chat_id", mit->first.toStdString())
<< makeKeyValueReference("unread_count", count)
<< mit2->second;
}
}
resp.mStateToken = mUnreadMsgsStateToken;
resp.setOk();
}
void ChatHandler::handleInitiateDistantChatConnexion(Request& req, Response& resp)
{
std::string own_gxs_hex, remote_gxs_hex;
req.mStream << makeKeyValueReference("own_gxs_hex", own_gxs_hex)
<< makeKeyValueReference("remote_gxs_hex", remote_gxs_hex);
RsGxsId sender_id(own_gxs_hex);
if(sender_id.isNull())
{
resp.setFail("own_gxs_hex is invalid");
return;
}
RsGxsId receiver_id(remote_gxs_hex);
if(receiver_id.isNull())
{
resp.setFail("remote_gxs_hex is invalid");
return;
}
DistantChatPeerId distant_chat_id;
uint32_t error_code;
if(mRsMsgs->initiateDistantChatConnexion(receiver_id, sender_id,
distant_chat_id, error_code))
resp.setOk();
else resp.setFail("Failed to initiate distant chat");
ChatId chat_id(distant_chat_id);
resp.mDataStream << makeKeyValue("chat_id", chat_id.toStdString())
<< makeKeyValueReference("error_code", error_code);
}
void ChatHandler::handleDistantChatStatus(Request& req, Response& resp)
{
std::string distant_chat_hex;
req.mStream << makeKeyValueReference("chat_id", distant_chat_hex);
ChatId id(distant_chat_hex);
DistantChatPeerInfo info;
if(mRsMsgs->getDistantChatStatus(id.toDistantChatId(), info)) resp.setOk();
else resp.setFail("Failed to get status for distant chat");
resp.mDataStream << makeKeyValue("own_gxs_hex", info.own_id.toStdString())
<< makeKeyValue("remote_gxs_hex", info.to_id.toStdString())
<< makeKeyValue("chat_id", info.peer_id.toStdString())
<< makeKeyValue("status", info.status);
}
void ChatHandler::handleCloseDistantChatConnexion(Request& req, Response& resp)
{
std::string distant_chat_hex;
req.mStream << makeKeyValueReference("distant_chat_hex", distant_chat_hex);
DistantChatPeerId chat_id(distant_chat_hex);
if (mRsMsgs->closeDistantChatConnexion(chat_id)) resp.setOk();
else resp.setFail("Failed to close distant chat");
}
void ChatHandler::handleCreateLobby(Request& req, Response& resp)
{
std::set<RsPeerId> invited_identites;
std::string lobby_name;
std::string lobby_topic;
std::string gxs_id;
req.mStream << makeKeyValueReference("lobby_name", lobby_name);
req.mStream << makeKeyValueReference("lobby_topic", lobby_topic);
req.mStream << makeKeyValueReference("gxs_id", gxs_id);
RsGxsId gxsId(gxs_id);
bool lobby_public;
bool pgp_signed;
req.mStream << makeKeyValueReference("lobby_public", lobby_public);
req.mStream << makeKeyValueReference("pgp_signed", pgp_signed);
ChatLobbyFlags lobby_flags;
if(lobby_public)
lobby_flags |= RS_CHAT_LOBBY_FLAGS_PUBLIC;
if(pgp_signed)
lobby_flags |= RS_CHAT_LOBBY_FLAGS_PGP_SIGNED;
mRsMsgs->createChatLobby(lobby_name, gxsId, lobby_topic, invited_identites, lobby_flags);
tick();
resp.setOk();
}
} // namespace resource_api