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
18import logging
19
20from nbxmpp.namespaces import Namespace
21from nbxmpp.protocol import NodeProcessed
22from nbxmpp.protocol import InvalidFrom
23from nbxmpp.protocol import InvalidStanza
24from nbxmpp.protocol import Message
25from nbxmpp.structs import MAMData
26from nbxmpp.structs import CarbonData
27from nbxmpp.modules.delay import parse_delay
28
29
30log = logging.getLogger('nbxmpp.m.misc')
31
32
33def unwrap_carbon(stanza, own_jid):
34    carbon = stanza.getTag('received', namespace=Namespace.CARBONS)
35    if carbon is None:
36        carbon = stanza.getTag('sent', namespace=Namespace.CARBONS)
37        if carbon is None:
38            return stanza, None
39
40    # Carbon must be from our bare jid
41    if stanza.getFrom() != own_jid.new_as_bare():
42        raise InvalidFrom('Invalid from: %s' % stanza.getAttr('from'))
43
44    forwarded = carbon.getTag('forwarded', namespace=Namespace.FORWARD)
45    message = Message(node=forwarded.getTag('message'))
46
47    type_ = carbon.getName()
48
49    # Fill missing to/from
50    to = message.getTo()
51    if to is None:
52        message.setTo(own_jid.bare)
53
54    frm = message.getFrom()
55    if frm is None:
56        message.setFrom(own_jid.bare)
57
58    if type_ == 'received':
59        if message.getFrom().bare_match(own_jid):
60            # Drop 'received' Carbons from ourself, we already
61            # got the message with the 'sent' Carbon or via the
62            # message itself
63            raise NodeProcessed('Drop "received"-Carbon from ourself')
64
65        if message.getTag('x', namespace=Namespace.MUC_USER) is not None:
66            # A MUC broadcasts messages sent to us to all resources
67            # there is no need to process the received carbon
68            raise NodeProcessed('Drop MUC-PM "received"-Carbon')
69
70    return message, CarbonData(type=type_)
71
72
73def unwrap_mam(stanza, own_jid):
74    result = stanza.getTag('result', namespace=Namespace.MAM_2)
75    if result is None:
76        result = stanza.getTag('result', namespace=Namespace.MAM_1)
77        if result is None:
78            return stanza, None
79
80    query_id = result.getAttr('queryid')
81    if query_id is None:
82        log.warning('No queryid on MAM message')
83        log.warning(stanza)
84        raise InvalidStanza
85
86    id_ = result.getAttr('id')
87    if id_ is None:
88        log.warning('No id on MAM message')
89        log.warning(stanza)
90        raise InvalidStanza
91
92    forwarded = result.getTag('forwarded', namespace=Namespace.FORWARD)
93    message = Message(node=forwarded.getTag('message'))
94
95    # Fill missing to/from
96    to = message.getTo()
97    if to is None:
98        message.setTo(own_jid.bare)
99
100    frm = message.getFrom()
101    if frm is None:
102        message.setFrom(own_jid.bare)
103
104    # Timestamp parsing
105    # Most servers dont set the 'from' attr, so we cant check for it
106    delay_timestamp = parse_delay(forwarded)
107    if delay_timestamp is None:
108        log.warning('No timestamp on MAM message')
109        log.warning(stanza)
110        raise InvalidStanza
111
112    return message, MAMData(id=id_,
113                            query_id=query_id,
114                            archive=stanza.getFrom(),
115                            namespace=result.getNamespace(),
116                            timestamp=delay_timestamp)
117
118
119def build_xhtml_body(xhtml, xmllang=None):
120    try:
121        if xmllang is not None:
122            body = '<body xmlns="%s" xml:lang="%s">%s</body>' % (
123                Namespace.XHTML, xmllang, xhtml)
124        else:
125            body = '<body xmlns="%s">%s</body>' % (Namespace.XHTML, xhtml)
126    except Exception as error:
127        log.error('Error while building xhtml node: %s', error)
128        return None
129    return body
130