From 86aad702d1f2e831c8e27bbe4ca1402626e4c542 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Fri, 11 Nov 2011 16:01:32 +0100
Subject: Message Receipts (XEP-0184) support for 1-to-1 conversations
 (including 1-to-1 MUC).

Warn icon from already existing theme. Check icon from Wikipedia. See Swift/resources/icons/license_info.txt for details.

License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php

diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index a3d9fb5..9a56300 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -16,7 +16,6 @@
 #include <Swiften/Chat/ChatStateNotifier.h>
 #include <Swiften/Chat/ChatStateTracker.h>
 #include <Swiften/Client/StanzaChannel.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 #include <Swiften/Client/NickResolver.h>
 #include <Swift/Controllers/XMPPEvents/EventController.h>
@@ -26,15 +25,19 @@
 #include <Swiften/Base/foreach.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h>
 
+#include <Swiften/Base/Log.h>
 
 namespace Swift {
 	
 /**
  * The controller does not gain ownership of the stanzaChannel, nor the factory.
  */
-ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream) {
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts)
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -70,7 +73,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1));
 	chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1));
 	handleBareJIDCapsChanged(toJID_);
-
+	eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1));
 }
 
 void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) {
@@ -80,6 +83,7 @@ void ChatController::handleContactNickChanged(const JID& jid, const std::string&
 }
 
 ChatController::~ChatController() {
+	eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1));
 	nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
 	delete chatStateNotifier_;
 	delete chatStateTracker_;
@@ -93,9 +97,17 @@ void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
 		} else {
 			chatWindow_->setCorrectionEnabled(ChatWindow::No);
 		}
+		if (disco->hasFeature(DiscoInfo::MessageDeliveryReceiptsFeature)) {
+			contactSupportsReceipts_ = ChatWindow::Yes;
+		} else {
+			contactSupportsReceipts_ = ChatWindow::No;
+		}
 	} else {
+		SWIFT_LOG(debug) << "No disco info :(" << std::endl;
 		chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
+		contactSupportsReceipts_ = ChatWindow::Maybe;
 	}
+	checkForDisplayingDisplayReceiptsAlert();
 }
 
 void ChatController::setToJID(const JID& jid) {
@@ -129,6 +141,21 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
 	}
 	chatStateTracker_->handleMessageReceived(message);
 	chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
+
+	if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) {
+		SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl;
+		if (requestedReceipts_.find(receipt->getReceivedID()) != requestedReceipts_.end()) {
+			chatWindow_->setMessageReceiptState(requestedReceipts_[receipt->getReceivedID()], ChatWindow::ReceiptReceived);
+			requestedReceipts_.erase(receipt->getReceivedID());
+		}
+	} else if (message->getPayload<DeliveryReceiptRequest>()) {
+		if (receivingPresenceFromUs_) {
+			boost::shared_ptr<Message> receiptMessage = boost::make_shared<Message>();
+			receiptMessage->setTo(toJID_);
+			receiptMessage->addPayload(boost::make_shared<DeliveryReceipt>(message->getID()));
+			stanzaChannel_->sendMessage(receiptMessage);
+		}
+	}
 }
 
 void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
@@ -138,6 +165,30 @@ void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> m
 
 void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
 	chatStateNotifier_->addChatStateRequest(message);
+	if (userWantsReceipts_ && (contactSupportsReceipts_ != ChatWindow::No) && message) {
+		message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
+	}
+}
+
+void ChatController::setContactIsReceivingPresence(bool isReceivingPresence) {
+	receivingPresenceFromUs_ = isReceivingPresence;
+}
+
+void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+	if (boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> toggleAllowReceipts = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event)) {
+		userWantsReceipts_ = toggleAllowReceipts->getEnabled();
+		checkForDisplayingDisplayReceiptsAlert();
+	}
+}
+
+void ChatController::checkForDisplayingDisplayReceiptsAlert() {
+	if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) {
+		chatWindow_->setAlert("This chat doesn't support delivery receipts.");
+	} else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) {
+		chatWindow_->setAlert("This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.");
+	} else {
+		chatWindow_->cancelAlert();
+	}
 }
 
 void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
@@ -148,10 +199,17 @@ void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<
 	} else {
 		myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
 	}
+
 	if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
 		chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
 		unackedStanzas_[sentStanza] = myLastMessageUIID_;
 	}
+
+	if (sentStanza->getPayload<DeliveryReceiptRequest>()) {
+		requestedReceipts_[sentStanza->getID()] = myLastMessageUIID_;
+		chatWindow_->setMessageReceiptState(myLastMessageUIID_, ChatWindow::ReceiptRequested);
+	}
+
 	lastWasPresence_ = false;
 	chatStateNotifier_->userSentMessage();
 }
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 2531adb..9c01923 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -11,6 +11,8 @@
 #include <map>
 #include <string>
 
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+
 namespace Swift {
 	class AvatarManager;
 	class ChatStateNotifier;
@@ -18,14 +20,16 @@ namespace Swift {
 	class NickResolver;
 	class EntityCapsProvider;
 	class FileTransferController;
+	class UIEvent;
 
 	class ChatController : public ChatControllerBase {
 		public:
-			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider);
+			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts);
 			virtual ~ChatController();
 			virtual void setToJID(const JID& jid);
 			virtual void setOnline(bool online);
 			virtual void handleNewFileTransferController(FileTransferController* ftc);
+			virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/);
 
 		private:
 			void handlePresenceChange(boost::shared_ptr<Presence> newPresence);
@@ -47,6 +51,9 @@ namespace Swift {
 			void handleFileTransferAccept(std::string /* id */, std::string /* filename */);
 			void handleSendFileRequest(std::string filename);
 
+			void handleUIEvent(boost::shared_ptr<UIEvent> event);
+			void checkForDisplayingDisplayReceiptsAlert();
+
 		private:
 			NickResolver* nickResolver_;
 			ChatStateNotifier* chatStateNotifier_;
@@ -56,9 +63,13 @@ namespace Swift {
 			bool lastWasPresence_;
 			std::string lastStatusChangeString_;
 			std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_;
+			std::map<std::string, std::string> requestedReceipts_;
 			StatusShow::Type lastShownStatus_;
 			UIEventStream* eventStream_;
 
+			ChatWindow::Tristate contactSupportsReceipts_;
+			bool receivingPresenceFromUs_;
+			bool userWantsReceipts_;
 			std::map<std::string, FileTransferController*> ftControllers;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index a857f3d..f1ecfe8 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -54,6 +54,7 @@ namespace Swift {
 			int getUnreadCount();
 			const JID& getToJID() {return toJID_;}
 			void handleCapsChanged(const JID& jid);
+
 		protected:
 			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider);
 
@@ -71,6 +72,7 @@ namespace Swift {
 			virtual void dayTicked() {};
 			virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
 			std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
+			virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {}
 
 		private:
 			IDGenerator idGenerator_;
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index e648f20..8111c2b 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -21,6 +21,7 @@
 #include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
 #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
 #include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h>
 #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
 #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
@@ -28,12 +29,15 @@
 #include <Swiften/Client/NickResolver.h>
 #include <Swiften/MUC/MUCManager.h>
 #include <Swiften/Elements/ChatState.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
 #include <Swiften/MUC/MUCBookmarkManager.h>
 #include <Swift/Controllers/FileTransfer/FileTransferController.h>
 #include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
 #include <Swift/Controllers/ProfileSettingsProvider.h>
 #include <Swiften/Avatars/AvatarManager.h>
 #include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swiften/Roster/XMPPRoster.h>
 
 namespace Swift {
 
@@ -61,6 +65,7 @@ ChatsManager::ChatsManager(
 		MUCSearchWindowFactory* mucSearchWindowFactory,
 		ProfileSettingsProvider* settings,
 		FileTransferOverview* ftOverview,
+		XMPPRoster* roster,
 		bool eagleMode) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
@@ -69,6 +74,7 @@ ChatsManager::ChatsManager(
 			entityCapsProvider_(entityCapsProvider), 
 			mucManager(mucManager),
 			ftOverview_(ftOverview),
+			roster_(roster),
 			eagleMode_(eagleMode) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
@@ -95,11 +101,19 @@ ChatsManager::ChatsManager(
 	mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings);
 	mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1));
 	ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1));
+	roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
+	roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
+	roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
+	roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this));
 	setupBookmarks();
 	loadRecents();
 }
 
 ChatsManager::~ChatsManager() {
+	roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1));
+	roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1));
+	roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1));
+	roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this));
 	delete joinMUCWindow_;
 	foreach (JIDChatControllerPair controllerPair, chatControllers_) {
 		delete controllerPair.second;
@@ -136,6 +150,39 @@ void ChatsManager::handleClearRecentsRequested() {
 	handleUnreadCountChanged(NULL);
 }
 
+void ChatsManager::handleJIDAddedToRoster(const JID &jid) {
+	updatePresenceReceivingStateOnChatController(jid);
+}
+
+void ChatsManager::handleJIDRemovedFromRoster(const JID &jid) {
+	updatePresenceReceivingStateOnChatController(jid);
+}
+
+void ChatsManager::handleJIDUpdatedInRoster(const JID &jid) {
+	updatePresenceReceivingStateOnChatController(jid);
+}
+
+void ChatsManager::handleRosterCleared() {
+	/*	Setting that all chat controllers aren't receiving presence anymore;
+		including MUC 1-to-1 chats due to the assumtion that this handler
+		is only called on log out. */
+	foreach(JIDChatControllerPair pair, chatControllers_) {
+		pair.second->setContactIsReceivingPresence(false);
+	}
+}
+
+void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) {
+	ChatController* controller = getChatControllerIfExists(jid);
+	if (controller) {
+		if (!mucRegistry_->isMUC(jid.toBare())) {
+			RosterItemPayload::Subscription subscription = roster_->getSubscriptionStateForJID(jid);
+			controller->setContactIsReceivingPresence(subscription == RosterItemPayload::From || subscription == RosterItemPayload::Both);
+		} else {
+			controller->setContactIsReceivingPresence(true);
+		}
+	}
+}
+
 void ChatsManager::loadRecents() {
 	std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
 	std::vector<std::string> recents;
@@ -316,6 +363,11 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 		mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark());
 		return;
 	}
+	boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> toggleRequestDeliveryReceipsEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event);
+	if (toggleRequestDeliveryReceipsEvent) {
+		userWantsReceipts_ = toggleRequestDeliveryReceipsEvent->getEnabled();
+		return;
+	}
 
 	boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event);
 	if (editMUCBookmarkEvent) {
@@ -436,11 +488,12 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)
 
 ChatController* ChatsManager::createNewChatController(const JID& contact) {
 	assert(chatControllers_.find(contact) == chatControllers_.end());
-	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_);
+	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_);
 	chatControllers_[contact] = controller;
 	controller->setAvailableServerFeatures(serverDiscoInfo_);
 	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
 	controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+	updatePresenceReceivingStateOnChatController(contact);
 	return controller;
 }
 
@@ -522,7 +575,7 @@ void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
 	JID jid = message->getFrom();
 	boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
 	bool isInvite = message->getPayload<MUCInvitationPayload>();
-	if (!event->isReadable() && !message->getPayload<ChatState>() && !isInvite && !message->hasSubject()) {
+	if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !message->hasSubject()) {
 		return;
 	}
 
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 5d8d555..0c7f492 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -45,10 +45,11 @@ namespace Swift {
 	class MUCSearchController;
 	class FileTransferOverview;
 	class FileTransferController;
+	class XMPPRoster;
 	
 	class ChatsManager {
 		public:
-			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview, bool eagleMode);
+			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -81,6 +82,12 @@ namespace Swift {
 			void handleUnreadCountChanged(ChatControllerBase* controller);
 			void handleAvatarChanged(const JID& jid);
 			void handleClearRecentsRequested();
+			void handleJIDAddedToRoster(const JID&);
+			void handleJIDRemovedFromRoster(const JID&);
+			void handleJIDUpdatedInRoster(const JID&);
+			void handleRosterCleared();
+
+			void updatePresenceReceivingStateOnChatController(const JID&);
 
 			ChatController* getChatControllerOrFindAnother(const JID &contact);
 			ChatController* createNewChatController(const JID &contact);
@@ -115,6 +122,8 @@ namespace Swift {
 			std::list<ChatListWindow::Chat> recentChats_;
 			ProfileSettingsProvider* profileSettings_;
 			FileTransferOverview* ftOverview_;
+			XMPPRoster* roster_;
 			bool eagleMode_;
+			bool userWantsReceipts_;
 	};
 }
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index df519e8..edb431a 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -43,8 +43,11 @@
 #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
 #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h"
 #include <Swift/Controllers/ProfileSettingsProvider.h>
 #include "Swift/Controllers/FileTransfer/FileTransferOverview.h"
+#include "Swiften/Elements/DeliveryReceiptRequest.h"
+#include "Swiften/Elements/DeliveryReceipt.h"
 #include <Swiften/Base/Algorithm.h>
 
 using namespace Swift;
@@ -63,6 +66,10 @@ class ChatsManagerTest : public CppUnit::TestFixture {
 	CPPUNIT_TEST(testUnbindRebind);
 	CPPUNIT_TEST(testNoDuplicateUnbind);
 	CPPUNIT_TEST(testThreeMUCWindows);
+	CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnRemoveFromRoster);
+	CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnAddToRoster);
+	CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth);
+	CPPUNIT_TEST(testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom);
 	CPPUNIT_TEST_SUITE_END();
 	
 public:
@@ -93,8 +100,9 @@ public:
 		chatListWindow_ = new MockChatListWindow();
 		ftManager_ = new DummyFileTransferManager();
 		ftOverview_ = new FileTransferOverview(ftManager_);
+
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, false);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false);
 
 		avatarManager_ = new NullAvatarManager();
 		manager_->setAvatarManager(avatarManager_);
@@ -335,11 +343,104 @@ public:
 		manager_->handleIncomingMessage(message2b);
 		CPPUNIT_ASSERT_EQUAL(body2b, window1->lastMessageBody_);
 	}
-	
+
+	/**
+	 *	Test that ChatController doesn't send receipts anymore after removal of the contact from the roster.
+	 */
+	void testChatControllerPresenceAccessUpdatedOnRemoveFromRoster() {
+		JID messageJID("testling@test.com/resource1");
+		xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
+
+		MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+		uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true)));
+
+		boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+		manager_->handleIncomingMessage(message);
+		Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
+		CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+		CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
+
+		xmppRoster_->removeContact(messageJID);
+
+		message->setID("2");
+		manager_->handleIncomingMessage(message);
+		CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+	}
+
+	/**
+	 *	Test that ChatController sends receipts after the contact has been added to the roster.
+	 */
+	void testChatControllerPresenceAccessUpdatedOnAddToRoster() {
+		JID messageJID("testling@test.com/resource1");
+
+		MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+		uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true)));
+
+		boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+		manager_->handleIncomingMessage(message);
+
+		CPPUNIT_ASSERT_EQUAL((size_t)0, stanzaChannel_->sentStanzas.size());
+
+		xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), RosterItemPayload::Both);
+		message->setID("2");
+		manager_->handleIncomingMessage(message);
+
+		CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+		Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
+		CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
+	}
+
+	/**
+	 *	Test that ChatController sends receipts if requested after change from subscription state To to subscription state Both.
+	 */
+	void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToBoth() {
+		testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::Both);
+	}
+
+	/**
+	 *	Test that ChatController sends receipts if requested after change from subscription state To to subscription state From.
+	 */
+	void testChatControllerPresenceAccessUpdatedOnSubscriptionChangeToFrom() {
+		testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::To, RosterItemPayload::From);
+	}
+
+	void testhelperChatControllerPresenceAccessUpdatedOnSubscriptionChangeReceiptsAllowed(RosterItemPayload::Subscription from, RosterItemPayload::Subscription to) {
+		JID messageJID("testling@test.com/resource1");
+		xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), from);
+
+		MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window);
+		uiEventStream_->send(boost::shared_ptr<UIEvent>(new ToggleRequestDeliveryReceiptsUIEvent(true)));
+
+		boost::shared_ptr<Message> message = makeDeliveryReceiptTestMessage(messageJID, "1");
+		manager_->handleIncomingMessage(message);
+
+		CPPUNIT_ASSERT_EQUAL((size_t)0, stanzaChannel_->sentStanzas.size());
+
+		xmppRoster_->addContact(messageJID, "foo", std::vector<std::string>(), to);
+		message->setID("2");
+		manager_->handleIncomingMessage(message);
+
+		CPPUNIT_ASSERT_EQUAL((size_t)1, stanzaChannel_->sentStanzas.size());
+		Stanza::ref stanzaContactOnRoster = stanzaChannel_->getStanzaAtIndex<Stanza>(0);
+		CPPUNIT_ASSERT(stanzaContactOnRoster->getPayload<DeliveryReceipt>() != 0);
+	}
+
+private:
+	boost::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) {
+		boost::shared_ptr<Message> message = boost::make_shared<Message>();
+		message->setFrom(from);
+		message->setID(id);
+		message->addPayload(boost::make_shared<DeliveryReceiptRequest>());
+		return message;
+	}
+
 private:
 	JID jid_;
 	ChatsManager* manager_;
-	StanzaChannel* stanzaChannel_;
+	DummyStanzaChannel* stanzaChannel_;
 	IQChannel* iqChannel_;
 	IQRouter* iqRouter_;
 	EventController* eventController_;
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index ce347ab..d485abc 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -60,6 +60,7 @@
 #include "Swiften/StringCodecs/Hexify.h"
 #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
 #include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h"
+#include "Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h"
 #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
 #include "Swift/Controllers/Storages/CertificateStorageFactory.h"
 #include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h"
@@ -79,6 +80,7 @@ static const std::string CLIENT_NAME = "Swift";
 static const std::string CLIENT_NODE = "http://swift.im";
 
 static const std::string SHOW_NOTIFICATIONS = "showNotifications";
+static const std::string REQUEST_DELIVERYRECEIPTS = "requestDeliveryReceipts";
 
 MainController::MainController(
 		EventLoop* eventLoop,
@@ -249,6 +251,11 @@ void MainController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 		notifier_->setPersistentEnabled(enabled);
 		settings_->storeBool(SHOW_NOTIFICATIONS, enabled);
 	}
+	boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> deliveryReceiptEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event);
+	if (deliveryReceiptEvent) {
+		settings_->storeBool(REQUEST_DELIVERYRECEIPTS, deliveryReceiptEvent->getEnabled());
+	}
+
 }
 
 void MainController::resetPendingReconnects() {
@@ -291,7 +298,7 @@ void MainController::handleConnected() {
 
 		contactEditController_ = new ContactEditController(rosterController_, uiFactory_, uiEventStream_);
 
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, eagleMode_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), eagleMode_);
 		
 		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
 		chatsManager_->setAvatarManager(client_->getAvatarManager());
@@ -311,6 +318,7 @@ void MainController::handleConnected() {
 		discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
 		discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
 #endif
+		discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature);
 		client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
 		client_->getDiscoManager()->setDiscoInfo(discoInfo);
 
@@ -335,6 +343,9 @@ void MainController::handleConnected() {
 	sendPresence(statusTracker_->getNextPresence());
 	/* Enable chats last of all, so rejoining MUCs has the right sent presence */
 	chatsManager_->setOnline(true);
+
+	// notify world about current delivey receipt request setting state.
+	uiEventStream_->send(boost::make_shared<ToggleRequestDeliveryReceiptsUIEvent>(settings_->getBoolSetting(REQUEST_DELIVERYRECEIPTS, false)));
 }
 
 void MainController::handleEventQueueLengthChange(int count) {
diff --git a/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h b/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h
new file mode 100644
index 0000000..1ea2db5
--- /dev/null
+++ b/Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIEvents/UIEvent.h"
+
+namespace Swift {
+	class ToggleRequestDeliveryReceiptsUIEvent : public UIEvent {
+		public:
+			ToggleRequestDeliveryReceiptsUIEvent(bool enable) : enabled_(enable) {}
+			bool getEnabled() {return enabled_;}
+		private:
+			bool enabled_;
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index 6fce7a0..fd99514 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -32,6 +32,7 @@ namespace Swift {
 	class ChatWindow {
 		public:
 			enum AckState {Pending, Received, Failed};
+			enum ReceiptState {ReceiptRequested, ReceiptReceived};
 			enum Tristate {Yes, No, Maybe};
 			enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor};
 			enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed};
@@ -57,6 +58,9 @@ namespace Swift {
 			virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0;
 			virtual void addMUCInvitation(const JID& jid, const std::string& reason, const std::string& password) = 0;
 
+			// message receipts
+			virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0;
+
 			virtual void setContactChatState(ChatState::ChatStateType state) = 0;
 			virtual void setName(const std::string& name) = 0;
 			virtual void show() = 0;
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 7e31179..5e43549 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -25,6 +25,8 @@ namespace Swift {
 			virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { };
 			virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { };
 			
+			virtual void setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { }
+
 			virtual void setContactChatState(ChatState::ChatStateType /*state*/) {};
 			virtual void setName(const std::string& name) {name_ = name;};
 			virtual void show() {};
diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp
index 47cb1a0..a2e4651 100644
--- a/Swift/QtUI/MessageSnippet.cpp
+++ b/Swift/QtUI/MessageSnippet.cpp
@@ -32,7 +32,7 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co
 		}
 	}
 
-	content_.replace("%message%", "<span class='swift_message'>" + escape(message) + "</span><span class='swift_ack'></span>");
+	content_.replace("%message%", "<span class='swift_message'>" + escape(message) + "</span><span class='swift_ack'></span><span class='swift_receipt'></span>");
 	content_.replace("%sender%", escape(sender));
 	content_.replace("%time%", "<span class='swift_time'>" + timeToEscapedString(time) + "</span>");
 	content_.replace("%userIconPath%", escape(iconURI));
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 1c3dd37..db00ba0 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -196,6 +196,22 @@ void QtChatView::setAckXML(const QString& id, const QString& xml) {
 	ackElement.setInnerXml(xml);
 }
 
+void QtChatView::setReceiptXML(const QString& id, const QString& xml) {
+	QWebElement message = document_.findFirst("#" + id);
+	if (message.isNull()) return;
+	QWebElement receiptElement = message.findFirst("span.swift_receipt");
+	assert(!receiptElement.isNull());
+	receiptElement.setInnerXml(xml);
+}
+
+void QtChatView::displayReceiptInfo(const QString& id, bool showIt) {
+	QWebElement message = document_.findFirst("#" + id);
+	if (message.isNull()) return;
+	QWebElement receiptElement = message.findFirst("span.swift_receipt");
+	assert(!receiptElement.isNull());
+	receiptElement.setStyleProperty("display", showIt ? "inline" : "none");
+}
+
 void QtChatView::rememberScrolledToBottom() {
 	isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
 }
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index cf47029..0cc521a 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -35,6 +35,9 @@ namespace Swift {
 			void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time);
 			void rememberScrolledToBottom();
 			void setAckXML(const QString& id, const QString& xml);
+			void setReceiptXML(const QString& id, const QString& xml);
+			void displayReceiptInfo(const QString& id, bool showIt);
+
 			QString getLastSentMessage();
 			void addToJSEnvironment(const QString&, QObject*);
 			void setFileTransferProgress(QString id, const int percentageDone);
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index f0f268d..88f326f 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -475,13 +475,32 @@ void QtChatWindow::flash() {
 void QtChatWindow::setAckState(std::string const& id, ChatWindow::AckState state) {
 	QString xml;
 	switch (state) {
-		case ChatWindow::Pending: xml = "<img src='qrc:/icons/throbber.gif' alt='" + tr("This message has not been received by your server yet.") + "'/>"; break;
-		case ChatWindow::Received: xml = ""; break;
+		case ChatWindow::Pending:
+			xml = "<img src='qrc:/icons/throbber.gif' alt='" + tr("This message has not been received by your server yet.") + "'/>";
+			messageLog_->displayReceiptInfo(P2QSTRING(id), false);
+			break;
+		case ChatWindow::Received:
+			xml = "";
+			messageLog_->displayReceiptInfo(P2QSTRING(id), true);
+			break;
 		case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' alt='" + tr("This message may not have been transmitted.") + "'/>"; break;
 	}
 	messageLog_->setAckXML(P2QSTRING(id), xml);
 }
 
+void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
+	QString xml;
+	switch (state) {
+		case ChatWindow::ReceiptReceived:
+			xml = "<img src='qrc:/icons/check.png' alt'" + tr("The receipt for this message has been received.") + "'/>";
+			break;
+		case ChatWindow::ReceiptRequested:
+			xml = "<img src='qrc:/icons/warn.png' alt='" + tr("The receipt for this message has not yet been received. The receipient(s) might not have received this message.") + "'/>";
+			break;
+	}
+	messageLog_->setReceiptXML(P2QSTRING(id), xml);
+}
+
 int QtChatWindow::getCount() {
 	return unreadCount_;
 }
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 233f2bc..55e929d 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -68,6 +68,10 @@ namespace Swift {
 			int getCount();
 			void replaceLastMessage(const std::string& message);
 			void setAckState(const std::string& id, AckState state);
+
+			// message receipts
+			void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state);
+
 			void flash();
 			QByteArray getSplitterState();
 			virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions);
diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp
index e339d79..5a8b9ab 100644
--- a/Swift/QtUI/QtLoginWindow.cpp
+++ b/Swift/QtUI/QtLoginWindow.cpp
@@ -455,6 +455,10 @@ void QtLoginWindow::hide() {
 	window()->hide();
 }
 
+QtLoginWindow::QtMenus QtLoginWindow::getMenus() const {
+	return QtMenus(swiftMenu_, generalMenu_);
+}
+
 void QtLoginWindow::resizeEvent(QResizeEvent*) {
 	emit geometryChanged();
 }
diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h
index acf327f..a830af5 100644
--- a/Swift/QtUI/QtLoginWindow.h
+++ b/Swift/QtUI/QtLoginWindow.h
@@ -27,6 +27,13 @@ namespace Swift {
 	class QtLoginWindow : public QMainWindow, public LoginWindow {
 		Q_OBJECT
 		public:
+			struct QtMenus {
+				QtMenus(QMenu* swiftMenu, QMenu* generalMenu) : swiftMenu(swiftMenu), generalMenu(generalMenu) {}
+				QMenu* swiftMenu;
+				QMenu* generalMenu;
+			};
+
+		public:
 			QtLoginWindow(UIEventStream* uiEventStream, bool eagleMode);
 
 			void morphInto(MainWindow *mainWindow);
@@ -39,7 +46,7 @@ namespace Swift {
 			void selectUser(const std::string& user);
 			bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate);
 			void hide();
-
+			QtMenus getMenus() const;
 			virtual void quit();
 
 		signals:
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index a4ce98e..199e388 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -25,6 +25,7 @@
 #include <Swift/QtUI/QtTabWidget.h>
 #include <Swift/QtUI/QtSettingsProvider.h>
 #include <Swift/QtUI/QtUIPreferences.h>
+#include <Swift/QtUI/QtLoginWindow.h>
 #include <Roster/QtRosterWidget.h>
 #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
@@ -33,12 +34,13 @@
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
+#include <Swift/Controllers/UIEvents/ToggleRequestDeliveryReceiptsUIEvent.h>
 
 namespace Swift {
 
 #define CURRENT_ROSTER_TAB "current_roster_tab"
 
-QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventStream, QtUIPreferences* uiPreferences) : QWidget(), MainWindow(false) {
+QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventStream, QtUIPreferences* uiPreferences, QtLoginWindow::QtMenus loginMenus) : QWidget(), MainWindow(false), loginMenus_(loginMenus) {
 	uiEventStream_ = uiEventStream;
 	uiPreferences_ = uiPreferences;
 	settings_ = settings;
@@ -123,6 +125,12 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
 	connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction()));
 	actionsMenu->addAction(signOutAction);
 
+	toggleRequestDeliveryReceipts_ = new QAction(tr("&Request Delivery Receipts"), this);
+	toggleRequestDeliveryReceipts_->setCheckable(true);
+	toggleRequestDeliveryReceipts_->setChecked(false);
+	connect(toggleRequestDeliveryReceipts_, SIGNAL(toggled(bool)), SLOT(handleToggleRequestDeliveryReceipts(bool)));
+	loginMenus_.generalMenu->addAction(toggleRequestDeliveryReceipts_);
+
 	treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1));
 
 	setAvailableAdHocCommands(std::vector<DiscoItems::Item>());
@@ -143,6 +151,10 @@ void QtMainWindow::handleTabChanged(int index) {
 	settings_->storeInt(CURRENT_ROSTER_TAB, index);
 }
 
+void QtMainWindow::handleToggleRequestDeliveryReceipts(bool enabled) {
+	uiEventStream_->send(boost::make_shared<ToggleRequestDeliveryReceiptsUIEvent>(enabled));
+}
+
 QtEventWindow* QtMainWindow::getEventWindow() {
 	return eventWindow_;
 }
@@ -192,6 +204,7 @@ void QtMainWindow::handleChatUserActionTriggered(bool /*checked*/) {
 }
 
 void QtMainWindow::handleSignOutAction() {
+	loginMenus_.generalMenu->removeAction(toggleRequestDeliveryReceipts_);
 	onSignOutRequest();
 }
 
@@ -212,6 +225,10 @@ void QtMainWindow::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 	if (toggleEvent) {
 		handleShowOfflineToggled(toggleEvent->getShow());
 	}
+	boost::shared_ptr<ToggleRequestDeliveryReceiptsUIEvent> deliveryReceiptEvent = boost::dynamic_pointer_cast<ToggleRequestDeliveryReceiptsUIEvent>(event);
+	if (deliveryReceiptEvent) {
+		toggleRequestDeliveryReceipts_->setChecked(deliveryReceiptEvent->getEnabled());
+	}
 }
 
 void QtMainWindow::handleShowOfflineToggled(bool state) {
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index afcb57c..3c8bdec 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -13,6 +13,7 @@
 #include "Swift/QtUI/QtRosterHeader.h"
 #include "Swift/QtUI/EventViewer/QtEventWindow.h"
 #include "Swift/QtUI/ChatList/QtChatListWindow.h"
+#include "Swift/QtUI/QtLoginWindow.h"
 
 #include <vector>
 
@@ -35,7 +36,7 @@ namespace Swift {
 	class QtMainWindow : public QWidget, public MainWindow {
 		Q_OBJECT
 		public:
-			QtMainWindow(QtSettingsProvider*, UIEventStream* eventStream, QtUIPreferences* uiPreferences);
+			QtMainWindow(QtSettingsProvider*, UIEventStream* eventStream, QtUIPreferences* uiPreferences, QtLoginWindow::QtMenus loginMenus);
 			virtual ~QtMainWindow();
 			std::vector<QMenu*> getMenus() {return menus_;}
 			void setMyNick(const std::string& name);
@@ -62,9 +63,11 @@ namespace Swift {
 			void handleChatCountUpdated(int count);
 			void handleEditProfileRequest();
 			void handleTabChanged(int index);
+			void handleToggleRequestDeliveryReceipts(bool enabled);
 
 		private:
 			QtSettingsProvider* settings_;
+			QtLoginWindow::QtMenus loginMenus_;
 			std::vector<QMenu*> menus_;
 			QtRosterWidget* treeWidget_;
 			QtRosterHeader* meView_;
@@ -72,6 +75,7 @@ namespace Swift {
 			QAction* editUserAction_;
 			QAction* chatUserAction_;
 			QAction* showOfflineAction_;
+			QAction* toggleRequestDeliveryReceipts_;
 			QMenu* serverAdHocMenu_;
 			QtTabWidget* tabs_;
 			QWidget* contactsTabWidget_;
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 8a026f2..88da781 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -55,7 +55,7 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
 }
 
 MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) {
-	lastMainWindow  = new QtMainWindow(settings, eventStream, uiPreferences);
+	lastMainWindow  = new QtMainWindow(settings, eventStream, uiPreferences, loginWindow->getMenus());
 	return lastMainWindow;
 }
 
diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc
index d2798e4..61d8cc0 100644
--- a/Swift/QtUI/Swift.qrc
+++ b/Swift/QtUI/Swift.qrc
@@ -11,6 +11,8 @@
 		<file alias="icons/offline.png">../resources/icons/offline.png</file>
 		<file alias="icons/certificate.png">../resources/icons/certificate.png</file>
 		<file alias="icons/error.png">../resources/icons/error.png</file>
+		<file alias="icons/warn.png">../resources/icons/warn.png</file>
+		<file alias="icons/check.png">../resources/icons/check.png</file>
 		<file alias="icons/throbber.gif">../resources/icons/throbber.gif</file>
 		<file alias="icons/avatar.png">../resources/icons/avatar.png</file>
 		<file alias="icons/no-avatar.png">../resources/icons/no-avatar.png</file>
diff --git a/Swift/resources/icons/check.png b/Swift/resources/icons/check.png
new file mode 100644
index 0000000..6e29fd8
Binary files /dev/null and b/Swift/resources/icons/check.png differ
diff --git a/Swift/resources/icons/license_info.txt b/Swift/resources/icons/license_info.txt
new file mode 100644
index 0000000..fffbf1d
--- /dev/null
+++ b/Swift/resources/icons/license_info.txt
@@ -0,0 +1,5 @@
+Resources:
+
+Filename		Source 												License
+warn.png		Swift/resources/themes/Default/images/warn.png		BSD
+check.png		http://en.wikipedia.org/wiki/File:Green_check.svg	Public Domain
\ No newline at end of file
diff --git a/Swift/resources/icons/warn.png b/Swift/resources/icons/warn.png
new file mode 100644
index 0000000..357337e
Binary files /dev/null and b/Swift/resources/icons/warn.png differ
diff --git a/Swiften/Elements/DeliveryReceipt.h b/Swiften/Elements/DeliveryReceipt.h
new file mode 100644
index 0000000..927da73
--- /dev/null
+++ b/Swiften/Elements/DeliveryReceipt.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Payload.h>
+
+namespace Swift {
+
+class DeliveryReceipt : public Payload {
+	public:
+		typedef boost::shared_ptr<DeliveryReceipt> ref;
+
+	public:
+		DeliveryReceipt() {}
+
+		DeliveryReceipt(const std::string& msgId) : receivedID_(msgId) {}
+
+		void setReceivedID(const std::string& msgId) {
+			receivedID_ = msgId;
+		}
+
+		std::string getReceivedID() const {
+			return receivedID_;
+		}
+
+	private:
+		std::string receivedID_;
+};
+
+}
diff --git a/Swiften/Elements/DeliveryReceiptRequest.h b/Swiften/Elements/DeliveryReceiptRequest.h
new file mode 100644
index 0000000..3998785
--- /dev/null
+++ b/Swiften/Elements/DeliveryReceiptRequest.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/Payload.h>
+
+namespace Swift {
+
+class DeliveryReceiptRequest : public Payload {
+	public:
+		typedef boost::shared_ptr<DeliveryReceiptRequest> ref;
+
+	public:
+		DeliveryReceiptRequest() {}
+};
+
+}
diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp
index 35d4d04..a4ce079 100644
--- a/Swiften/Elements/DiscoInfo.cpp
+++ b/Swiften/Elements/DiscoInfo.cpp
@@ -21,7 +21,7 @@ const std::string DiscoInfo::JingleFTFeature = std::string("urn:xmpp:jingle:apps
 const std::string DiscoInfo::JingleTransportsIBBFeature = std::string("urn:xmpp:jingle:transports:ibb:1");
 const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp:jingle:transports:s5b:1");
 const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams");
-
+const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:xmpp:receipts");
 
 bool DiscoInfo::Identity::operator<(const Identity& other) const {
 	if (category_ == other.category_) {
diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h
index 6d6e722..3173ee6 100644
--- a/Swiften/Elements/DiscoInfo.h
+++ b/Swiften/Elements/DiscoInfo.h
@@ -28,6 +28,7 @@ namespace Swift {
 			static const std::string JingleTransportsIBBFeature;
 			static const std::string JingleTransportsS5BFeature;
 			static const std::string Bytestream;
+			static const std::string MessageDeliveryReceiptsFeature;
 
 			class Identity {
 				public:
diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.cpp b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.cpp
new file mode 100644
index 0000000..347200d
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h>
+
+#include <boost/optional.hpp>
+
+namespace Swift {
+
+DeliveryReceiptParser::DeliveryReceiptParser() : level_(0) {
+}
+
+void DeliveryReceiptParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributeMap) {
+	if (level_ == 0) {
+		if (element == "received") {
+			if (attributeMap.getAttributeValue("id").is_initialized()) {
+				getPayloadInternal()->setReceivedID(attributeMap.getAttributeValue("id").get());
+			}
+		}
+	}
+	++level_;
+}
+
+void DeliveryReceiptParser::handleEndElement(const std::string&, const std::string&) {
+	--level_;
+}
+
+void DeliveryReceiptParser::handleCharacterData(const std::string&) {
+
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h
new file mode 100644
index 0000000..90a0921
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+	class DeliveryReceiptParser : public GenericPayloadParser<DeliveryReceipt> {
+		public:
+			DeliveryReceiptParser();
+
+			virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributeMap);
+			virtual void handleEndElement(const std::string& element, const std::string&);
+			virtual void handleCharacterData(const std::string& data);
+
+		private:
+			int level_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h
new file mode 100644
index 0000000..259e04a
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Parser/PayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h>
+
+namespace Swift {
+	class PayloadParserFactoryCollection;
+
+	class DeliveryReceiptParserFactory : public PayloadParserFactory {
+		public:
+			DeliveryReceiptParserFactory() {
+			}
+
+			virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const {
+				return ns == "urn:xmpp:receipts" && element == "received";
+			}
+
+			virtual PayloadParser* createPayloadParser() {
+				return new DeliveryReceiptParser();
+			}
+
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.cpp b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.cpp
new file mode 100644
index 0000000..f10cbc9
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Base/Log.h>
+
+namespace Swift {
+
+DeliveryReceiptRequestParser::DeliveryReceiptRequestParser() {
+}
+
+void DeliveryReceiptRequestParser::handleStartElement(const std::string&, const std::string&, const AttributeMap&) {
+
+}
+
+void DeliveryReceiptRequestParser::handleEndElement(const std::string&, const std::string&) {
+
+}
+
+void DeliveryReceiptRequestParser::handleCharacterData(const std::string&) {
+
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h
new file mode 100644
index 0000000..55f7997
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+	class DeliveryReceiptRequestParser : public GenericPayloadParser<DeliveryReceiptRequest> {
+		public:
+			DeliveryReceiptRequestParser();
+
+			virtual void handleStartElement(const std::string&, const std::string&, const AttributeMap&);
+			virtual void handleEndElement(const std::string&, const std::string&);
+			virtual void handleCharacterData(const std::string& data);
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h
new file mode 100644
index 0000000..9bd98c2
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Parser/PayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h>
+
+namespace Swift {
+	class PayloadParserFactoryCollection;
+
+	class DeliveryReceiptRequestParserFactory : public PayloadParserFactory {
+		public:
+			DeliveryReceiptRequestParserFactory() {
+			}
+
+			virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const {
+				return ns == "urn:xmpp:receipts" && element == "request";
+			}
+
+			virtual PayloadParser* createPayloadParser() {
+				return new DeliveryReceiptRequestParser();
+			}
+
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 04e9fa6..01addf5 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -64,6 +64,8 @@
 #include <Swiften/Parser/PayloadParsers/S5BProxyRequestParser.h>
 #include <Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h>
 #include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h>
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParserFactory.h>
 
 using namespace boost;
 
@@ -121,6 +123,8 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferReceivedParser> >("received", "urn:xmpp:jingle:apps:file-transfer:3"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleFileTransferHashParser> >("checksum"));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<S5BProxyRequestParser> >("query", "http://jabber.org/protocol/bytestreams"));
+	factories_.push_back(boost::make_shared<DeliveryReceiptParserFactory>());
+	factories_.push_back(boost::make_shared<DeliveryReceiptRequestParserFactory>());
 
 	foreach(shared_ptr<PayloadParserFactory> factory, factories_) {
 		addFactory(factory.get());
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp
new file mode 100644
index 0000000..919c342
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptParser.h>
+#include <Swiften/Parser/PayloadParsers/DeliveryReceiptRequestParser.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
+
+using namespace Swift;
+
+class DeliveryReceiptParserTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(DeliveryReceiptParserTest);
+		CPPUNIT_TEST(testParseXEP0184Example3);
+		CPPUNIT_TEST(testParseXEP0184Example4);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testParseXEP0184Example3() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse("<request xmlns='urn:xmpp:receipts'/>"));
+
+			DeliveryReceiptRequest::ref request = boost::dynamic_pointer_cast<DeliveryReceiptRequest>(parser.getPayload());
+
+			CPPUNIT_ASSERT(request);
+		}
+
+		void testParseXEP0184Example4() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse("<received xmlns='urn:xmpp:receipts' id='richard2-4.1.247'/>"));
+
+			DeliveryReceipt::ref receipt = boost::dynamic_pointer_cast<DeliveryReceipt>(parser.getPayload());
+
+			CPPUNIT_ASSERT_EQUAL(std::string("richard2-4.1.247"), receipt->getReceivedID());
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DeliveryReceiptParserTest);
+
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index e1ec1d8..e4c2778 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -70,6 +70,8 @@ sources = [
 		"PayloadParsers/ReplaceParser.cpp",
 		"PayloadParsers/LastParser.cpp",
 		"PayloadParsers/S5BProxyRequestParser.cpp",
+		"PayloadParsers/DeliveryReceiptParser.cpp",
+		"PayloadParsers/DeliveryReceiptRequestParser.cpp",
 		"PlatformXMLParserFactory.cpp",
 		"PresenceParser.cpp",
 		"SerializingParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index ee21ba8..8c3ad42 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -178,6 +178,8 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp",
+			"Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp",
+			"Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp",
 			"Serializer/PresenceSerializer.cpp",
 			"Serializer/StanzaSerializer.cpp",
 			"Serializer/StreamErrorSerializer.cpp",
@@ -309,6 +311,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Parser/PayloadParsers/UnitTest/ReplaceTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/MUCUserPayloadParserTest.cpp"),
+			File("Parser/PayloadParsers/UnitTest/DeliveryReceiptParserTest.cpp"),
 			File("Parser/UnitTest/BOSHBodyExtractorTest.cpp"),
 			File("Parser/UnitTest/AttributeMapTest.cpp"),
 			File("Parser/UnitTest/IQParserTest.cpp"),
@@ -357,6 +360,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp"),
+			File("Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp"),
 			File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"),
 			File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp
new file mode 100644
index 0000000..eeb0d90
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.cpp
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+
+#include <Swiften/Base/Log.h>
+
+namespace Swift {
+
+DeliveryReceiptRequestSerializer::DeliveryReceiptRequestSerializer() : GenericPayloadSerializer<DeliveryReceiptRequest>() {
+}
+
+std::string DeliveryReceiptRequestSerializer::serializePayload(boost::shared_ptr<DeliveryReceiptRequest> /* request*/) const {
+	XMLElement requestXML("request", "urn:xmpp:receipts");
+	return requestXML.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h
new file mode 100644
index 0000000..aa3c315
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+
+namespace Swift {
+	class DeliveryReceiptRequestSerializer : public GenericPayloadSerializer<DeliveryReceiptRequest> {
+		public:
+			DeliveryReceiptRequestSerializer();
+
+			virtual std::string serializePayload(boost::shared_ptr<DeliveryReceiptRequest> request) const;
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp
new file mode 100644
index 0000000..a9033b2
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+
+namespace Swift {
+
+DeliveryReceiptSerializer::DeliveryReceiptSerializer() : GenericPayloadSerializer<DeliveryReceipt>() {
+}
+
+std::string DeliveryReceiptSerializer::serializePayload(boost::shared_ptr<DeliveryReceipt> receipt) const {
+	XMLElement received("received", "urn:xmpp:receipts");
+	received.setAttribute("id", receipt->getReceivedID());
+	return received.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h
new file mode 100644
index 0000000..5fda3ea
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the BSD license.
+ * See http://www.opensource.org/licenses/bsd-license.php for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+
+namespace Swift {
+	class DeliveryReceiptSerializer : public GenericPayloadSerializer<DeliveryReceipt> {
+		public:
+			DeliveryReceiptSerializer();
+
+			virtual std::string serializePayload(boost::shared_ptr<DeliveryReceipt> receipt) const;
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 87dfbc3..499a185 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -60,6 +60,8 @@
 #include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/S5BProxyRequestSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h>
 
 namespace Swift {
 
@@ -116,6 +118,8 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new JingleS5BTransportPayloadSerializer());
 	serializers_.push_back(new JinglePayloadSerializer(this));
 	serializers_.push_back(new S5BProxyRequestSerializer());
+	serializers_.push_back(new DeliveryReceiptSerializer());
+	serializers_.push_back(new DeliveryReceiptRequestSerializer());
 	
 	foreach(PayloadSerializer* serializer, serializers_) {
 		addSerializer(serializer);
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp
new file mode 100644
index 0000000..9282db4
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/DeliveryReceiptSerializerTest.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/DeliveryReceiptRequestSerializer.h>
+
+using namespace Swift;
+
+class DeliveryReceiptSerializerTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(DeliveryReceiptSerializerTest);
+		CPPUNIT_TEST(testSerialize_XEP0184Example3);
+		CPPUNIT_TEST(testSerialize_XEP0184Example4);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void testSerialize_XEP0184Example3() {
+			std::string expected =	"<request xmlns=\"urn:xmpp:receipts\"/>";
+
+			DeliveryReceiptRequest::ref receipt = boost::make_shared<DeliveryReceiptRequest>();
+
+			boost::shared_ptr<DeliveryReceiptRequestSerializer> serializer = boost::make_shared<DeliveryReceiptRequestSerializer>();
+			CPPUNIT_ASSERT_EQUAL(expected, serializer->serializePayload(receipt));
+		}
+
+		void testSerialize_XEP0184Example4() {
+			std::string expected =	"<received id=\"richard2-4.1.247\" xmlns=\"urn:xmpp:receipts\"/>";
+
+			DeliveryReceipt::ref receipt = boost::make_shared<DeliveryReceipt>("richard2-4.1.247");
+
+			boost::shared_ptr<DeliveryReceiptSerializer> serializer = boost::make_shared<DeliveryReceiptSerializer>();
+			CPPUNIT_ASSERT_EQUAL(expected, serializer->serializePayload(receipt));
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(DeliveryReceiptSerializerTest);
+
-- 
cgit v0.10.2-6-g49f6