1# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.com>
2#
3# This file is part of nbxmpp.
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 3
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; If not, see <http://www.gnu.org/licenses/>.
17
18from nbxmpp.namespaces import Namespace
19from nbxmpp.protocol import Iq
20from nbxmpp.protocol import JID
21from nbxmpp.modules.base import BaseModule
22from nbxmpp.errors import StanzaError
23from nbxmpp.errors import MalformedStanzaError
24from nbxmpp.task import iq_request_task
25from nbxmpp.structs import BlockingPush
26from nbxmpp.structs import StanzaHandler
27from nbxmpp.modules.util import process_response
28
29
30class Blocking(BaseModule):
31    def __init__(self, client):
32        BaseModule.__init__(self, client)
33
34        self._client = client
35        self.handlers = [
36            StanzaHandler(name='iq',
37                          priority=15,
38                          callback=self._process_blocking_push,
39                          typ='set',
40                          ns=Namespace.BLOCKING),
41        ]
42
43    @iq_request_task
44    def request_blocking_list(self):
45        _task = yield
46
47        result = yield _make_blocking_list_request()
48        if result.isError():
49            raise StanzaError(result)
50
51        blocklist = result.getTag('blocklist', namespace=Namespace.BLOCKING)
52        if blocklist is None:
53            raise MalformedStanzaError('blocklist node missing', result)
54
55        blocked = []
56        for item in blocklist.getTags('item'):
57            blocked.append(item.getAttr('jid'))
58
59        self._log.info('Received blocking list: %s', blocked)
60        yield blocked
61
62    @iq_request_task
63    def block(self, jids, report=None):
64        _task = yield
65
66        self._log.info('Block: %s', jids)
67
68        response = yield _make_block_request(jids, report)
69        yield process_response(response)
70
71    @iq_request_task
72    def unblock(self, jids):
73        _task = yield
74
75        self._log.info('Unblock: %s', jids)
76
77        response = yield _make_unblock_request(jids)
78        yield process_response(response)
79
80    @staticmethod
81    def _process_blocking_push(client, stanza, properties):
82        unblock = stanza.getTag('unblock', namespace=Namespace.BLOCKING)
83        if unblock is not None:
84            properties.blocking = _parse_push(unblock)
85            return
86
87        block = stanza.getTag('block', namespace=Namespace.BLOCKING)
88        if block is not None:
89            properties.blocking = _parse_push(block)
90
91        reply = stanza.buildSimpleReply('result')
92        client.send_stanza(reply)
93
94
95def _make_blocking_list_request():
96    iq = Iq('get', Namespace.BLOCKING)
97    iq.setQuery('blocklist')
98    return iq
99
100
101def _make_block_request(jids, report):
102    iq = Iq('set', Namespace.BLOCKING)
103    query = iq.setQuery(name='block')
104    for jid in jids:
105        item = query.addChild(name='item', attrs={'jid': jid})
106        if report in ('spam', 'abuse'):
107            action = item.addChild(name='report',
108                                   namespace=Namespace.REPORTING)
109            action.setTag(report)
110    return iq
111
112
113def _make_unblock_request(jids):
114    iq = Iq('set', Namespace.BLOCKING)
115    query = iq.setQuery(name='unblock')
116    for jid in jids:
117        query.addChild(name='item', attrs={'jid': jid})
118    return iq
119
120
121def _parse_push(node):
122    items = node.getTags('item')
123    if not items:
124        return BlockingPush(block=[], unblock=[], unblock_all=True)
125
126    jids = []
127    for item in items:
128        jid = item.getAttr('jid')
129        if not jid:
130            continue
131
132        try:
133            jid = JID.from_string(jid)
134        except Exception:
135            continue
136
137        jids.append(jid)
138
139
140    block, unblock = [], []
141    if node.getName() == 'block':
142        block = jids
143    else:
144        unblock = jids
145
146    return BlockingPush(block=block, unblock=unblock, unblock_all=False)
147