/* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Swift { typedef std::pair StringMUCOccupantPair; MUCImpl::MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry) { scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUCImpl::handleIncomingPresence, this, _1)); } MUCImpl::~MUCImpl() { } //FIXME: discover reserved nickname /** * Join the MUC with default context. */ void MUCImpl::joinAs(const std::string &nick) { joinSince_ = boost::posix_time::not_a_date_time; internalJoin(nick); } /** * Set the password used for entering the room. */ void MUCImpl::setPassword(const boost::optional& newPassword) { password = newPassword; } /** * Join the MUC with context since date. */ void MUCImpl::joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) { joinSince_ = since; internalJoin(nick); } std::map MUCImpl::getOccupants() const { return occupants; } bool MUCImpl::isEqualExceptID(const Presence& lhs, const Presence& rhs) { bool isEqual = false; if (lhs.getFrom() == rhs.getFrom() && lhs.getTo() == rhs.getTo() && lhs.getStatus() == rhs.getStatus() && lhs.getShow() == rhs.getShow()) { CapsInfo::ref lhsCaps = lhs.getPayload(); CapsInfo::ref rhsCaps = rhs.getPayload(); if (!!lhsCaps && !!rhsCaps) { isEqual = (*lhsCaps == *rhsCaps); } else { isEqual = (!lhsCaps && !rhsCaps); } } return isEqual; } void MUCImpl::internalJoin(const std::string &nick) { //TODO: history request joinComplete_ = false; joinSucceeded_ = false; mucRegistry->addMUC(getJID()); ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick); Presence::ref joinPresence = presenceSender->getLastSentUndirectedPresence() ? (*presenceSender->getLastSentUndirectedPresence())->clone() : std::make_shared(); assert(joinPresence->getType() == Presence::Available); joinPresence->setTo(ownMUCJID); MUCPayload::ref mucPayload = std::make_shared(); if (joinSince_ != boost::posix_time::not_a_date_time) { mucPayload->setSince(joinSince_); } if (password) { mucPayload->setPassword(*password); } joinPresence->addPayload(mucPayload); joinRequestPresence_ = joinPresence; presenceSender->sendPresence(joinPresence); } void MUCImpl::changeNickname(const std::string& newNickname) { Presence::ref changeNicknamePresence = std::make_shared(); changeNicknamePresence->setTo(ownMUCJID.toBare().toString() + std::string("/") + newNickname); presenceSender->sendPresence(changeNicknamePresence); } void MUCImpl::part() { presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); mucRegistry->removeMUC(getJID()); } void MUCImpl::handleUserLeft(LeavingType type) { std::map::iterator i = occupants.find(ownMUCJID.getResource()); if (i != occupants.end()) { MUCOccupant me = i->second; occupants.erase(i); onOccupantLeft(me, type, ""); } occupants.clear(); joinComplete_ = false; joinSucceeded_ = false; isUnlocked_ = false; presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); } void MUCImpl::handleIncomingPresence(Presence::ref presence) { if (!isFromMUC(presence->getFrom())) { return; } MUCUserPayload::ref mucPayload; for (MUCUserPayload::ref payload : presence->getPayloads()) { if (!payload->getItems().empty() || !payload->getStatusCodes().empty()) { mucPayload = payload; } } // On the first incoming presence, check if our join has succeeded // (i.e. we start getting non-error presence from the MUC) or not if (!joinSucceeded_) { if (presence->getType() == Presence::Error) { onJoinFailed(presence->getPayload()); return; } else if (presence->getType() == Presence::Available) { joinSucceeded_ = true; presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); if (presenceSender->getLastSentUndirectedPresence() && !isEqualExceptID(**(presenceSender->getLastSentUndirectedPresence()), *joinRequestPresence_)) { // our presence changed between join request and join complete, send current presence to MUC Presence::ref latestPresence = std::make_shared(**presenceSender->getLastSentUndirectedPresence()); latestPresence->setTo(ownMUCJID); presenceSender->sendPresence(latestPresence); } } else if (presence->getType() == Presence::Unavailable) { onJoinFailed(std::shared_ptr()); return; } } std::string nick = presence->getFrom().getResource(); if (nick.empty()) { return; } MUCOccupant::Role role(MUCOccupant::NoRole); MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation); boost::optional realJID; if (mucPayload && mucPayload->getItems().size() > 0) { role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole; affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation; realJID = mucPayload->getItems()[0].realJID; } //100 is non-anonymous //TODO: 100 may also be specified in a //170 is room logging to http //TODO: Nick changes if (presence->getType() == Presence::Unavailable) { LeavingType type = LeavePart; boost::optional newNickname; if (mucPayload) { if (std::dynamic_pointer_cast(mucPayload->getPayload())) { type = LeaveDestroy; } else { for (MUCUserPayload::StatusCode status : mucPayload->getStatusCodes()) { if (status.code == 307) { type = LeaveKick; } else if (status.code == 301) { type = LeaveBan; } else if (status.code == 321) { type = LeaveNotMember; } else if (status.code == 303) { if (mucPayload->getItems().size() == 1) { newNickname = mucPayload->getItems()[0].nick; } } } } } if (newNickname) { std::map::iterator i = occupants.find(nick); if (i != occupants.end()) { MUCOccupant occupant = i->second; occupants.erase(i); occupant.setNick(newNickname.get()); occupants.insert(std::make_pair(newNickname.get(), occupant)); onOccupantNicknameChanged(nick, newNickname.get()); } } else { if (presence->getFrom() == ownMUCJID) { handleUserLeft(type); return; } else { std::map::iterator i = occupants.find(nick); if (i != occupants.end()) { //TODO: part type MUCOccupant occupant = i->second; occupants.erase(i); onOccupantLeft(occupant, type, ""); } } } } else if (presence->getType() == Presence::Available) { std::map::iterator it = occupants.find(nick); MUCOccupant occupant(nick, role, affiliation); bool isJoin = true; if (realJID) { occupant.setRealJID(realJID.get()); } if (it != occupants.end()) { isJoin = false; MUCOccupant oldOccupant = it->second; if (oldOccupant.getRole() != role) { onOccupantRoleChanged(nick, occupant, oldOccupant.getRole()); } if (oldOccupant.getAffiliation() != affiliation) { onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation()); } occupants.erase(it); } std::pair::iterator, bool> result = occupants.insert(std::make_pair(nick, occupant)); if (isJoin) { onOccupantJoined(result.first->second); } onOccupantPresenceChange(presence); if (mucPayload && !joinComplete_) { bool isLocked = false; for (MUCUserPayload::StatusCode status : mucPayload->getStatusCodes()) { if (status.code == 110) { /* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */ joinComplete_ = true; if (ownMUCJID != presence->getFrom()) { presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); ownMUCJID = presence->getFrom(); presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); } } // MUC status 201: a new room has been created if (status.code == 201) { isLocked = true; /* Room is created and locked */ /* Currently deal with this by making an instant room */ if (ownMUCJID != presence->getFrom()) { presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); ownMUCJID = presence->getFrom(); presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); } if (createAsReservedIfNew) { unlocking = true; requestConfigurationForm(); } else { // Accept default room configuration and create an instant room http://xmpp.org/extensions/xep-0045.html#createroom-instant MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence); mucPayload->setPayload(std::make_shared
(Form::SubmitType)); std::shared_ptr< GenericRequest > request = std::make_shared< GenericRequest >(IQ::Set, getJID(), mucPayload, iqRouter_); request->onResponse.connect(boost::bind(&MUCImpl::handleCreationConfigResponse, this, _1, _2)); request->send(); } } } if (joinComplete_ && !isLocked) { assert(hasOccupant(getOwnNick())); onJoinComplete(getOwnNick()); } if (!isLocked && !isUnlocked_ && (presence->getFrom() == ownMUCJID)) { isUnlocked_ = true; onUnlocked(); } } } } void MUCImpl::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPayload::ref error) { unlocking = false; if (error) { presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence); onJoinFailed(error); } else { onJoinComplete(getOwnNick()); /* Previously, this wasn't needed here, as the presence duplication bug caused an emit elsewhere. */ isUnlocked_ = true; onUnlocked(); } } bool MUCImpl::hasOccupant(const std::string& nick) { return occupants.find(nick) != occupants.end(); } const MUCOccupant& MUCImpl::getOccupant(const std::string& nick) { return occupants.find(nick)->second; } void MUCImpl::kickOccupant(const JID& jid) { changeOccupantRole(jid, MUCOccupant::NoRole); } /** * Call with the room JID, not the real JID. */ void MUCImpl::changeOccupantRole(const JID& jid, MUCOccupant::Role role) { MUCAdminPayload::ref mucPayload = std::make_shared(); MUCItem item; item.role = role; item.nick = jid.getResource(); mucPayload->addItem(item); std::shared_ptr > request = std::make_shared >(IQ::Set, getJID(), mucPayload, iqRouter_); request->onResponse.connect(boost::bind(&MUCImpl::handleOccupantRoleChangeResponse, this, _1, _2, jid, role)); request->send(); } void MUCImpl::handleOccupantRoleChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Role role) { if (error) { onRoleChangeFailed(error, jid, role); } } void MUCImpl::requestAffiliationList(MUCOccupant::Affiliation affiliation) { MUCAdminPayload::ref mucPayload = std::make_shared(); MUCItem item; item.affiliation = affiliation; mucPayload->addItem(item); std::shared_ptr > request = std::make_shared< GenericRequest >(IQ::Get, getJID(), mucPayload, iqRouter_); request->onResponse.connect(boost::bind(&MUCImpl::handleAffiliationListResponse, this, _1, _2, affiliation)); request->send(); } /** * Must be called with the real JID, not the room JID. */ void MUCImpl::changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) { MUCAdminPayload::ref mucPayload = std::make_shared(); MUCItem item; item.affiliation = affiliation; item.realJID = jid.toBare(); mucPayload->addItem(item); std::shared_ptr > request = std::make_shared >(IQ::Set, getJID(), mucPayload, iqRouter_); request->onResponse.connect(boost::bind(&MUCImpl::handleAffiliationChangeResponse, this, _1, _2, jid, affiliation)); request->send(); } void MUCImpl::handleAffiliationListResponse(MUCAdminPayload::ref payload, ErrorPayload::ref error, MUCOccupant::Affiliation affiliation) { if (error) { onAffiliationListFailed(error); } else { std::vector jids; for (MUCItem item : payload->getItems()) { if (item.realJID) { jids.push_back(*item.realJID); } } onAffiliationListReceived(affiliation, jids); } } void MUCImpl::handleAffiliationChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Affiliation affiliation) { if (error) { onAffiliationChangeFailed(error, jid, affiliation); } } void MUCImpl::changeSubject(const std::string& subject) { Message::ref message = std::make_shared(); message->setSubject(subject); message->setType(Message::Groupchat); message->setTo(ownMUCJID.toBare()); stanzaChannel->sendMessage(message); } void MUCImpl::requestConfigurationForm() { MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); std::shared_ptr > request = std::make_shared >(IQ::Get, getJID(), mucPayload, iqRouter_); request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationFormReceived, this, _1, _2)); request->send(); } void MUCImpl::cancelConfigureRoom() { MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); mucPayload->setPayload(std::make_shared(Form::CancelType)); std::shared_ptr > request = std::make_shared >(IQ::Set, getJID(), mucPayload, iqRouter_); request->send(); } void MUCImpl::handleConfigurationFormReceived(MUCOwnerPayload::ref payload, ErrorPayload::ref error) { Form::ref form; if (payload) { form = payload->getForm(); } if (error || !form) { onConfigurationFailed(error); } else { onConfigurationFormReceived(form); } } void MUCImpl::handleConfigurationResultReceived(MUCOwnerPayload::ref /*payload*/, ErrorPayload::ref error) { if (error) { onConfigurationFailed(error); } } void MUCImpl::configureRoom(Form::ref form) { MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload()); mucPayload->setPayload(form); std::shared_ptr > request = std::make_shared >(IQ::Set, getJID(), mucPayload, iqRouter_); if (unlocking) { request->onResponse.connect(boost::bind(&MUCImpl::handleCreationConfigResponse, this, _1, _2)); } else { request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationResultReceived, this, _1, _2)); } request->send(); } void MUCImpl::destroyRoom() { MUCOwnerPayload::ref mucPayload = std::make_shared(); MUCDestroyPayload::ref mucDestroyPayload = std::make_shared(); mucPayload->setPayload(mucDestroyPayload); std::shared_ptr< GenericRequest > request = std::make_shared >(IQ::Set, getJID(), mucPayload, iqRouter_); request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationResultReceived, this, _1, _2)); request->send(); } void MUCImpl::invitePerson(const JID& person, const std::string& reason, bool isImpromptu, bool isReuseChat) { Message::ref message = std::make_shared(); message->setTo(person); message->setType(Message::Normal); MUCInvitationPayload::ref invite = std::make_shared(); invite->setReason(reason); invite->setJID(ownMUCJID.toBare()); invite->setIsImpromptu(isImpromptu); invite->setIsContinuation(isReuseChat); message->addPayload(invite); stanzaChannel->sendMessage(message); } //TODO: Invites(direct/mediated) //TODO: requesting membership //TODO: get member list //TODO: request voice //TODO: moderator use cases //TODO: Admin use cases //TODO: Owner use cases }