Mercurial > libervia-backend
annotate src/plugins/plugin_xep_0071.py @ 853:c2f6ada7858f
core (sqlite): automatic database update:
- new Updater class check database consistency (by calculating a hash on the .schema), and updates base if necessary
- database now has a version (1 for current, 0 will be for 0.3's database), for each change this version will be increased
- creation statements and update statements are in the form of dict of dict with tuples. There is a help text at the top of the module to explain how it works
- if we are on a development version, the updater try to update the database automaticaly (without deleting table or columns). The Updater.generateUpdateData method can be used to ease the creation of update data (i.e. the dictionary at the top, see the one for the key 1 for an example).
- if there is an inconsistency, an exception is raised, and a message indicate the SQL statements that should fix the situation.
- well... this is rather complicated, a KISS method would maybe have been better. The future will say if we need to simplify it :-/
- new DatabaseError exception
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 23 Feb 2014 23:30:32 +0100 |
parents | c4b22aedb7d7 |
children | c897c8d321b3 |
rev | line source |
---|---|
668 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for Publish-Subscribe (xep-0071) | |
811 | 5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) |
668 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
771 | 20 from sat.core.i18n import _ |
832
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
21 from sat.core import exceptions |
668 | 22 from logging import debug, info, error |
23 | |
24 from wokkel import disco, pubsub, iwokkel | |
25 from zope.interface import implements | |
26 # from lxml import etree | |
27 from lxml import html | |
28 try: | |
29 from twisted.words.protocols.xmlstream import XMPPHandler | |
30 except ImportError: | |
31 from wokkel.subprotocols import XMPPHandler | |
32 | |
33 NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im' | |
34 NS_XHTML = 'http://www.w3.org/1999/xhtml' | |
35 | |
36 PLUGIN_INFO = { | |
37 "name": "XHTML-IM Plugin", | |
38 "import_name": "XEP-0071", | |
39 "type": "XEP", | |
40 "protocols": ["XEP-0071"], | |
41 "dependencies": ["TEXT-SYNTAXES"], | |
42 "main": "XEP_0071", | |
43 "handler": "yes", | |
44 "description": _("""Implementation of XHTML-IM""") | |
45 } | |
46 | |
47 allowed = { | |
48 "a": set(["href", "style", "type"]), | |
49 "blockquote": set(["style"]), | |
50 "body": set(["style"]), | |
51 "br": set([]), | |
52 "cite": set(["style"]), | |
53 "em": set([]), | |
54 "img": set(["alt", "height", "src", "style", "width"]), | |
55 "li": set(["style"]), | |
56 "ol": set(["style"]), | |
57 "p": set(["style"]), | |
58 "span": set(["style"]), | |
59 "strong": set([]), | |
60 "ul": set(["style"]), | |
61 } | |
62 | |
63 styles_allowed = ["background-color", "color", "font-family", "font-size", "font-style", "font-weight", "margin-left", "margin-right", "text-align", "text-decoration"] | |
64 | |
65 blacklist = ['script'] # tag that we have to kill (we don't keep content) | |
66 | |
67 | |
68 class XEP_0071(object): | |
69 SYNTAX_XHTML_IM = "XHTML-IM" | |
70 | |
71 def __init__(self, host): | |
72 info(_("XHTML-IM plugin initialization")) | |
73 self.host = host | |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
74 self.synt_plg = self.host.plugins["TEXT-SYNTAXES"] |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
75 self.synt_plg.addSyntax(self.SYNTAX_XHTML_IM, lambda xhtml: xhtml, self.XHTML2XHTML_IM, [self.synt_plg.OPT_HIDDEN]) |
668 | 76 host.trigger.add("MessageReceived", self.messageReceivedTrigger) |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
77 host.trigger.add("sendMessage", self.sendMessageTrigger) |
668 | 78 |
79 def getHandler(self, profile): | |
80 return XEP_0071_handler(self) | |
81 | |
82 def _messagePostTreat(self, data, body_elt): | |
83 """ Callback which manage the post treatment of the message in case of XHTML-IM found | |
84 @param data: data send by MessageReceived trigger through post_treat deferred | |
85 @param xhtml_im: XHTML-IM body element found | |
86 @return: the data with the extra parameter updated | |
87 """ | |
88 #TODO: check if text only body is empty, then try to convert XHTML-IM to pure text and show a warning message | |
89 def converted(xhtml): | |
90 data['extra']['xhtml'] = xhtml | |
91 return data | |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
92 d = self.synt_plg.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True) |
668 | 93 d.addCallback(converted) |
94 return d | |
95 | |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
96 def _sendMessageAddRich(self, mess_data, profile): |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
97 """ Construct XHTML-IM node and add it XML element |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
98 @param mess_data: message data as sended by sendMessage callback |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
99 """ |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
100 def syntax_converted(xhtml_im): |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
101 message_elt = mess_data['xml'] |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
102 html_elt = message_elt.addElement('html', NS_XHTML_IM) |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
103 body_elt = html_elt.addElement('body', NS_XHTML) |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
104 body_elt.addRawXml(xhtml_im) |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
105 mess_data['extra']['xhtml'] = xhtml_im |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
106 return mess_data |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
107 |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
108 syntax = self.synt_plg.getCurrentSyntax(profile) |
832
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
109 rich = mess_data['extra'].get('rich', '') |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
110 xhtml = mess_data['extra'].get('xhtml', '') |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
111 if rich: |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
112 d = self.synt_plg.convert(rich, syntax, self.SYNTAX_XHTML_IM) |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
113 if xhtml: |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
114 raise exceptions.DataError(_("Can't have xhtml and rich content at the same time")) |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
115 if xhtml: |
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
116 d = self.synt_plg.clean_xhtml(xhtml) |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
117 d.addCallback(syntax_converted) |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
118 return d |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
119 |
668 | 120 def messageReceivedTrigger(self, message, post_treat, profile): |
121 """ Check presence of XHTML-IM in message | |
122 """ | |
123 try: | |
124 html_elt = message.elements(NS_XHTML_IM, 'html').next() | |
125 body_elt = html_elt.elements(NS_XHTML, 'body').next() | |
126 # OK, we have found rich text | |
127 post_treat.addCallback(self._messagePostTreat, body_elt) | |
128 except StopIteration: | |
129 # No XHTML-IM | |
130 pass | |
131 return True | |
132 | |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
133 def sendMessageTrigger(self, mess_data, treatments, profile): |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
134 """ Check presence of rich text in extra |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
135 """ |
832
c4b22aedb7d7
plugin groupblog, XEP-0071, XEP-0277, text_syntaxes: manage raw/rich/xhtml data for content/title:
souliane <souliane@mailoo.org>
parents:
811
diff
changeset
|
136 if 'rich' in mess_data['extra'] or 'xhtml' in mess_data['extra']: |
702
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
137 treatments.addCallback(self._sendMessageAddRich, profile) |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
138 return True |
a25db3fe3959
plugin XEP-0071: rich messages management for sendMessage
Goffi <goffi@goffi.org>
parents:
701
diff
changeset
|
139 |
668 | 140 def _purgeStyle(self, styles_raw): |
141 """ Remove unauthorised styles according to the XEP-0071 | |
142 @param styles_raw: raw styles (value of the style attribute) | |
143 """ | |
144 purged = [] | |
145 | |
146 styles = [style.strip().split(':') for style in styles_raw.split(';')] | |
147 | |
148 for style_tuple in styles: | |
149 if len(style_tuple) != 2: | |
150 continue | |
151 name, value = style_tuple | |
152 name = name.strip() | |
153 if name not in styles_allowed: | |
154 continue | |
155 purged.append((name, value.strip())) | |
156 | |
157 return u'; '.join([u"%s: %s" % data for data in purged]) | |
158 | |
159 def XHTML2XHTML_IM(self, xhtml): | |
160 """ Convert XHTML document to XHTML_IM subset | |
161 @param xhtml: raw xhtml to convert | |
162 """ | |
163 # TODO: more clever tag replacement (replace forbidden tags with equivalents when possible) | |
164 | |
165 parser = html.HTMLParser(remove_comments=True, encoding='utf-8') | |
166 root = html.fromstring(xhtml, parser=parser) | |
167 body_elt = root.find('body') | |
168 if body_elt is None: | |
169 # we use the whole XML as body if no body element is found | |
170 body_elt = html.Element('body') | |
171 body_elt.append(root) | |
172 else: | |
173 body_elt.attrib.clear() | |
174 | |
175 allowed_tags = allowed.keys() | |
176 to_strip = [] | |
177 for elem in body_elt.iter(): | |
178 if elem.tag not in allowed_tags: | |
179 to_strip.append(elem) | |
180 else: | |
181 # we remove unallowed attributes | |
182 attrib = elem.attrib | |
183 att_to_remove = set(attrib).difference(allowed[elem.tag]) | |
184 for att in att_to_remove: | |
185 del(attrib[att]) | |
186 if "style" in attrib: | |
187 attrib["style"] = self._purgeStyle(attrib["style"]) | |
188 | |
189 for elem in to_strip: | |
190 if elem.tag in blacklist: | |
191 #we need to remove the element and all descendants | |
192 debug(u"removing black listed tag: %s" % (elem.tag)) | |
193 elem.drop_tree() | |
194 else: | |
195 elem.drop_tag() | |
701
98b2400e17d6
plugin XEP-0071: XHTML2XHTML_IM don't return the <body> root tag anymore.
Goffi <goffi@goffi.org>
parents:
668
diff
changeset
|
196 if len(body_elt) !=1: |
98b2400e17d6
plugin XEP-0071: XHTML2XHTML_IM don't return the <body> root tag anymore.
Goffi <goffi@goffi.org>
parents:
668
diff
changeset
|
197 root_elt = body_elt |
98b2400e17d6
plugin XEP-0071: XHTML2XHTML_IM don't return the <body> root tag anymore.
Goffi <goffi@goffi.org>
parents:
668
diff
changeset
|
198 body_elt.tag = "p" |
98b2400e17d6
plugin XEP-0071: XHTML2XHTML_IM don't return the <body> root tag anymore.
Goffi <goffi@goffi.org>
parents:
668
diff
changeset
|
199 else: |
98b2400e17d6
plugin XEP-0071: XHTML2XHTML_IM don't return the <body> root tag anymore.
Goffi <goffi@goffi.org>
parents:
668
diff
changeset
|
200 root_elt = body_elt[0] |
668 | 201 |
701
98b2400e17d6
plugin XEP-0071: XHTML2XHTML_IM don't return the <body> root tag anymore.
Goffi <goffi@goffi.org>
parents:
668
diff
changeset
|
202 return html.tostring(root_elt, encoding='unicode', method='xml') |
668 | 203 |
204 class XEP_0071_handler(XMPPHandler): | |
205 implements(iwokkel.IDisco) | |
206 | |
207 def __init__(self, plugin_parent): | |
208 self.plugin_parent = plugin_parent | |
209 self.host = plugin_parent.host | |
210 | |
211 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | |
212 return [disco.DiscoFeature(NS_XHTML_IM)] | |
213 | |
214 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | |
215 return [] |