From 497b647fe034a3d2cdc6d75ce0ff70e3df3aaf04 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Fri, 8 Mar 2013 16:17:30 +0100
Subject: Adding support for Blocking Command (XEP-0191) to Swift(-en).

Change-Id: I7c92518dc389474d520d4cf96f96a11459f73d26
License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

diff --git a/Swift/Controllers/BlockListController.cpp b/Swift/Controllers/BlockListController.cpp
new file mode 100644
index 0000000..e7bc45d
--- /dev/null
+++ b/Swift/Controllers/BlockListController.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/BlockListController.h>
+
+#include <boost/bind.hpp>
+
+#include <Swiften/Client/ClientBlockListManager.h>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/format.h>
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
+#include <Swift/Controllers/XMPPEvents/ErrorEvent.h>
+#include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+
+namespace Swift {
+
+BlockListController::BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController) : blockListManager_(blockListManager), blockListEditorWidgetFactory_(blockListEditorWidgetFactory), blockListEditorWidget_(0), eventController_(eventController), remainingRequests_(0) {
+	uiEventStream->onUIEvent.connect(boost::bind(&BlockListController::handleUIEvent, this, _1));
+	blockListManager_->getBlockList()->onItemAdded.connect(boost::bind(&BlockListController::handleBlockListChanged, this));
+	blockListManager_->getBlockList()->onItemRemoved.connect(boost::bind(&BlockListController::handleBlockListChanged, this));
+}
+
+BlockListController::~BlockListController() {
+	blockListManager_->getBlockList()->onItemAdded.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this));
+	blockListManager_->getBlockList()->onItemRemoved.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this));
+}
+
+void BlockListController::blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID> &jidsToUnblock, std::vector<JID> &jidsToBlock) const {
+	foreach (const JID& jid, blockListBeforeEdit) {
+		if (std::find(newBlockList.begin(), newBlockList.end(), jid) == newBlockList.end()) {
+			jidsToUnblock.push_back(jid);
+		}
+	}
+
+	foreach (const JID& jid, newBlockList) {
+		if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) {
+			jidsToBlock.push_back(jid);
+		}
+	}
+}
+
+void BlockListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) {
+	// handle UI dialog
+	boost::shared_ptr<RequestBlockListDialogUIEvent> requestDialogEvent = boost::dynamic_pointer_cast<RequestBlockListDialogUIEvent>(rawEvent);
+	if (requestDialogEvent != NULL) {
+		if (blockListEditorWidget_ == NULL) {
+			blockListEditorWidget_ = blockListEditorWidgetFactory_->createBlockListEditorWidget();
+			blockListEditorWidget_->onSetNewBlockList.connect(boost::bind(&BlockListController::handleSetNewBlockList, this, _1));
+		}
+		blockListBeforeEdit = blockListManager_->getBlockList()->getItems();
+		blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit);
+		blockListEditorWidget_->show();
+		return;
+	}
+
+	// handle block state change
+	boost::shared_ptr<RequestChangeBlockStateUIEvent> changeStateEvent = boost::dynamic_pointer_cast<RequestChangeBlockStateUIEvent>(rawEvent);
+	if (changeStateEvent != NULL) {
+		if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Blocked) {
+			GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDRequest(changeStateEvent->getContact());
+			blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false));
+			blockRequest->send();
+		} else if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Unblocked) {
+			GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDRequest(changeStateEvent->getContact());
+			unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false));
+			unblockRequest->send();
+		}
+		return;
+	}
+}
+
+void BlockListController::handleBlockResponse(GenericRequest<BlockPayload>::ref request, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) {
+	if (error) {
+		std::string errorMessage;
+		// FIXME: Handle reporting of list of JIDs in a translatable way.
+		errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to block %1%.")) % jids.at(0).toString());
+		if (!error->getText().empty()) {
+			errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText());
+		}
+		eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage));
+	}
+	if (originEditor) {
+		remainingRequests_--;
+		if (blockListEditorWidget_ && (remainingRequests_ == 0)) {
+			blockListEditorWidget_->setBusy(false);
+		}
+	}
+}
+
+void BlockListController::handleUnblockResponse(GenericRequest<UnblockPayload>::ref request, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) {
+	if (error) {
+		std::string errorMessage;
+		// FIXME: Handle reporting of list of JIDs in a translatable way.
+		errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to unblock %1%.")) % jids.at(0).toString());
+		if (!error->getText().empty()) {
+			errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText());
+		}
+		eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage));
+	}
+	if (originEditor) {
+		remainingRequests_--;
+		if (blockListEditorWidget_ && (remainingRequests_ == 0)) {
+			blockListEditorWidget_->setBusy(false);
+		}
+	}
+}
+
+void BlockListController::handleSetNewBlockList(const std::vector<JID> &newBlockList) {
+	std::vector<JID> jidsToBlock;
+	std::vector<JID> jidsToUnblock;
+
+	blockListDifferences(newBlockList, jidsToUnblock, jidsToBlock);
+
+	if (!jidsToBlock.empty()) {
+		remainingRequests_++;
+		GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDsRequest(jidsToBlock);
+		blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, jidsToBlock, true));
+		blockRequest->send();
+	}
+	if (!jidsToUnblock.empty()) {
+		remainingRequests_++;
+		GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDsRequest(jidsToUnblock);
+		unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, jidsToUnblock, true));
+		unblockRequest->send();
+	}
+	if (!jidsToBlock.empty() || jidsToUnblock.empty()) {
+		assert(blockListEditorWidget_);
+		blockListEditorWidget_->setBusy(true);
+	}
+}
+
+void BlockListController::handleBlockListChanged() {
+	if (blockListEditorWidget_) {
+		std::vector<JID> jidsToBlock;
+		std::vector<JID> jidsToUnblock;
+
+		blockListDifferences(blockListEditorWidget_->getCurrentBlockList(), jidsToUnblock, jidsToBlock);
+		blockListBeforeEdit = blockListManager_->getBlockList()->getItems();
+
+		foreach (const JID& jid, jidsToBlock) {
+			blockListBeforeEdit.push_back(jid);
+		}
+
+		foreach (const JID& jid, jidsToUnblock) {
+			blockListBeforeEdit.erase(std::remove(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid), blockListBeforeEdit.end());;
+		}
+
+		blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit);
+	}
+}
+
+}
diff --git a/Swift/Controllers/BlockListController.h b/Swift/Controllers/BlockListController.h
new file mode 100644
index 0000000..4c9caad
--- /dev/null
+++ b/Swift/Controllers/BlockListController.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Queries/GenericRequest.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h>
+
+namespace Swift {
+
+class BlockPayload;
+class UnblockPayload;
+class ClientBlockListManager;
+class EventController;
+
+class BlockListController {
+public:
+	BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController);
+	~BlockListController();
+
+private:
+	void blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID>& jidsToUnblock, std::vector<JID>& jidsToBlock) const;
+
+	void handleUIEvent(boost::shared_ptr<UIEvent> event);
+
+	void handleBlockResponse(GenericRequest<BlockPayload>::ref, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor);
+	void handleUnblockResponse(GenericRequest<UnblockPayload>::ref, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor);
+
+	void handleSetNewBlockList(const std::vector<JID>& newBlockList);
+
+	void handleBlockListChanged();
+
+private:
+	ClientBlockListManager* blockListManager_;
+	BlockListEditorWidgetFactory* blockListEditorWidgetFactory_;
+	BlockListEditorWidget* blockListEditorWidget_;
+	EventController* eventController_;
+	std::vector<JID> blockListBeforeEdit;
+	int remainingRequests_;
+};
+
+}
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 3c0e933..f0de59c 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -36,14 +36,15 @@
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/Highlighter.h>
 #include <Swiften/Base/Log.h>
+#include <Swiften/Client/ClientBlockListManager.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, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) {
+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, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager)
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -145,6 +146,19 @@ void ChatController::setToJID(const JID& jid) {
 	handleBareJIDCapsChanged(toJID_);
 }
 
+void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
+	ChatControllerBase::setAvailableServerFeatures(info);
+	if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
+		boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+
+		blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this));
+		blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingItemAdded, this, _1));
+		blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingItemRemoved, this, _1));
+
+		handleBlockingStateChanged();
+	}
+}
+
 bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
 	return false;
 }
@@ -216,6 +230,36 @@ void ChatController::checkForDisplayingDisplayReceiptsAlert() {
 	}
 }
 
+void ChatController::handleBlockingStateChanged() {
+	boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+	if (blockList->getState() == BlockList::Available) {
+		if (blockList->isBlocked(toJID_.toBare())) {
+			chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
+			chatWindow_->setInputEnabled(false);
+			chatWindow_->setBlockingState(ChatWindow::IsBlocked);
+		} else {
+			chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
+		}
+	}
+}
+
+void ChatController::handleBlockingItemAdded(const JID& jid) {
+	if (jid == toJID_.toBare()) {
+		chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first."));
+		chatWindow_->setInputEnabled(false);
+		chatWindow_->setBlockingState(ChatWindow::IsBlocked);
+	}
+}
+
+void ChatController::handleBlockingItemRemoved(const JID& jid) {
+	if (jid == toJID_.toBare()) {
+		// FIXME: Support for different types of alerts.
+		chatWindow_->cancelAlert();
+		chatWindow_->setInputEnabled(true);
+		chatWindow_->setBlockingState(ChatWindow::IsUnblocked);
+	}
+}
+
 void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
 	boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
 	if (replace) {
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 6021ec1..4322240 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -23,12 +23,14 @@ namespace Swift {
 	class SettingsProvider;
 	class HistoryController;
 	class HighlightManager;
+	class ClientBlockListManager;
 
 	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, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager);
+			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, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager);
 			virtual ~ChatController();
 			virtual void setToJID(const JID& jid);
+			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
 			virtual void setOnline(bool online);
 			virtual void handleNewFileTransferController(FileTransferController* ftc);
 			virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
@@ -67,6 +69,10 @@ namespace Swift {
 			void handleSettingChanged(const std::string& settingPath);
 			void checkForDisplayingDisplayReceiptsAlert();
 
+			void handleBlockingStateChanged();
+			void handleBlockingItemAdded(const JID&);
+			void handleBlockingItemRemoved(const JID&);
+
 		private:
 			NickResolver* nickResolver_;
 			ChatStateNotifier* chatStateNotifier_;
@@ -86,6 +92,11 @@ namespace Swift {
 			std::map<std::string, FileTransferController*> ftControllers;
 			SettingsProvider* settings_;
 			std::string lastWbID_;
+
+			ClientBlockListManager* clientBlockListManager_;
+			boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
+			boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
+			boost::bsignals::scoped_connection blockingOnItemRemovedConnection_;
 	};
 }
 
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index baef9e6..84bd06a 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -48,7 +48,7 @@ namespace Swift {
 			virtual ~ChatControllerBase();
 			void showChatWindow();
 			void activateChatWindow();
-			void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
+			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
 			void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
 			std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
 			void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index dba8565..d6010e9 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -43,6 +43,7 @@
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swiften/Client/StanzaChannel.h>
 #include <Swift/Controllers/WhiteboardManager.h>
+#include <Swiften/Client/ClientBlockListManager.h>
 
 namespace Swift {
 
@@ -75,7 +76,8 @@ ChatsManager::ChatsManager(
 		SettingsProvider* settings,
 		HistoryController* historyController,
 		WhiteboardManager* whiteboardManager,
-		HighlightManager* highlightManager) :
+		HighlightManager* highlightManager,
+		ClientBlockListManager* clientBlockListManager) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -88,7 +90,8 @@ ChatsManager::ChatsManager(
 			settings_(settings),
 			historyController_(historyController),
 			whiteboardManager_(whiteboardManager),
-			highlightManager_(highlightManager) {
+			highlightManager_(highlightManager),
+			clientBlockListManager_(clientBlockListManager) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -523,7 +526,7 @@ 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_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_);
+	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_);
 	chatControllers_[contact] = controller;
 	controller->setAvailableServerFeatures(serverDiscoInfo_);
 	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 55e62b9..4d7e9a8 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -51,10 +51,11 @@ namespace Swift {
 	class WhiteboardManager;
 	class HistoryController;
 	class HighlightManager;
+	class ClientBlockListManager;
 	
 	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* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager);
+			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* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -138,5 +139,6 @@ namespace Swift {
 			HistoryController* historyController_;
 			WhiteboardManager* whiteboardManager_;
 			HighlightManager* highlightManager_;
+			ClientBlockListManager* clientBlockListManager_;
 	};
 }
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index dd90d66..84a407c 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -10,48 +10,49 @@
 
 #include <boost/bind.hpp>
 
-#include "Swift/Controllers/Chat/ChatsManager.h"
-
-#include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/Settings/DummySettingsProvider.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h"
-#include "Swiften/Client/Client.h"
-#include "Swiften/Disco/EntityCapsManager.h"
-#include "Swiften/Disco/CapsProvider.h"
-#include "Swiften/MUC/MUCManager.h"
-#include "Swift/Controllers/Chat/ChatController.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swift/Controllers/Chat/MUCController.h"
-#include "Swiften/Presence/StanzaChannelPresenceSender.h"
-#include "Swiften/Avatars/NullAvatarManager.h"
-#include "Swiften/Avatars/AvatarMemoryStorage.h"
-#include "Swiften/VCards/VCardManager.h"
-#include "Swiften/VCards/VCardMemoryStorage.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swiften/Presence/DirectedPresenceSender.h"
-#include "Swiften/Roster/XMPPRosterImpl.h"
-#include "Swift/Controllers/UnitTest/MockChatWindow.h"
-#include "Swiften/Client/DummyStanzaChannel.h"
-#include "Swiften/Queries/DummyIQChannel.h"
-#include "Swiften/Presence/PresenceOracle.h"
-#include "Swiften/Jingle/JingleSessionManager.h"
-#include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include <Swift/Controllers/Chat/ChatsManager.h>
+
+#include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/Settings/DummySettingsProvider.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
+#include <Swiften/Client/Client.h>
+#include <Swiften/Disco/EntityCapsManager.h>
+#include <Swiften/Disco/CapsProvider.h>
+#include <Swiften/MUC/MUCManager.h>
+#include <Swift/Controllers/Chat/ChatController.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/Chat/MUCController.h>
+#include <Swiften/Presence/StanzaChannelPresenceSender.h>
+#include <Swiften/Avatars/NullAvatarManager.h>
+#include <Swiften/Avatars/AvatarMemoryStorage.h>
+#include <Swiften/VCards/VCardManager.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Presence/DirectedPresenceSender.h>
+#include <Swiften/Roster/XMPPRosterImpl.h>
+#include <Swift/Controllers/UnitTest/MockChatWindow.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Queries/DummyIQChannel.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Jingle/JingleSessionManager.h>
+#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/ProfileSettingsProvider.h>
-#include "Swift/Controllers/FileTransfer/FileTransferOverview.h"
-#include "Swiften/Elements/DeliveryReceiptRequest.h"
-#include "Swiften/Elements/DeliveryReceipt.h"
+#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
 #include <Swiften/Base/Algorithm.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/WhiteboardManager.h>
 #include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+#include <Swiften/Client/ClientBlockListManager.h>
 
 using namespace Swift;
 
@@ -109,7 +110,8 @@ public:
 		highlightManager_ = new HighlightManager(settings_);
 
 		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_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_);
+		clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_);
 
 		manager_->setAvatarManager(avatarManager_);
 	}
@@ -120,6 +122,7 @@ public:
 		delete profileSettings_;
 		delete avatarManager_;
 		delete manager_;
+		delete clientBlockListManager_;
 		delete ftOverview_;
 		delete ftManager_;
 		delete wbSessionManager_;
@@ -484,6 +487,7 @@ private:
 	WhiteboardSessionManager* wbSessionManager_;
 	WhiteboardManager* wbManager_;
 	HighlightManager* highlightManager_;
+	ClientBlockListManager* clientBlockListManager_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 0c9c09c..32d045d 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -87,6 +87,8 @@
 #include <Swiften/Client/StanzaChannel.h>
 #include <Swift/Controllers/HighlightManager.h>
 #include <Swift/Controllers/HighlightEditorController.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swift/Controllers/BlockListController.h>
 
 namespace Swift {
 
@@ -130,6 +132,7 @@ MainController::MainController(
 	historyViewController_ = NULL;
 	eventWindowController_ = NULL;
 	profileController_ = NULL;
+	blockListController_ = NULL;
 	showProfileController_ = NULL;
 	contactEditController_ = NULL;
 	userSearchControllerChat_ = NULL;
@@ -321,11 +324,13 @@ void MainController::handleConnected() {
 		client_->getFileTransferManager()->startListeningOnPort(randomPort);
 		ftOverview_ = new FileTransferOverview(client_->getFileTransferManager());
 		fileTransferListController_->setFileTransferOverview(ftOverview_);
-		rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_);
+		rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager());
 		rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));
 		rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this));
 		rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this));
 
+		blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_);
+
 		contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_);
 		whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager());
 
@@ -337,9 +342,9 @@ void MainController::handleConnected() {
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 		historyController_ = new HistoryController(storages_->getHistoryStorage());
 		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
-		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(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_);
+		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(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager());
 #else
-		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(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_);
+		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(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager());
 #endif
 		
 		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
@@ -731,6 +736,10 @@ void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo>
 	if (!error) {
 		chatsManager_->setServerDiscoInfo(info);
 		adHocManager_->setServerDiscoInfo(info);
+		if (info->hasFeature(DiscoInfo::BlockingCommandFeature)) {
+			rosterController_->getWindow()->setBlockingCommandAvailable(true);
+			rosterController_->initBlockingCommand();
+		}
 	}
 }
 
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index 4f37e12..d60805a 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -74,6 +74,7 @@ namespace Swift {
 	class WhiteboardManager;
 	class HighlightManager;
 	class HighlightEditorController;
+	class BlockListController;
 
 	class MainController {
 		public:
@@ -154,6 +155,7 @@ namespace Swift {
 			HistoryViewController* historyViewController_;
 			HistoryController* historyController_;
 			FileTransferListController* fileTransferListController_;
+			BlockListController* blockListController_;
 			ChatsManager* chatsManager_;
 			ProfileController* profileController_;
 			ShowProfileController* showProfileController_;
diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp
index f301552..bf85167 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.cpp
+++ b/Swift/Controllers/Roster/ContactRosterItem.cpp
@@ -16,7 +16,7 @@
 namespace Swift {
 
 
-ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID) {
+ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID), blockState_(BlockingNotSupported) {
 }
 
 ContactRosterItem::~ContactRosterItem() {
@@ -134,6 +134,14 @@ bool ContactRosterItem::supportsFeature(const Feature feature) const {
 	return features_.find(feature) != features_.end();
 }
 
+void ContactRosterItem::setBlockState(BlockState state) {
+	blockState_ = state;
+}
+
+ContactRosterItem::BlockState ContactRosterItem::blockState() const {
+	return blockState_;
+}
+
 }
 
 
diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h
index 247c606..fc65d6d 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.h
+++ b/Swift/Controllers/Roster/ContactRosterItem.h
@@ -28,6 +28,12 @@ class ContactRosterItem : public RosterItem {
 			FileTransferFeature,
 			WhiteboardFeature
 		};
+
+		enum BlockState {
+			BlockingNotSupported,
+			IsBlocked,
+			IsUnblocked
+		};
 		
 	public:
 		ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent);
@@ -53,6 +59,9 @@ class ContactRosterItem : public RosterItem {
 		void setSupportedFeatures(const std::set<Feature>& features);
 		bool supportsFeature(Feature feature) const;
 
+		void setBlockState(BlockState state);
+		BlockState blockState() const;
+
 	private:
 		JID jid_;
 		JID displayJID_;
@@ -63,6 +72,7 @@ class ContactRosterItem : public RosterItem {
 		boost::shared_ptr<Presence> shownPresence_;
 		std::vector<std::string> groups_;
 		std::set<Feature> features_;
+		BlockState blockState_;
 };
 
 }
diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp
index b5c5998..3d0ca2e 100644
--- a/Swift/Controllers/Roster/Roster.cpp
+++ b/Swift/Controllers/Roster/Roster.cpp
@@ -22,7 +22,7 @@
 
 namespace Swift {
 
-Roster::Roster(bool sortByStatus, bool fullJIDMapping) {
+Roster::Roster(bool sortByStatus, bool fullJIDMapping) : blockingSupported_(false) {
 	sortByStatus_ = sortByStatus;
 	fullJIDMapping_ = fullJIDMapping;
 	root_ = new GroupRosterItem("Dummy-Root", NULL, sortByStatus_);
@@ -71,6 +71,28 @@ void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterIt
 	}
 }
 
+void Roster::setBlockedState(const std::vector<JID> &jids, ContactRosterItem::BlockState state) {
+	if (!blockingSupported_ ) {
+		foreach(ItemMap::value_type i, itemMap_) {
+			foreach(ContactRosterItem* item, i.second) {
+				item->setBlockState(ContactRosterItem::IsUnblocked);
+			}
+		}
+	}
+
+	foreach(const JID& jid, jids) {
+		ItemMap::const_iterator i = itemMap_.find(fullJIDMapping_ ? jid : jid.toBare());
+		if (i == itemMap_.end()) {
+			continue;
+		}
+		foreach(ContactRosterItem* item, i->second) {
+			item->setBlockState(state);
+		}
+	}
+
+	blockingSupported_ = true;
+}
+
 void Roster::removeGroup(const std::string& group) {
 	root_->removeGroupChild(group);
 }
@@ -87,6 +109,9 @@ void Roster::addContact(const JID& jid, const JID& displayJID, const std::string
 	GroupRosterItem* group(getGroup(groupName));
 	ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group);	
 	item->setAvatarPath(avatarPath);
+	if (blockingSupported_) {
+		item->setBlockState(ContactRosterItem::IsUnblocked);
+	}
 	group->addChild(item);
 	ItemMap::iterator i = itemMap_.insert(std::make_pair(fullJIDMapping_ ? jid : jid.toBare(), std::vector<ContactRosterItem*>())).first;
 	if (!i->second.empty()) {
diff --git a/Swift/Controllers/Roster/Roster.h b/Swift/Controllers/Roster/Roster.h
index 74547d6..91a152f 100644
--- a/Swift/Controllers/Roster/Roster.h
+++ b/Swift/Controllers/Roster/Roster.h
@@ -45,6 +45,7 @@ class Roster {
 		boost::signal<void (RosterItem*)> onDataChanged;
 		GroupRosterItem* getGroup(const std::string& groupName);
 		void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features);
+		void setBlockedState(const std::vector<JID>& jids, ContactRosterItem::BlockState state);
 
 	private:
 		void handleDataChanged(RosterItem* item);
@@ -58,6 +59,7 @@ class Roster {
 		ItemMap itemMap_;
 		bool fullJIDMapping_;
 		bool sortByStatus_;
+		bool blockingSupported_;
 };
 
 }
diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp
index ec52993..d09ef4c 100644
--- a/Swift/Controllers/Roster/RosterController.cpp
+++ b/Swift/Controllers/Roster/RosterController.cpp
@@ -44,14 +44,15 @@
 #include <Swiften/Disco/EntityCapsManager.h>
 #include <Swiften/Jingle/JingleSessionManager.h>
 #include <Swift/Controllers/SettingConstants.h>
+#include <Swiften/Client/ClientBlockListManager.h>
 
 namespace Swift {
 
 /**
  * The controller does not gain ownership of these parameters.
  */
-RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview)
- : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview) {
+RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager)
+	: myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) {
 	assert(fileTransferOverview);
 	iqRouter_ = iqRouter;
 	presenceOracle_ = presenceOracle;
@@ -183,6 +184,20 @@ void RosterController::handleSettingChanged(const std::string& settingPath) {
 	}
 }
 
+void RosterController::handleBlockingStateChanged() {
+	if (clientBlockListManager_->getBlockList()->getState() == BlockList::Available) {
+		roster_->setBlockedState(clientBlockListManager_->getBlockList()->getItems(), ContactRosterItem::IsBlocked);
+	}
+}
+
+void RosterController::handleBlockingItemAdded(const JID& jid) {
+	roster_->setBlockedState(std::vector<JID>(1, jid), ContactRosterItem::IsBlocked);
+}
+
+void RosterController::handleBlockingItemRemoved(const JID& jid) {
+	roster_->setBlockedState(std::vector<JID>(1, jid), ContactRosterItem::IsUnblocked);
+}
+
 void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 	if (boost::shared_ptr<AddContactUIEvent> addContactEvent = boost::dynamic_pointer_cast<AddContactUIEvent>(event)) {
 		RosterItemPayload item;
@@ -256,6 +271,18 @@ void RosterController::updateItem(const XMPPRosterItem& item) {
 	request->send();
 }
 
+void RosterController::initBlockingCommand() {
+	boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+
+	blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&RosterController::handleBlockingStateChanged, this));
+	blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&RosterController::handleBlockingItemAdded, this, _1));
+	blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&RosterController::handleBlockingItemRemoved, this, _1));
+
+	if (blockList->getState() == BlockList::Available) {
+		roster_->setBlockedState(blockList->getItems(), ContactRosterItem::IsBlocked);
+	}
+}
+
 void RosterController::handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload) {
 	if (!error) {
 		return;
diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h
index ec07574..06b551e 100644
--- a/Swift/Controllers/Roster/RosterController.h
+++ b/Swift/Controllers/Roster/RosterController.h
@@ -39,10 +39,11 @@ namespace Swift {
 	class NickManager;
 	class EntityCapsProvider;
 	class FileTransferManager;
-	
+	class ClientBlockListManager;
+
 	class RosterController {
 		public:
-			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview);
+			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager);
 			~RosterController();
 			void showRosterWindow();
 			MainWindow* getWindow() {return mainWindow_;}
@@ -57,6 +58,8 @@ namespace Swift {
 			void setContactGroups(const JID& jid, const std::vector<std::string>& groups);
 			void updateItem(const XMPPRosterItem&);
 
+			void initBlockingCommand();
+
 		private:
 			void handleOnJIDAdded(const JID &jid);
 			void handleRosterCleared();
@@ -76,6 +79,10 @@ namespace Swift {
 			void handleOnCapsChanged(const JID& jid);
 			void handleSettingChanged(const std::string& settingPath);
 
+			void handleBlockingStateChanged();
+			void handleBlockingItemAdded(const JID& jid);
+			void handleBlockingItemRemoved(const JID& jid);
+
 			JID myJID_;
 			XMPPRoster* xmppRoster_;
 			MainWindowFactory* mainWindowFactory_;
@@ -94,7 +101,11 @@ namespace Swift {
 			UIEventStream* uiEventStream_;
 			EntityCapsProvider* entityCapsManager_;
 			FileTransferOverview* ftOverview_;
+			ClientBlockListManager* clientBlockListManager_;
 			
+			boost::bsignals::scoped_connection blockingOnStateChangedConnection_;
+			boost::bsignals::scoped_connection blockingOnItemAddedConnection_;
+			boost::bsignals::scoped_connection blockingOnItemRemovedConnection_;
 			boost::bsignals::scoped_connection changeStatusConnection_;
 			boost::bsignals::scoped_connection signOutConnection_;
 			boost::bsignals::scoped_connection uiEventConnection_;
diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
index e439c78..b0034e6 100644
--- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
+++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
@@ -37,6 +37,7 @@
 #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
 #include <Swiften/Base/Algorithm.h>
 #include <Swiften/EventLoop/DummyEventLoop.h>
+#include <Swiften/Client/ClientBlockListManager.h>
 
 using namespace Swift;
 
@@ -82,12 +83,14 @@ class RosterControllerTest : public CppUnit::TestFixture {
 
 			ftManager_ = new DummyFileTransferManager();
 			ftOverview_ = new FileTransferOverview(ftManager_);
-			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_);
+			clientBlockListManager_ = new ClientBlockListManager(router_);
+			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_);
 			mainWindow_ = mainWindowFactory_->last;
 		}
 
 		void tearDown() {
 			delete rosterController_;
+			delete clientBlockListManager_;
 			delete ftManager_;
 			delete jingleSessionManager_;
 			
@@ -337,6 +340,7 @@ class RosterControllerTest : public CppUnit::TestFixture {
 		JingleSessionManager* jingleSessionManager_;
 		FileTransferManager* ftManager_;
 		FileTransferOverview* ftOverview_;
+		ClientBlockListManager* clientBlockListManager_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest);
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 26b9334..6f36a52 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -49,6 +49,7 @@ if env["SCONS_STAGE"] == "build" :
 			"HistoryViewController.cpp",
 			"HistoryController.cpp",
 			"FileTransferListController.cpp",
+			"BlockListController.cpp",
 			"StatusTracker.cpp",
 			"PresenceNotifier.cpp",
 			"EventNotifier.cpp",
diff --git a/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h
new file mode 100644
index 0000000..d29cb4f
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+
+class RequestBlockListDialogUIEvent : public UIEvent {
+};
+
+}
diff --git a/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h
new file mode 100644
index 0000000..9b7abcb
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+
+class RequestChangeBlockStateUIEvent : public UIEvent {
+	public:
+		enum BlockState {
+			Blocked,
+			Unblocked
+		};
+
+	public:
+		RequestChangeBlockStateUIEvent(BlockState newState, const JID& contact) : state_(newState), contact_(contact) {}
+
+		BlockState getBlockState() const {
+			return state_;
+		}
+
+		JID getContact() const {
+			return contact_;
+		}
+	private:
+		BlockState state_;
+		JID contact_;
+};
+
+}
diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h
new file mode 100644
index 0000000..60a1c11
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Base/boost_bsignals.h>
+
+namespace Swift {
+
+	class ClientBlockListManager;
+
+	class BlockListEditorWidget {
+		public:
+			virtual ~BlockListEditorWidget() {}
+
+			virtual void show() = 0;
+
+			virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs) = 0;
+			virtual void setBusy(bool isBusy) = 0;
+
+			virtual std::vector<JID> getCurrentBlockList() const = 0;
+
+			boost::signal<void (const std::vector<JID>& /* blockedJID */)> onSetNewBlockList;
+	};
+
+}
diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h
new file mode 100644
index 0000000..eb91ac1
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+
+	class BlockListEditorWidget;
+
+	class BlockListEditorWidgetFactory {
+		public:
+			virtual ~BlockListEditorWidgetFactory() {}
+
+			virtual BlockListEditorWidget* createBlockListEditorWidget() = 0;
+	};
+
+}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index d6b3656..b8e6722 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -39,6 +39,7 @@ namespace Swift {
 			enum RoomAction {ChangeSubject, Configure, Affiliations, Destroy, Invite};
 			enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed};
 			enum WhiteboardSessionState {WhiteboardAccepted, WhiteboardTerminated, WhiteboardRejected};
+			enum BlockingState {BlockingUnsupported, IsBlocked, IsUnblocked};
 			ChatWindow() {}
 			virtual ~ChatWindow() {}
 
@@ -89,6 +90,7 @@ namespace Swift {
 			virtual void setSubject(const std::string& subject) = 0;
 			virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) = 0;
 			virtual void setAvailableRoomActions(const std::vector<RoomAction> &actions) = 0;
+			virtual void setBlockingState(BlockingState state) = 0;
 			/**
 			 * Set an alert on the window.
 			 * @param alertText Description of alert (required).
diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h
index 2df2c10..3b10041 100644
--- a/Swift/Controllers/UIInterfaces/MainWindow.h
+++ b/Swift/Controllers/UIInterfaces/MainWindow.h
@@ -34,6 +34,7 @@ namespace Swift {
 			/** Must be able to cope with NULL to clear the roster */
 			virtual void setRosterModel(Roster* roster) = 0;
 			virtual void setConnecting() = 0;
+			virtual void setBlockingCommandAvailable(bool isAvailable) = 0;
 			virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) = 0;
 			virtual void setStreamEncryptionStatus(bool tlsInPlaceAndValid) = 0;
 			virtual void openCertificateDialog(const std::vector<Certificate::ref>& chain) = 0;
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index dcd1779..990dc98 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -22,6 +22,7 @@
 #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h>
 #include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h>
+#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h>
 
 namespace Swift {
 	class UIFactory : 
@@ -40,7 +41,8 @@ namespace Swift {
 			public AdHocCommandWindowFactory,
 			public FileTransferListWidgetFactory,
 			public WhiteboardWindowFactory,
-			public HighlightEditorWidgetFactory {
+			public HighlightEditorWidgetFactory,
+			public BlockListEditorWidgetFactory {
 		public:
 			virtual ~UIFactory() {}
 	};
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 84aaa04..8af65f7 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -60,6 +60,8 @@ namespace Swift {
 			virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {}
 			virtual InviteToChatWindow* createInviteToChatWindow() {return NULL;}
 
+			virtual void setBlockingState(BlockingState) {}
+
 			std::string name_;
 			std::string lastMessageBody_;
 			std::vector<SecurityLabelsCatalog::Item> labels_;
diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h
index 19ab522..69a4e25 100644
--- a/Swift/Controllers/UnitTest/MockMainWindow.h
+++ b/Swift/Controllers/UnitTest/MockMainWindow.h
@@ -24,6 +24,7 @@ namespace Swift {
 			virtual void setConnecting() {}
 			virtual void setStreamEncryptionStatus(bool /*tlsInPlaceAndValid*/) {}
 			virtual void openCertificateDialog(const std::vector<Certificate::ref>& /*chain*/) {}
+			virtual void setBlockingCommandAvailable(bool /*isAvailable*/) {}
 			Roster* roster;
 
 	};
diff --git a/Swift/QtUI/QtBlockListEditorWindow.cpp b/Swift/QtUI/QtBlockListEditorWindow.cpp
new file mode 100644
index 0000000..63e8d1f
--- /dev/null
+++ b/Swift/QtUI/QtBlockListEditorWindow.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <QtBlockListEditorWindow.h>
+#include <ui_QtBlockListEditorWindow.h>
+
+#include <boost/bind.hpp>
+
+#include <QLineEdit>
+#include <QMovie>
+#include <QShortcut>
+#include <QStyledItemDelegate>
+#include <QValidator>
+
+#include <Swift/QtUI/QtUtilities.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Base/foreach.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+
+class QtJIDValidator : public QValidator {
+	public:
+		QtJIDValidator(QObject* parent) : QValidator(parent) {}
+		virtual ~QtJIDValidator() {}
+		virtual QValidator::State validate(QString& input, int&) const {
+			return JID(Q2PSTRING(input)).isValid() ? Acceptable : Intermediate;
+		}
+};
+
+class QtJIDValidatedItemDelegate : public QItemDelegate {
+	public:
+		QtJIDValidatedItemDelegate(QObject* parent) : QItemDelegate(parent) {}
+		virtual ~QtJIDValidatedItemDelegate() {}
+
+		virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const {
+			QLineEdit *editor = new QLineEdit(parent);
+			editor->setValidator(new QtJIDValidator(editor));
+			return editor;
+		}
+
+		void setEditorData(QWidget *editor, const QModelIndex &index) const {
+			QString value = index.model()->data(index, Qt::EditRole).toString();
+
+			QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
+			lineEdit->setText(value);
+		}
+
+		void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
+			QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
+			QString currentValue = lineEdit->text();
+			int pos = 0;
+			if (lineEdit->validator()->validate(currentValue, pos) == QValidator::Acceptable) {
+				model->setData(index, lineEdit->text(), Qt::EditRole);
+			} else {
+				model->setData(index, QString(), Qt::EditRole);
+			}
+		}
+
+		void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const {
+			editor->setGeometry(option.rect);
+		}
+};
+
+QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow) {
+	ui->setupUi(this);
+	new QShortcut(QKeySequence::Close, this, SLOT(close()));
+	ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this));
+
+	itemDelegate = new QtRemovableItemDelegate(style());
+
+	connect(ui->savePushButton, SIGNAL(clicked()), SLOT(applyChanges()));
+
+	ui->blockListTreeWidget->setColumnCount(2);
+	ui->blockListTreeWidget->header()->setStretchLastSection(false);
+	int closeIconWidth = ui->blockListTreeWidget->fontMetrics().height();
+	ui->blockListTreeWidget->header()->resizeSection(1, closeIconWidth);
+
+#if QT_VERSION >= 0x050000
+	ui->blockListTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+#else
+	ui->blockListTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch);
+#endif
+
+	ui->blockListTreeWidget->setHeaderHidden(true);
+	ui->blockListTreeWidget->setRootIsDecorated(false);
+	ui->blockListTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked);
+	ui->blockListTreeWidget->setItemDelegateForColumn(0, new QtJIDValidatedItemDelegate(this));
+	ui->blockListTreeWidget->setItemDelegateForColumn(1, itemDelegate);
+	connect(ui->blockListTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int)));
+
+	QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << "");
+	item->setFlags(item->flags() | Qt::ItemIsEditable);
+	ui->blockListTreeWidget->addTopLevelItem(item);
+}
+
+QtBlockListEditorWindow::~QtBlockListEditorWindow() {
+}
+
+void QtBlockListEditorWindow::show() {
+	QWidget::show();
+	QWidget::activateWindow();
+}
+
+void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *, int) {
+	bool hasEmptyRow = false;
+	QList<QTreeWidgetItem*> rows = ui->blockListTreeWidget->findItems("", Qt::MatchFixedString);
+	foreach(QTreeWidgetItem* row, rows) {
+		if (row->text(0).isEmpty()) {
+			hasEmptyRow = true;
+		}
+	}
+
+	if (!hasEmptyRow) {
+		QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << "");
+		item->setFlags(item->flags() | Qt::ItemIsEditable);
+		ui->blockListTreeWidget->addTopLevelItem(item);
+	}
+}
+
+void QtBlockListEditorWindow::applyChanges() {
+	onSetNewBlockList(getCurrentBlockList());
+}
+
+void Swift::QtBlockListEditorWindow::setCurrentBlockList(const std::vector<JID> &blockedJIDs) {
+	ui->blockListTreeWidget->clear();
+
+	foreach(const JID& jid, blockedJIDs) {
+		QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(P2QSTRING(jid.toString())) << "");
+		item->setFlags(item->flags() | Qt::ItemIsEditable);
+		ui->blockListTreeWidget->addTopLevelItem(item);
+	}
+	handleItemChanged(0,0);
+}
+
+void Swift::QtBlockListEditorWindow::setBusy(bool isBusy) {
+	if (isBusy) {
+		ui->throbberLabel->movie()->start();
+		ui->throbberLabel->show();
+	} else {
+		ui->throbberLabel->movie()->stop();
+		ui->throbberLabel->hide();
+	}
+}
+
+std::vector<JID> Swift::QtBlockListEditorWindow::getCurrentBlockList() const {
+	std::vector<JID> futureBlockedJIDs;
+
+	for(int i=0; i < ui->blockListTreeWidget->topLevelItemCount(); ++i) {
+		QTreeWidgetItem* row = ui->blockListTreeWidget->topLevelItem(i);
+		if (!row->text(0).isEmpty()) {
+			futureBlockedJIDs.push_back(JID(Q2PSTRING(row->text(0))));
+		}
+	}
+	return futureBlockedJIDs;
+}
+
+}
diff --git a/Swift/QtUI/QtBlockListEditorWindow.h b/Swift/QtUI/QtBlockListEditorWindow.h
new file mode 100644
index 0000000..4b124a3
--- /dev/null
+++ b/Swift/QtUI/QtBlockListEditorWindow.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h>
+#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h>
+
+#include <QWidget>
+#include <QTreeWidgetItem>
+
+namespace Ui {
+	class QtBlockListEditorWindow;
+}
+
+namespace Swift {
+
+class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget {
+	Q_OBJECT
+
+	public:
+		QtBlockListEditorWindow();
+		virtual ~QtBlockListEditorWindow();
+
+		virtual void show();
+		virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs);
+		virtual void setBusy(bool isBusy);
+		virtual std::vector<JID> getCurrentBlockList() const;
+
+	private slots:
+		void handleItemChanged(QTreeWidgetItem*, int);
+		void applyChanges();
+
+	private:
+		Ui::QtBlockListEditorWindow* ui;
+		QtRemovableItemDelegate* itemDelegate;
+};
+
+}
diff --git a/Swift/QtUI/QtBlockListEditorWindow.ui b/Swift/QtUI/QtBlockListEditorWindow.ui
new file mode 100644
index 0000000..f71bbae
--- /dev/null
+++ b/Swift/QtUI/QtBlockListEditorWindow.ui
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtBlockListEditorWindow</class>
+ <widget class="QWidget" name="QtBlockListEditorWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>348</width>
+    <height>262</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Edit Block List</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="spacing">
+    <number>5</number>
+   </property>
+   <property name="margin">
+    <number>5</number>
+   </property>
+   <item>
+    <widget class="QTreeWidget" name="blockListTreeWidget">
+     <attribute name="headerVisible">
+      <bool>false</bool>
+     </attribute>
+     <column>
+      <property name="text">
+       <string notr="true">1</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QLabel" name="errorLabel">
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="throbberLabel">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::NoTextInteraction</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="savePushButton">
+       <property name="text">
+        <string>Save</string>
+       </property>
+       <property name="default">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 11e64ab..efdab99 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -24,6 +24,7 @@
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
 #include "QtChatWindowJSBridge.h"
 #include "QtUtilities.h"
 
@@ -47,6 +48,7 @@
 #include <QTextEdit>
 #include <QTime>
 #include <QUrl>
+#include <QToolButton>
 #include <QPushButton>
 #include <QFileDialog>
 #include <QMenu>
@@ -66,7 +68,7 @@ const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetrans
 const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite");
 
 
-QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons) {
+QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons), blockingState_(BlockingUnsupported) {
 	settings_ = settings;
 	unreadCount_ = 0;
 	idCounter_ = 0;
@@ -105,21 +107,18 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	alertLabel_->setStyleSheet(alertStyleSheet_);
 	alertWidget_->hide();
 
-	QBoxLayout* subjectLayout = new QBoxLayout(QBoxLayout::LeftToRight);
+	subjectLayout_ = new QBoxLayout(QBoxLayout::LeftToRight);
 	subject_ = new QLineEdit(this);
-	subjectLayout->addWidget(subject_);
+	subjectLayout_->addWidget(subject_);
 	setSubject("");
 	subject_->setReadOnly(true);
 
-	actionButton_ = new QPushButton(this);
+	QPushButton* actionButton_ = new QPushButton(this);
 	actionButton_->setIcon(QIcon(":/icons/actions.png"));
 	connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked()));
-	subjectLayout->addWidget(actionButton_);
-
 	subject_->hide();
-	actionButton_->hide();
 
-	layout->addLayout(subjectLayout);
+	layout->addLayout(subjectLayout_);
 
 	logRosterSplitter_ = new QSplitter(this);
 	logRosterSplitter_->setAutoFillBackground(true);
@@ -159,6 +158,12 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	correctingLabel_ = new QLabel(tr("Correcting"), this);
 	inputBarLayout->addWidget(correctingLabel_);
 	correctingLabel_->hide();
+
+	// using an extra layout to work around Qt margin glitches on OS X
+	QHBoxLayout* actionLayout = new QHBoxLayout();
+	actionLayout->addWidget(actionButton_);
+
+	inputBarLayout->addLayout(actionLayout);
 	layout->addLayout(inputBarLayout);
 
 	inputClearing_ = false;
@@ -415,7 +420,6 @@ void QtChatWindow::convertToMUC() {
 	setAcceptDrops(false);
 	treeWidget_->show();
 	subject_->show();
-	actionButton_->show();
 }
 
 void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) {
@@ -504,8 +508,7 @@ QString QtChatWindow::linkimoticonify(const QString& message) const {
 	return messageHTML;
 }
 
-QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight)
-{
+QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight) {
 	QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor()));
 	QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground()));
 	if (color.isEmpty()) {
@@ -922,15 +925,26 @@ void QtChatWindow::handleActionButtonClicked() {
 	QAction* destroy = NULL;
 	QAction* invite = NULL;
 
-	foreach(ChatWindow::RoomAction availableAction, availableRoomActions_)
-	{
-		switch(availableAction)
+	QAction* block = NULL;
+	QAction* unblock = NULL;
+
+	if (availableRoomActions_.empty()) {
+		if (blockingState_ == IsBlocked) {
+			unblock = contextMenu.addAction(tr("Unblock"));
+		} else if (blockingState_ == IsUnblocked) {
+			block = contextMenu.addAction(tr("Block"));
+		}
+	} else {
+		foreach(ChatWindow::RoomAction availableAction, availableRoomActions_)
 		{
-			case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break;
-			case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break;
-			case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break;
-			case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break;
-			case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break;
+			switch(availableAction)
+			{
+				case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break;
+				case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break;
+				case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break;
+				case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break;
+				case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break;
+			}
 		}
 	}
 
@@ -970,6 +984,12 @@ void QtChatWindow::handleActionButtonClicked() {
 	else if (result == invite) {
 		onInvitePersonToThisMUCRequest();
 	}
+	else if (result == block) {
+		eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, JID(Q2PSTRING(contact_))));
+	}
+	else if (result == unblock) {
+		eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, JID(Q2PSTRING(contact_))));
+	}
 }
 
 void QtChatWindow::handleAffiliationEditorAccepted() {
@@ -981,11 +1001,14 @@ void QtChatWindow::setAffiliations(MUCOccupant::Affiliation affiliation, const s
 	affiliationEditor_->setAffiliations(affiliation, jids);
 }
 
-void QtChatWindow::setAvailableRoomActions(const std::vector<RoomAction> &actions)
-{
+void QtChatWindow::setAvailableRoomActions(const std::vector<RoomAction>& actions) {
 	availableRoomActions_ = actions;
 }
 
+void QtChatWindow::setBlockingState(BlockingState state) {
+	blockingState_ = state;
+}
+
 void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
 	if (mucConfigurationWindow_) {
 		delete mucConfigurationWindow_.data();
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 4abd456..3ccea31 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -132,7 +132,8 @@ namespace Swift {
 			void showRoomConfigurationForm(Form::ref);
 			void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true);
 			void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&);
-			void setAvailableRoomActions(const std::vector<RoomAction> &actions);
+			void setAvailableRoomActions(const std::vector<RoomAction>& actions);
+			void setBlockingState(BlockingState state);
 
 			InviteToChatWindow* createInviteToChatWindow();
 
@@ -209,6 +210,7 @@ namespace Swift {
 			QtChatTheme* theme_;
 			QtTextEdit* input_;
 			QWidget* midBar_; 
+			QBoxLayout* subjectLayout_;
 			QComboBox* labelsWidget_;
 			QtOccupantListWidget* treeWidget_;
 			QLabel* correctingLabel_;
@@ -217,7 +219,6 @@ namespace Swift {
 			QPushButton* alertButton_;
 			TabComplete* completer_;
 			QLineEdit* subject_;
-			QPushButton* actionButton_;
 			bool isCorrection_;
 			bool previousMessageWasSelf_;
 			PreviousMessageKind previousMessageKind_;
@@ -240,5 +241,6 @@ namespace Swift {
 			bool showEmoticons_;
 			QPalette defaultLabelsPalette_;
 			LabelModel* labelModel_;
+			BlockingState blockingState_;
 	};
 }
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 9749397..572b06f 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -33,6 +33,7 @@
 #include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h>
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
 #include <Swift/QtUI/QtUISettingConstants.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swiften/Base/Platform.h>
@@ -138,6 +139,10 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr
 	connect(viewLogsAction, SIGNAL(triggered()), SLOT(handleViewLogsAction()));
 	actionsMenu->addAction(viewLogsAction);
 #endif
+	openBlockingListEditor_ = new QAction(tr("Edit &Blocking List…"), this);
+	connect(openBlockingListEditor_, SIGNAL(triggered()), SLOT(handleEditBlockingList()));
+	actionsMenu->addAction(openBlockingListEditor_);
+	openBlockingListEditor_->setVisible(false);
 	addUserAction_ = new QAction(tr("&Add Contact…"), this);
 	connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool)));
 	actionsMenu->addAction(addUserAction_);
@@ -190,6 +195,10 @@ void QtMainWindow::handleShowCertificateInfo() {
 	onShowCertificateRequest();
 }
 
+void QtMainWindow::handleEditBlockingList() {
+	uiEventStream_->send(boost::make_shared<RequestBlockListDialogUIEvent>());
+}
+
 QtEventWindow* QtMainWindow::getEventWindow() {
 	return eventWindow_;
 }
@@ -359,5 +368,9 @@ void QtMainWindow::setAvailableAdHocCommands(const std::vector<DiscoItems::Item>
 	}
 }
 
+void QtMainWindow::setBlockingCommandAvailable(bool isAvailable) {
+	openBlockingListEditor_->setVisible(isAvailable);
+}
+
 }
 
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index 99bc675..3e6e1d3 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -53,6 +53,7 @@ namespace Swift {
 			QtChatListWindow* getChatListWindow();
 			void setRosterModel(Roster* roster);
 			void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands);
+			void setBlockingCommandAvailable(bool isAvailable);
 		private slots:
 			void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage);
 			void handleSettingChanged(const std::string& settingPath);
@@ -72,6 +73,7 @@ namespace Swift {
 			void handleTabChanged(int index);
 			void handleToggleRequestDeliveryReceipts(bool enabled);
 			void handleShowCertificateInfo();
+			void handleEditBlockingList();
 
 		private:
 			SettingsProvider* settings_;
@@ -85,6 +87,7 @@ namespace Swift {
 			QAction* showOfflineAction_;
 			QAction* compactRosterAction_;
 			QAction* showEmoticonsAction_;
+			QAction* openBlockingListEditor_;
 			QAction* toggleRequestDeliveryReceipts_;
 			QMenu* serverAdHocMenu_;
 			QtTabWidget* tabs_;
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 7ec25df..0c7fbc2 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -4,34 +4,35 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "QtUIFactory.h"
+#include <Swift/QtUI/QtUIFactory.h>
 
 #include <QSplitter>
 
-#include "QtXMLConsoleWidget.h"
-#include "QtChatTabs.h"
-#include "QtMainWindow.h"
-#include "QtLoginWindow.h"
-#include "QtSystemTray.h"
-#include "QtSettingsProvider.h"
-#include "QtMainWindow.h"
-#include "QtChatWindow.h"
-#include "QtJoinMUCWindow.h"
-#include "QtChatWindowFactory.h"
-#include "QtSwiftUtil.h"
-#include "MUCSearch/QtMUCSearchWindow.h"
-#include "UserSearch/QtUserSearchWindow.h"
-#include "QtProfileWindow.h"
-#include "QtContactEditWindow.h"
-#include "QtAdHocCommandWindow.h"
-#include "QtFileTransferListWidget.h"
-#include <QtHighlightEditorWidget.h>
-#include "Whiteboard/QtWhiteboardWindow.h"
+#include <Swift/QtUI/QtXMLConsoleWidget.h>
+#include <Swift/QtUI/QtChatTabs.h>
+#include <Swift/QtUI/QtMainWindow.h>
+#include <Swift/QtUI/QtLoginWindow.h>
+#include <Swift/QtUI/QtSystemTray.h>
+#include <Swift/QtUI/QtSettingsProvider.h>
+#include <Swift/QtUI/QtMainWindow.h>
+#include <Swift/QtUI/QtChatWindow.h>
+#include <Swift/QtUI/QtJoinMUCWindow.h>
+#include <Swift/QtUI/QtChatWindowFactory.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/MUCSearch/QtMUCSearchWindow.h>
+#include <Swift/QtUI/UserSearch/QtUserSearchWindow.h>
+#include <Swift/QtUI/QtProfileWindow.h>
+#include <Swift/QtUI/QtContactEditWindow.h>
+#include <Swift/QtUI/QtAdHocCommandWindow.h>
+#include <Swift/QtUI/QtFileTransferListWidget.h>
+#include <Swift/QtUI/QtHighlightEditorWidget.h>
+#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h>
 #include <Swift/Controllers/Settings/SettingsProviderHierachy.h>
 #include <Swift/QtUI/QtUISettingConstants.h>
-#include <QtHistoryWindow.h>
+#include <Swift/QtUI/QtHistoryWindow.h>
 #include <Swiften/Whiteboard/WhiteboardSession.h>
-#include <QtSingleWindow.h>
+#include <Swift/QtUI/QtSingleWindow.h>
+#include <Swift/QtUI/QtBlockListEditorWindow.h>
 
 namespace Swift {
 
@@ -167,6 +168,10 @@ HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() {
 	return new QtHighlightEditorWidget();
 }
 
+BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() {
+	return new QtBlockListEditorWindow();
+}
+
 void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) {
 	new QtAdHocCommandWindow(command);
 }
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index a1baa82..662c78e 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -49,6 +49,7 @@ namespace Swift {
 			virtual FileTransferListWidget* createFileTransferListWidget();
 			virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession);
 			virtual HighlightEditorWidget* createHighlightEditorWidget();
+			virtual BlockListEditorWidget* createBlockListEditorWidget();
 			virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
 
 		private slots:
diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp
index b783ff6..6bf3989 100644
--- a/Swift/QtUI/Roster/QtRosterWidget.cpp
+++ b/Swift/QtUI/Roster/QtRosterWidget.cpp
@@ -4,24 +4,25 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Roster/QtRosterWidget.h"
+#include <Swift/QtUI/Roster/QtRosterWidget.h>
 
 #include <QContextMenuEvent>
 #include <QMenu>
 #include <QInputDialog>
 #include <QFileDialog>
 
-#include "Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h"
-#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h"
-#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h"
-#include "Swift/Controllers/UIEvents/SendFileUIEvent.h"
-#include "Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h"
-#include "Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h"
-#include "QtContactEditWindow.h"
-#include "Swift/Controllers/Roster/ContactRosterItem.h"
-#include "Swift/Controllers/Roster/GroupRosterItem.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "QtSwiftUtil.h"
+#include <Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h>
+#include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h>
+#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h>
+#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
+#include <Swift/QtUI/QtContactEditWindow.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
 
 namespace Swift {
 
@@ -59,6 +60,17 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) {
 		QAction* editContact = contextMenu.addAction(tr("Edit…"));
 		QAction* removeContact = contextMenu.addAction(tr("Remove"));
 		QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile"));
+
+		QAction* unblockContact = NULL;
+		if (contact->blockState() == ContactRosterItem::IsBlocked) {
+			unblockContact = contextMenu.addAction(tr("Unblock"));
+		}
+
+		QAction* blockContact = NULL;
+		if (contact->blockState() == ContactRosterItem::IsUnblocked) {
+			blockContact = contextMenu.addAction(tr("Block"));
+		}
+
 #ifdef SWIFT_EXPERIMENTAL_FT
 		QAction* sendFile = NULL;
 		if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) {
@@ -71,6 +83,7 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) {
 			startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat"));
 		}
 #endif
+
 		QAction* result = contextMenu.exec(event->globalPos());
 		if (result == editContact) {
 			eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
@@ -83,6 +96,12 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) {
 		else if (result == showProfileForContact) {
 			eventStream_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(contact->getJID()));
 		}
+		else if (unblockContact && result == unblockContact) {
+			eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID()));
+		}
+		else if (blockContact && result == blockContact) {
+			eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, contact->getJID()));
+		}
 #ifdef SWIFT_EXPERIMENTAL_FT
 		else if (sendFile && result == sendFile) {
 			QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;"));
diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp
index 2909c05..8f39f35 100644
--- a/Swift/QtUI/Roster/RosterModel.cpp
+++ b/Swift/QtUI/Roster/RosterModel.cpp
@@ -162,6 +162,10 @@ QString RosterModel::getStatusText(RosterItem* item) const {
 QIcon RosterModel::getPresenceIcon(RosterItem* item) const {
 	ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
 	if (!contact) return QIcon();
+	if (contact->blockState() == ContactRosterItem::IsBlocked) {
+		return QIcon(":/icons/stop.png");
+	}
+
 	QString iconString;
 	switch (contact->getStatusShow()) {
 		case StatusShow::Online: iconString = "online";break;
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 0d69d6b..0bcab5e 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -94,6 +94,7 @@ sources = [
     "QtLoginWindow.cpp",
     "QtMainWindow.cpp",
     "QtProfileWindow.cpp",
+    "QtBlockListEditorWindow.cpp",
     "QtNameWidget.cpp",
     "QtSettingsProvider.cpp",
     "QtStatusWidget.cpp",
@@ -273,6 +274,7 @@ myenv.Uic4("QtHistoryWindow.ui")
 myenv.Uic4("QtConnectionSettings.ui")
 myenv.Uic4("QtHighlightRuleWidget.ui")
 myenv.Uic4("QtHighlightEditorWidget.ui")
+myenv.Uic4("QtBlockListEditorWindow.ui")
 myenv.Uic4("QtSpellCheckerWindow.ui")
 myenv.Qrc("DefaultTheme.qrc")
 myenv.Qrc("Swift.qrc")
diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc
index f1b3140..eeef80d 100644
--- a/Swift/QtUI/Swift.qrc
+++ b/Swift/QtUI/Swift.qrc
@@ -41,5 +41,6 @@
 		<file alias="icons/star-checked.png">../resources/icons/star-checked2.png</file>
 		<file alias="icons/star-unchecked.png">../resources/icons/star-unchecked2.png</file>
 		<file alias="icons/zzz.png">../resources/icons/zzz.png</file>
+		<file alias="icons/stop.png">../resources/icons/stop.png</file>
 	</qresource>
 </RCC>
diff --git a/Swift/resources/icons/stop.png b/Swift/resources/icons/stop.png
new file mode 100644
index 0000000..1574342
Binary files /dev/null and b/Swift/resources/icons/stop.png differ
diff --git a/Swift/resources/icons/stop.svg b/Swift/resources/icons/stop.svg
new file mode 100644
index 0000000..b6171ef
--- /dev/null
+++ b/Swift/resources/icons/stop.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="601"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.2 r9819"
+   height="601"
+   sodipodi:docname="Blank_stop_sign_octagon.svg"
+   inkscape:export-filename="/Users/tobias/Downloads/Blank_stop_sign_octagon.png"
+   inkscape:export-xdpi="3.29"
+   inkscape:export-ydpi="3.29">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs10" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1440"
+     inkscape:window-height="852"
+     id="namedview8"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:zoom="0.63429569"
+     inkscape:cx="102.64275"
+     inkscape:cy="390.12926"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg2" />
+  <path
+     d="m 0.5,424.5 v -248 l 176,-176 h 248 l 176,176 v 248 l -176,176 h -248 z"
+     id="path4"
+     inkscape:connector-curvature="0"
+     style="fill:#ffffff;stroke:#000000" />
+  <path
+     d="M 17,417 V 182 L 183,16 H 418 L 584,182 V 417 L 418,583 H 183 z"
+     id="path6"
+     inkscape:connector-curvature="0"
+     style="fill:#ff0000" />
+  <rect
+     style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+     id="rect2991"
+     width="368.91312"
+     height="96.169655"
+     x="116.04344"
+     y="249.42896" />
+</svg>
diff --git a/Swift/resources/icons/stop.txt b/Swift/resources/icons/stop.txt
new file mode 100644
index 0000000..9d4a1b1
--- /dev/null
+++ b/Swift/resources/icons/stop.txt
@@ -0,0 +1,2 @@
+License: Public Domain
+Source:  https://commons.wikimedia.org/wiki/File:Blank_stop_sign_octagon.svg
diff --git a/Swiften/Client/BlockList.cpp b/Swiften/Client/BlockList.cpp
index 0b2fc12..3ee7864 100644
--- a/Swiften/Client/BlockList.cpp
+++ b/Swiften/Client/BlockList.cpp
@@ -6,8 +6,17 @@
 
 #include <Swiften/Client/BlockList.h>
 
+#include <algorithm>
+
 using namespace Swift;
 
 BlockList::~BlockList() {
 
 }
+
+bool BlockList::isBlocked(const JID& jid) const {
+	const std::vector<JID>& items = getItems();
+	return (std::find(items.begin(), items.end(), jid.toBare()) != items.end()) ||
+			(std::find(items.begin(), items.end(), JID(jid.getDomain())) != items.end()) ||
+			(std::find(items.begin(), items.end(), jid) != items.end());
+}
diff --git a/Swiften/Client/BlockList.h b/Swiften/Client/BlockList.h
index d8cff60..99c83c1 100644
--- a/Swiften/Client/BlockList.h
+++ b/Swiften/Client/BlockList.h
@@ -6,7 +6,7 @@
 
 #pragma once
 
-#include <set>
+#include <vector>
 
 #include <Swiften/JID/JID.h>
 #include <Swiften/Base/boost_bsignals.h>
@@ -15,6 +15,7 @@ namespace Swift {
 	class BlockList {
 		public:
 			enum State {
+				Init,
 				Requesting,
 				Available,
 				Error
@@ -23,7 +24,9 @@ namespace Swift {
 
 			virtual State getState() const = 0;
 
-			virtual const std::set<JID>& getItems() const = 0;
+			virtual const std::vector<JID>& getItems() const = 0;
+
+			bool isBlocked(const JID& jid) const;
 
 		public:
 			boost::signal<void ()> onStateChanged;
diff --git a/Swiften/Client/BlockListImpl.cpp b/Swiften/Client/BlockListImpl.cpp
index dfaaaf1..5950233 100644
--- a/Swiften/Client/BlockListImpl.cpp
+++ b/Swiften/Client/BlockListImpl.cpp
@@ -8,30 +8,47 @@
 
 #include <Swiften/Base/foreach.h>
 
+#include <algorithm>
+
 using namespace Swift;
 
-BlockListImpl::BlockListImpl() {
+BlockListImpl::BlockListImpl() : state(Init) {
 
 }
 
 void BlockListImpl::setItems(const std::vector<JID>& items) {
-	this->items = std::set<JID>(items.begin(), items.end());
+	foreach (const JID& jid, this->items) {
+		if (std::find(items.begin(), items.end(), jid) != items.end()) {
+			onItemRemoved(jid);
+		}
+	}
+
+	foreach (const JID& jid, items) {
+		if (std::find(this->items.begin(), this->items.end(), jid) != this->items.end()) {
+			onItemAdded(jid);
+		}
+	}
+	this->items = items;
 }
 
 void BlockListImpl::addItem(const JID& item) {
-	if (items.insert(item).second) {
+	if (std::find(items.begin(), items.end(), item) == items.end()) {
+		items.push_back(item);
 		onItemAdded(item);
 	}
 }
 
 void BlockListImpl::removeItem(const JID& item) {
-	if (items.erase(item)) {
+	size_t oldSize = items.size();
+	items.erase(std::remove(items.begin(), items.end(), item), items.end());
+	if (items.size() != oldSize) {
 		onItemRemoved(item);
 	}
 }
 
 void BlockListImpl::setState(State state) {
 	if (this->state != state) {
+		this->state = state;
 		onStateChanged();
 	}
 }
@@ -43,14 +60,13 @@ void BlockListImpl::addItems(const std::vector<JID>& items) {
 }
 
 void BlockListImpl::removeItems(const std::vector<JID>& items) {
-	foreach (const JID& item, items) {
+	std::vector<JID> itemsToRemove = items;
+	foreach (const JID& item, itemsToRemove) {
 		removeItem(item);
 	}
 }
 
 void BlockListImpl::removeAllItems() {
-	foreach (const JID& item, items) {
-		removeItem(item);
-	}
+	removeItems(items);
 }
 
diff --git a/Swiften/Client/BlockListImpl.h b/Swiften/Client/BlockListImpl.h
index ef08340..2a799ae 100644
--- a/Swiften/Client/BlockListImpl.h
+++ b/Swiften/Client/BlockListImpl.h
@@ -19,7 +19,7 @@ namespace Swift {
 
 			void setState(State state);
 
-			virtual const std::set<JID>& getItems() const {
+			virtual const std::vector<JID>& getItems() const {
 				return items;
 			}
 
@@ -32,6 +32,6 @@ namespace Swift {
 
 		private:
 			State state;
-			std::set<JID> items;
+			std::vector<JID> items;
 	};
 }
diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp
index 1a6c64b..00f0f44 100644
--- a/Swiften/Client/Client.cpp
+++ b/Swiften/Client/Client.cpp
@@ -30,6 +30,7 @@
 #include <Swiften/Network/NetworkFactories.h>
 #include <Swiften/FileTransfer/FileTransferManagerImpl.h>
 #include <Swiften/Whiteboard/WhiteboardSessionManager.h>
+#include <Swiften/Client/ClientBlockListManager.h>
 #ifndef SWIFT_EXPERIMENTAL_FT
 #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h>
 #endif
@@ -68,6 +69,7 @@ Client::Client(const JID& jid, const SafeString& password, NetworkFactories* net
 	blindCertificateTrustChecker = new BlindCertificateTrustChecker();
 	
 	jingleSessionManager = new JingleSessionManager(getIQRouter());
+	blockListManager = new ClientBlockListManager(getIQRouter());
 	fileTransferManager = NULL;
 
 	whiteboardSessionManager = NULL;
diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h
index 126572a..f192539 100644
--- a/Swiften/Client/Client.h
+++ b/Swiften/Client/Client.h
@@ -37,6 +37,7 @@ namespace Swift {
 	class JingleSessionManager;
 	class FileTransferManager;
 	class WhiteboardSessionManager;
+	class ClientBlockListManager;
 
 	/**
 	 * Provides the core functionality for writing XMPP client software.
@@ -135,6 +136,10 @@ namespace Swift {
 			ClientDiscoManager* getDiscoManager() const {
 				return discoManager;
 			}
+
+			ClientBlockListManager* getClientBlockListManager() const {
+				return blockListManager;
+			}
 			
 			/**
 			 * Returns a FileTransferManager for the client. This is only available after the onConnected 
@@ -188,6 +193,7 @@ namespace Swift {
 			JingleSessionManager* jingleSessionManager;
 			FileTransferManager* fileTransferManager;
 			BlindCertificateTrustChecker* blindCertificateTrustChecker;
-		WhiteboardSessionManager* whiteboardSessionManager;
+			WhiteboardSessionManager* whiteboardSessionManager;
+			ClientBlockListManager* blockListManager;
 	};
 }
diff --git a/Swiften/Client/ClientBlockListManager.cpp b/Swiften/Client/ClientBlockListManager.cpp
index 7222cea..6646a5c 100644
--- a/Swiften/Client/ClientBlockListManager.cpp
+++ b/Swiften/Client/ClientBlockListManager.cpp
@@ -66,11 +66,14 @@ namespace {
 }
 
 ClientBlockListManager::ClientBlockListManager(IQRouter* iqRouter) : iqRouter(iqRouter) {
+
 }
 
 ClientBlockListManager::~ClientBlockListManager() {
-	unblockResponder->stop();
-	blockResponder->stop();
+	if (blockList && blockList->getState() == BlockList::Available) {
+		unblockResponder->stop();
+		blockResponder->stop();
+	}
 	if (getRequest) {
 		getRequest->onResponse.disconnect(boost::bind(&ClientBlockListManager::handleBlockListReceived, this, _1, _2));
 	}
@@ -88,13 +91,36 @@ boost::shared_ptr<BlockList> ClientBlockListManager::getBlockList() {
 	return blockList;
 }
 
+GenericRequest<BlockPayload>::ref ClientBlockListManager::createBlockJIDRequest(const JID& jid) {
+	return createBlockJIDsRequest(std::vector<JID>(1, jid));
+}
+
+GenericRequest<BlockPayload>::ref ClientBlockListManager::createBlockJIDsRequest(const std::vector<JID>& jids) {
+	boost::shared_ptr<BlockPayload> payload = boost::make_shared<BlockPayload>(jids);
+	return boost::make_shared< GenericRequest<BlockPayload> >(IQ::Set, JID(), payload, iqRouter);
+}
+
+GenericRequest<UnblockPayload>::ref ClientBlockListManager::createUnblockJIDRequest(const JID& jid) {
+	return createUnblockJIDsRequest(std::vector<JID>(1, jid));
+}
+
+GenericRequest<UnblockPayload>::ref ClientBlockListManager::createUnblockJIDsRequest(const std::vector<JID>& jids) {
+	boost::shared_ptr<UnblockPayload> payload = boost::make_shared<UnblockPayload>(jids);
+	return boost::make_shared< GenericRequest<UnblockPayload> >(IQ::Set, JID(), payload, iqRouter);
+}
+
+GenericRequest<UnblockPayload>::ref ClientBlockListManager::createUnblockAllRequest() {
+	return createUnblockJIDsRequest(std::vector<JID>());
+}
+
+
 void ClientBlockListManager::handleBlockListReceived(boost::shared_ptr<BlockListPayload> payload, ErrorPayload::ref error) {
 	if (error || !payload) {
 		blockList->setState(BlockList::Error);
 	}
 	else {
-		blockList->setState(BlockList::Available);
 		blockList->setItems(payload->getItems());
+		blockList->setState(BlockList::Available);
 		blockResponder = boost::make_shared<BlockResponder>(blockList, iqRouter);
 		blockResponder->start();
 		unblockResponder = boost::make_shared<UnblockResponder>(blockList, iqRouter);
diff --git a/Swiften/Client/ClientBlockListManager.h b/Swiften/Client/ClientBlockListManager.h
index 21d35e3..e8d4ac6 100644
--- a/Swiften/Client/ClientBlockListManager.h
+++ b/Swiften/Client/ClientBlockListManager.h
@@ -12,6 +12,7 @@
 #include <Swiften/Elements/BlockPayload.h>
 #include <Swiften/Elements/BlockListPayload.h>
 #include <Swiften/Elements/UnblockPayload.h>
+#include <Swiften/Elements/DiscoInfo.h>
 #include <Swiften/Queries/SetResponder.h>
 #include <Swiften/Queries/GenericRequest.h>
 #include <Swiften/Client/BlockList.h>
@@ -25,13 +26,18 @@ namespace Swift {
 			ClientBlockListManager(IQRouter *iqRouter);
 			~ClientBlockListManager();
 
-			bool isSupported() const;
-
 			/**
 			 * Returns the blocklist.
 			 */
 			boost::shared_ptr<BlockList> getBlockList();
 
+			GenericRequest<BlockPayload>::ref createBlockJIDRequest(const JID& jid);
+			GenericRequest<BlockPayload>::ref createBlockJIDsRequest(const std::vector<JID>& jids);
+
+			GenericRequest<UnblockPayload>::ref createUnblockJIDRequest(const JID& jid);
+			GenericRequest<UnblockPayload>::ref createUnblockJIDsRequest(const std::vector<JID>& jids);
+			GenericRequest<UnblockPayload>::ref createUnblockAllRequest();
+
 		private:
 			void handleBlockListReceived(boost::shared_ptr<BlockListPayload> payload, ErrorPayload::ref);
 
diff --git a/Swiften/Client/UnitTest/ClientBlockListManagerTest.cpp b/Swiften/Client/UnitTest/ClientBlockListManagerTest.cpp
new file mode 100644
index 0000000..9010042
--- /dev/null
+++ b/Swiften/Client/UnitTest/ClientBlockListManagerTest.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <algorithm>
+
+#include <Swiften/Base/foreach.h>
+
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Elements/IQ.h>
+
+using namespace Swift;
+
+class ClientBlockListManagerTest : public CppUnit::TestFixture {
+		CPPUNIT_TEST_SUITE(ClientBlockListManagerTest);
+		CPPUNIT_TEST(testFetchBlockList);
+		CPPUNIT_TEST(testBlockCommand);
+		CPPUNIT_TEST(testUnblockCommand);
+		CPPUNIT_TEST(testUnblockAllCommand);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		void setUp() {
+			ownJID_ = JID("kev@wonderland.lit");
+			stanzaChannel_ = new DummyStanzaChannel();
+			iqRouter_ = new IQRouter(stanzaChannel_);
+			iqRouter_->setJID(ownJID_);
+			clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
+		}
+
+		void testFetchBlockList() {
+			std::vector<JID> blockJids;
+			blockJids.push_back(JID("romeo@montague.net"));
+			blockJids.push_back(JID("iago@shakespeare.lit"));
+			helperInitialBlockListFetch(blockJids);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), clientBlockListManager_->getBlockList()->getItems().size());
+		}
+
+		void testBlockCommand() {
+			// start with an already fetched block list
+			helperInitialBlockListFetch(std::vector<JID>(1, JID("iago@shakespeare.lit")));
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), clientBlockListManager_->getBlockList()->getItems().size());
+			CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState());
+
+			GenericRequest<BlockPayload>::ref blockRequest = clientBlockListManager_->createBlockJIDRequest(JID("romeo@montague.net"));
+			blockRequest->send();
+			IQ::ref request = stanzaChannel_->getStanzaAtIndex<IQ>(2);
+			CPPUNIT_ASSERT(request.get() != NULL);
+			boost::shared_ptr<BlockPayload> blockPayload = request->getPayload<BlockPayload>();
+			CPPUNIT_ASSERT(blockPayload.get() != NULL);
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), blockPayload->getItems().at(0));
+
+			IQ::ref blockRequestResponse = IQ::createResult(request->getFrom(), JID(), request->getID());
+			stanzaChannel_->sendIQ(blockRequestResponse);
+			stanzaChannel_->onIQReceived(blockRequestResponse);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), clientBlockListManager_->getBlockList()->getItems().size());
+
+			// send block push
+			boost::shared_ptr<BlockPayload> pushPayload = boost::make_shared<BlockPayload>();
+			pushPayload->addItem(JID("romeo@montague.net"));
+			IQ::ref blockPush = IQ::createRequest(IQ::Set, ownJID_, "push1", pushPayload);
+			stanzaChannel_->sendIQ(blockPush);
+			stanzaChannel_->onIQReceived(blockPush);
+
+			std::vector<JID> blockedJIDs = clientBlockListManager_->getBlockList()->getItems();
+			CPPUNIT_ASSERT(blockedJIDs.end() != std::find(blockedJIDs.begin(), blockedJIDs.end(), JID("romeo@montague.net")));
+		}
+
+		void testUnblockCommand() {
+			// start with an already fetched block list
+			std::vector<JID> initialBlockList = std::vector<JID>(1, JID("iago@shakespeare.lit"));
+			initialBlockList.push_back(JID("romeo@montague.net"));
+			helperInitialBlockListFetch(initialBlockList);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), clientBlockListManager_->getBlockList()->getItems().size());
+			CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState());
+
+			GenericRequest<UnblockPayload>::ref unblockRequest = clientBlockListManager_->createUnblockJIDRequest(JID("romeo@montague.net"));
+			unblockRequest->send();
+			IQ::ref request = stanzaChannel_->getStanzaAtIndex<IQ>(2);
+			CPPUNIT_ASSERT(request.get() != NULL);
+			boost::shared_ptr<UnblockPayload> unblockPayload = request->getPayload<UnblockPayload>();
+			CPPUNIT_ASSERT(unblockPayload.get() != NULL);
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), unblockPayload->getItems().at(0));
+
+			IQ::ref unblockRequestResponse = IQ::createResult(request->getFrom(), JID(), request->getID());
+			stanzaChannel_->sendIQ(unblockRequestResponse);
+			stanzaChannel_->onIQReceived(unblockRequestResponse);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), clientBlockListManager_->getBlockList()->getItems().size());
+
+			// send block push
+			boost::shared_ptr<UnblockPayload> pushPayload = boost::make_shared<UnblockPayload>();
+			pushPayload->addItem(JID("romeo@montague.net"));
+			IQ::ref unblockPush = IQ::createRequest(IQ::Set, ownJID_, "push1", pushPayload);
+			stanzaChannel_->sendIQ(unblockPush);
+			stanzaChannel_->onIQReceived(unblockPush);
+
+			std::vector<JID> blockedJIDs = clientBlockListManager_->getBlockList()->getItems();
+			CPPUNIT_ASSERT(blockedJIDs.end() == std::find(blockedJIDs.begin(), blockedJIDs.end(), JID("romeo@montague.net")));
+		}
+
+		void testUnblockAllCommand() {
+			// start with an already fetched block list
+			std::vector<JID> initialBlockList = std::vector<JID>(1, JID("iago@shakespeare.lit"));
+			initialBlockList.push_back(JID("romeo@montague.net"));
+			initialBlockList.push_back(JID("benvolio@montague.net"));
+			helperInitialBlockListFetch(initialBlockList);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), clientBlockListManager_->getBlockList()->getItems().size());
+			CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState());
+
+			GenericRequest<UnblockPayload>::ref unblockRequest = clientBlockListManager_->createUnblockAllRequest();
+			unblockRequest->send();
+			IQ::ref request = stanzaChannel_->getStanzaAtIndex<IQ>(2);
+			CPPUNIT_ASSERT(request.get() != NULL);
+			boost::shared_ptr<UnblockPayload> unblockPayload = request->getPayload<UnblockPayload>();
+			CPPUNIT_ASSERT(unblockPayload.get() != NULL);
+			CPPUNIT_ASSERT_EQUAL(true, unblockPayload->getItems().empty());
+
+			IQ::ref unblockRequestResponse = IQ::createResult(request->getFrom(), JID(), request->getID());
+			stanzaChannel_->sendIQ(unblockRequestResponse);
+			stanzaChannel_->onIQReceived(unblockRequestResponse);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), clientBlockListManager_->getBlockList()->getItems().size());
+
+			// send block push
+			boost::shared_ptr<UnblockPayload> pushPayload = boost::make_shared<UnblockPayload>();
+			IQ::ref unblockPush = IQ::createRequest(IQ::Set, ownJID_, "push1", pushPayload);
+			stanzaChannel_->sendIQ(unblockPush);
+			stanzaChannel_->onIQReceived(unblockPush);
+
+			CPPUNIT_ASSERT_EQUAL(true, clientBlockListManager_->getBlockList()->getItems().empty());
+		}
+
+		void tearDown() {
+			delete clientBlockListManager_;
+			delete iqRouter_;
+			delete stanzaChannel_;
+		}
+
+	private:
+		void helperInitialBlockListFetch(const std::vector<JID>& blockedJids) {
+			boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList();
+			CPPUNIT_ASSERT(blockList);
+
+			// check for IQ request
+			IQ::ref request = stanzaChannel_->getStanzaAtIndex<IQ>(0);
+			CPPUNIT_ASSERT(request.get() != NULL);
+			boost::shared_ptr<BlockListPayload> requestPayload = request->getPayload<BlockListPayload>();
+			CPPUNIT_ASSERT(requestPayload.get() != NULL);
+
+			CPPUNIT_ASSERT_EQUAL(BlockList::Requesting, blockList->getState());
+			CPPUNIT_ASSERT_EQUAL(BlockList::Requesting, clientBlockListManager_->getBlockList()->getState());
+
+			// build IQ response
+			boost::shared_ptr<BlockListPayload> responsePayload = boost::make_shared<BlockListPayload>();
+			foreach(const JID& jid, blockedJids) {
+				responsePayload->addItem(jid);
+			}
+
+			IQ::ref response = IQ::createResult(ownJID_, JID(), request->getID(), responsePayload);
+			stanzaChannel_->sendIQ(response);
+			stanzaChannel_->onIQReceived(response);
+
+			CPPUNIT_ASSERT_EQUAL(BlockList::Available, clientBlockListManager_->getBlockList()->getState());
+			CPPUNIT_ASSERT(responsePayload->getItems() == clientBlockListManager_->getBlockList()->getItems());
+		}
+
+
+	private:
+		JID ownJID_;
+		IQRouter* iqRouter_;
+		DummyStanzaChannel* stanzaChannel_;
+		ClientBlockListManager* clientBlockListManager_;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ClientBlockListManagerTest);
+
diff --git a/Swiften/Elements/BlockListPayload.h b/Swiften/Elements/BlockListPayload.h
index 25cb602..49df75f 100644
--- a/Swiften/Elements/BlockListPayload.h
+++ b/Swiften/Elements/BlockListPayload.h
@@ -14,7 +14,7 @@
 namespace Swift {
 	class BlockListPayload : public Payload {
 		public:
-			BlockListPayload() {
+			BlockListPayload(const std::vector<JID>& items = std::vector<JID>()) : items(items) {
 			}
 
 			void addItem(const JID& item) {
diff --git a/Swiften/Elements/BlockPayload.h b/Swiften/Elements/BlockPayload.h
index 6dd5170..49a0463 100644
--- a/Swiften/Elements/BlockPayload.h
+++ b/Swiften/Elements/BlockPayload.h
@@ -14,7 +14,7 @@
 namespace Swift {
 	class BlockPayload : public Payload {
 		public:
-			BlockPayload() {
+			BlockPayload(const std::vector<JID>& jids = std::vector<JID>()) : items(jids) {
 			}
 
 			void addItem(const JID& jid) {
diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp
index 1683916..f353c42 100644
--- a/Swiften/Elements/DiscoInfo.cpp
+++ b/Swiften/Elements/DiscoInfo.cpp
@@ -23,6 +23,7 @@ const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp:
 const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams");
 const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:xmpp:receipts");
 const std::string DiscoInfo::WhiteboardFeature = std::string("http://swift.im/whiteboard");
+const std::string DiscoInfo::BlockingCommandFeature = std::string("urn:xmpp:blocking");
 
 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 3334ac8..3701096 100644
--- a/Swiften/Elements/DiscoInfo.h
+++ b/Swiften/Elements/DiscoInfo.h
@@ -34,6 +34,7 @@ namespace Swift {
 			static const std::string Bytestream;
 			static const std::string MessageDeliveryReceiptsFeature;
 			static const std::string WhiteboardFeature;
+			static const std::string BlockingCommandFeature;
 
 			class Identity {
 				public:
diff --git a/Swiften/Elements/UnblockPayload.h b/Swiften/Elements/UnblockPayload.h
index b6593ab..c5e7c80 100644
--- a/Swiften/Elements/UnblockPayload.h
+++ b/Swiften/Elements/UnblockPayload.h
@@ -14,7 +14,7 @@
 namespace Swift {
 	class UnblockPayload : public Payload {
 		public:
-			UnblockPayload() {
+			UnblockPayload(const std::vector<JID>& jids = std::vector<JID>()) : items(jids) {
 			}
 
 			void addItem(const JID& item) {
diff --git a/Swiften/Parser/PayloadParsers/BlockParser.h b/Swiften/Parser/PayloadParsers/BlockParser.h
index cd727ad..6ee47a2 100644
--- a/Swiften/Parser/PayloadParsers/BlockParser.h
+++ b/Swiften/Parser/PayloadParsers/BlockParser.h
@@ -7,13 +7,14 @@
 #pragma once
 
 #include <Swiften/Elements/Nickname.h>
+#include <Swiften/JID/JID.h>
 #include <Swiften/Parser/GenericPayloadParser.h>
 
 namespace Swift {
 	template<typename BLOCK_ELEMENT>
 	class BlockParser : public GenericPayloadParser<BLOCK_ELEMENT> {
 		public:
-			BlockParser() : GenericPayloadParser<BLOCK_ELEMENT>() {
+			BlockParser() : GenericPayloadParser<BLOCK_ELEMENT>(), level(0) {
 			}
 
 			virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/BlockParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/BlockParserTest.cpp
new file mode 100644
index 0000000..ddd3a88
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/BlockParserTest.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swiften/Parser/PayloadParsers/BlockParser.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
+
+#include <Swiften/Elements/BlockPayload.h>
+#include <Swiften/Elements/UnblockPayload.h>
+#include <Swiften/Elements/BlockListPayload.h>
+
+using namespace Swift;
+
+class BlockParserTest : public CppUnit::TestFixture
+{
+		CPPUNIT_TEST_SUITE(BlockParserTest);
+		CPPUNIT_TEST(testExample4);
+		CPPUNIT_TEST(testExample6);
+		CPPUNIT_TEST(testExample10);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		BlockParserTest() {}
+
+		void testExample4() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse("<blocklist xmlns='urn:xmpp:blocking'>"
+											"<item jid='romeo@montague.net'/>"
+											"<item jid='iago@shakespeare.lit'/>"
+										"</blocklist>"));
+
+			BlockListPayload* payload = dynamic_cast<BlockListPayload*>(parser.getPayload().get());
+			CPPUNIT_ASSERT(payload);
+			CPPUNIT_ASSERT(2 == payload->getItems().size());
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), payload->getItems()[0]);
+			CPPUNIT_ASSERT_EQUAL(JID("iago@shakespeare.lit"), payload->getItems()[1]);
+		}
+
+		void testExample6() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse("<block xmlns='urn:xmpp:blocking'>"
+											"<item jid='romeo@montague.net'/>"
+										"</block>"));
+
+			BlockPayload* payload = dynamic_cast<BlockPayload*>(parser.getPayload().get());
+			CPPUNIT_ASSERT(payload);
+			CPPUNIT_ASSERT(1 == payload->getItems().size());
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), payload->getItems()[0]);
+		}
+
+		void testExample10() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse("<unblock xmlns='urn:xmpp:blocking'>"
+											"<item jid='romeo@montague.net'/>"
+										"</unblock>"));
+
+			UnblockPayload* payload = dynamic_cast<UnblockPayload*>(parser.getPayload().get());
+			CPPUNIT_ASSERT(payload);
+			CPPUNIT_ASSERT(1 == payload->getItems().size());
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), payload->getItems()[0]);
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BlockParserTest);
diff --git a/Swiften/Queries/GenericRequest.h b/Swiften/Queries/GenericRequest.h
index 68c86c5..b1b7f8a 100644
--- a/Swiften/Queries/GenericRequest.h
+++ b/Swiften/Queries/GenericRequest.h
@@ -22,6 +22,9 @@ namespace Swift {
 	template<typename PAYLOAD_TYPE>
 	class GenericRequest : public Request {
 		public:
+			typedef boost::shared_ptr<GenericRequest<PAYLOAD_TYPE> > ref;
+
+		public:
 			/**
 			 * Create a request suitable for client use.
 			 * @param type Iq type - Get or Set.
@@ -61,7 +64,7 @@ namespace Swift {
 				onResponse(boost::dynamic_pointer_cast<PAYLOAD_TYPE>(payload), error);
 			}
 
-		protected:
+		public:
 			boost::shared_ptr<PAYLOAD_TYPE> getPayloadGeneric() const {
 				return boost::dynamic_pointer_cast<PAYLOAD_TYPE>(getPayload());
 			}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index a62d344..b9fad17 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -318,6 +318,7 @@ if env["SCONS_STAGE"] == "build" :
 #		File("Chat/UnitTest/ChatStateTrackerTest.cpp"),
 			File("Client/UnitTest/ClientSessionTest.cpp"),
 			File("Client/UnitTest/NickResolverTest.cpp"),
+			File("Client/UnitTest/ClientBlockListManagerTest.cpp"),
 			File("Compress/UnitTest/ZLibCompressorTest.cpp"),
 			File("Compress/UnitTest/ZLibDecompressorTest.cpp"),
 			File("Component/UnitTest/ComponentHandshakeGeneratorTest.cpp"),
@@ -347,6 +348,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp"),
 			File("Network/UnitTest/BOSHConnectionTest.cpp"),
 			File("Network/UnitTest/BOSHConnectionPoolTest.cpp"),
+			File("Parser/PayloadParsers/UnitTest/BlockParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"),
 			File("Parser/PayloadParsers/UnitTest/DiscoItemsParserTest.cpp"),
@@ -401,6 +403,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("Roster/UnitTest/XMPPRosterControllerTest.cpp"),
 			File("Roster/UnitTest/XMPPRosterSignalHandler.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/PayloadsSerializer.cpp"),
+			File("Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/CapsInfoSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp"),
 			File("Serializer/PayloadSerializers/UnitTest/DiscoInfoSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/BlockSerializer.h b/Swiften/Serializer/PayloadSerializers/BlockSerializer.h
index 10ae8b0..fc628c2 100644
--- a/Swiften/Serializer/PayloadSerializers/BlockSerializer.h
+++ b/Swiften/Serializer/PayloadSerializers/BlockSerializer.h
@@ -8,6 +8,7 @@
 
 #include <boost/smart_ptr/make_shared.hpp>
 
+#include <Swiften/JID/JID.h>
 #include <Swiften/Serializer/GenericPayloadSerializer.h>
 #include <Swiften/Serializer/XML/XMLElement.h>
 
@@ -24,6 +25,7 @@ namespace Swift {
 				for (std::vector<JID>::const_iterator i = items.begin(); i != items.end(); ++i) {
 					boost::shared_ptr<XMLElement> item = boost::make_shared<XMLElement>("item");
 					item->setAttribute("jid", *i);
+					element.addNode(item);
 				}
 				return element.serialize();
 			}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp
new file mode 100644
index 0000000..7772381
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/BlockSerializerTest.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Swiften/Serializer/PayloadSerializers/BlockSerializer.h>
+#include <Swiften/Elements/BlockListPayload.h>
+#include <Swiften/Elements/BlockPayload.h>
+#include <Swiften/Elements/UnblockPayload.h>
+#include <Swiften/JID/JID.h>
+
+using namespace Swift;
+
+class BlockSerializerTest : public CppUnit::TestFixture
+{
+		CPPUNIT_TEST_SUITE(BlockSerializerTest);
+		CPPUNIT_TEST(testExample4);
+		CPPUNIT_TEST(testExample6);
+		CPPUNIT_TEST(testExample10);
+		CPPUNIT_TEST_SUITE_END();
+
+	public:
+		BlockSerializerTest() {}
+
+		void testExample4() {
+			BlockSerializer<BlockListPayload> testling("blocklist");
+			boost::shared_ptr<BlockListPayload> blocklist = boost::make_shared<BlockListPayload>();
+			blocklist->addItem(JID("romeo@montague.net"));
+			blocklist->addItem(JID("iago@shakespeare.lit"));
+
+			CPPUNIT_ASSERT_EQUAL(std::string("<blocklist xmlns=\"urn:xmpp:blocking\"><item jid=\"romeo@montague.net\"/><item jid=\"iago@shakespeare.lit\"/></blocklist>"), testling.serialize(blocklist));
+		}
+
+		void testExample6() {
+			BlockSerializer<BlockPayload> testling("block");
+			boost::shared_ptr<BlockPayload> block = boost::make_shared<BlockPayload>();
+			block->addItem(JID("romeo@montague.net"));
+
+			CPPUNIT_ASSERT_EQUAL(std::string("<block xmlns=\"urn:xmpp:blocking\"><item jid=\"romeo@montague.net\"/></block>"), testling.serialize(block));
+		}
+
+		void testExample10() {
+			BlockSerializer<UnblockPayload> testling("unblock");
+			boost::shared_ptr<UnblockPayload> unblock = boost::make_shared<UnblockPayload>();
+			unblock->addItem(JID("romeo@montague.net"));
+
+			CPPUNIT_ASSERT_EQUAL(std::string("<unblock xmlns=\"urn:xmpp:blocking\"><item jid=\"romeo@montague.net\"/></unblock>"), testling.serialize(unblock));
+		}
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BlockSerializerTest);
-- 
cgit v0.10.2-6-g49f6