Allow setting any safe attribute on image maps.
[matthijs/projects/wipi.git] / plugin / parser / ImageMap.py
1 # -*- coding: iso-8859-1 -*-
2 """
3     MoinMoin - ImageMap Parser
4
5     This parser is used to create clickable image maps.
6     
7     Syntax:
8     
9     {{{#!ImageMap
10     picsrc[;width=".."][;height=".."][;alt=".."][;title=".."] 
11     link_area1;shape="rect|circle|poly";coords="..";alt=".."[;title=".."]
12     link_area2;shape="rect|circle|poly";coords="..";alt=".."[;title=".."]
13     }}}
14
15     For a detailed explanation of the syntax see:
16     http://moinmoin.wikiwikiweb.de/ParserMarket/ImageMap
17
18     Please note: Image maps are not accessible by blind people. Do also have a
19     look at the best-practise-examples for using image maps
20     on http://moinmoin.wikiwikiweb.de/ParserMarket/ImageMap.
21     
22     Example:
23
24     {{{#!ImageMap
25     picture.jpg;width="345";height="312";alt="Clickable Organizational Chart" 
26     FrontPage;shape="rect";coords="11,10,59,29";alt="Area1"
27     http://www.xyz.com/;shape="circle";coords="42,36,96";alt="Area2"
28     FrontPage/SubPage;shape="poly";coords="48,311,105,248,96,210";alt="Area3"
29     Another Site in This Wiki;shape="rect";coords="88,10,59,29";alt="Area4"
30     InterWiki:RemotePage;shape="rect";coords="181,120,59,29";alt="Area5"
31     }}}
32
33     ImageMap Parser is partly based on ImageLink Macro
34     ImageLink Macro
35     @copyright: 2001 by Jeff Kunce,
36                 2004 by Marcin Zalewski,
37                 2004-2006 by Reimar Bauer,
38                 2006 by Thomas Waldmann
39     @license: GNU GPL, see COPYING for details.
40
41     ImageMap Parser
42     @copyright: 2006,2007 by Oliver Siemoneit
43     @license: GNU GPL, see COPYING for details.
44
45     Changes:
46
47     Version 1.1
48     * Made code PEP8 compatible.
49     * Parameter checking and stripping added to prevent inserting of malicious
50       code in html page via parser call.
51
52     Version 1.2
53     * Fixed ouput abstraction violations: on other formatters than html-formatter
54       just the specified image is output via formatter.image and map information
55       dropped.
56     * In case of missing alt texts: "alt" ist set to alt="" (for the whole map)
57       and to alt="area_url" (for the different clickable areas). 
58     * Now also "title" supported to generate tooltips for the map areas.
59     * Interwiki links can also be specified in "wiki:MoinMoin/Page" syntax now.
60
61     Version ?
62     * Allow setting any HTML attribute that is allowed by the HTML4.01 spec,
63       except for javascript event handlers.
64     
65 """
66
67 import os, random
68 from MoinMoin import wikiutil, config
69 from MoinMoin.action import AttachFile
70
71 # Define the valid attributes for map and area elements. These are directly
72 # taken from the HTML4.01 spec at http://www.w3.org/TR/html401/ The lists below
73 # mimic the structure used in the HTML spec.
74 html_core_attrs = ['id', 'class', 'style', 'title']
75 html_i18n = ['lang', 'dir']
76 html_events = [] # event attributes left out for security reasons.
77 html_attrs = html_core_attrs + html_i18n + html_events
78 html_map_attrs = html_attrs + ['name']
79 html_area_attrs = html_attrs + ['shape', 'coords', 'href', 'nohref', 'alt', 'tabindex', 'accesskey']
80
81 def _is_URL(text):
82     return '://' in text
83
84 def _is_InterWiki(text):
85     return ':' in text
86
87 def _is_allowed_Para(para, allowed_paras):
88     found = False
89     para += '="'
90     for p in allowed_paras:
91         if para.startswith(p):
92             found = True
93     return found
94
95 def _strip_Para(para):
96     _para = wikiutil.escape(para)
97     if para.count('"') < 2:
98         return _para
99     shortend_para = _para[0:_para.find('"')+1]
100     cut_para = _para[_para.find('"')+1:len(_para)]
101     shortend_para += cut_para[0:cut_para.find('"')+1]
102     return shortend_para
103
104
105 class Parser:
106
107     def __init__(self, raw, request, **kw):
108         self.raw = raw
109         self.request = request
110
111     def format(self, formatter):
112         request = self.request
113         _ = request.getText
114         row = self.raw.split('\n')
115                
116         # Produce <img ...> html-code stuff
117         paras = row[0].split(';')
118         image = wikiutil.escape(paras[0])
119         mapname = '%s_%s' % ([image, image[:15]][(len(image) > 14)], str(random.randint(1, 999999)))
120         
121         if _is_URL(image):
122             imgurl = image
123         elif _is_InterWiki(image):
124             if image.startswith('wiki:'):
125                 image = image[5:]
126             wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(request, image)
127             imgurl = wikiutil.join_wiki(wikiurl, wikitail)
128         else:
129             pagename, attname = AttachFile.absoluteName(image, formatter.page.page_name)
130             imgurl = AttachFile.getAttachUrl(pagename, attname, request)
131             attachment_fname = AttachFile.getFilename(request, pagename, attname)
132
133             if not os.path.exists(attachment_fname):
134                 linktext = _('Upload new attachment "%(filename)s"')
135                 output = wikiutil.link_tag(request,
136                                          ('%s?action=AttachFile&rename=%s' % (
137                                              wikiutil.quoteWikinameURL(pagename),
138                                              wikiutil.url_quote_plus(attname))),
139                                           text=linktext % {'filename': attname},
140                                           formatter=formatter)
141                 request.write(output)
142                 return
143         
144         html = '''
145 <img src="%s"''' % imgurl
146         paras.pop(0)
147
148         kw = {}
149         kw['src'] = imgurl
150         for p in paras:
151             # Prevent attacks like: pic.png;height="10" onmouseover="ExecuteBadCode()";alt="..";
152             # and: pic.png;height="10&#34; onmouseover=&#34;ExecuteBadCode()";alt="..";
153             # and: pic.png;height=&#34;10&#34; onmouseover="ExecuteBadCode()";alt="..";
154             p = _strip_Para(p)
155             if _is_allowed_Para(p, html_map_attrs): 
156                 html += ' %s' % p
157                 # Prepare dict for formatter.image if formatter.rawHTML call fails
158                 key, value = p.split('=', 1)
159                 kw[str(key.lower())] = value.strip('"')
160
161         # If there is no alt provided, create one
162         if not 'alt' in kw:
163             kw['alt'] = image
164             html += ' alt=""'
165
166         html += ' usemap="#%s"> ' % mapname
167         row.pop(0)
168         
169         # Produce <map ..> html-code stuff
170         html += '''
171 <map name="%s">''' % mapname
172
173         for p in row:
174             paras = p.split(';')
175             paras[0] = wikiutil.escape(paras[0])
176
177             if _is_URL(paras[0]):
178                 area_url = paras[0]
179             elif _is_InterWiki(paras[0]):
180                 if paras[0].startswith('wiki:'):
181                     paras[0] = paras[0][5:]
182                 wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(request, paras[0])
183                 area_url = wikiutil.join_wiki(wikiurl, wikitail)
184             else:
185                 area_url = wikiutil.quoteWikinameURL(paras[0])
186             paras.pop(0)
187             
188             html += '''
189     <area href="%s"''' % area_url
190
191             for i in paras:
192                 # Prevent attacks like: FrontPage;shape="rect" onmouseover="ExecuteBadCode()";coords="..";
193                 # and: FrontPage;shape="rect&#34; onmouseover=&#34;ExecuteBadCode()";coords="..";
194                 # and: FrontPage;shape=&#34;rect&#34; onmouseover="ExecuteBadCode()";coords="..";
195                 i = _strip_Para(i) 
196                 if _is_allowed_Para(i, html_area_attrs): 
197                     html += ' %s' % i
198             # If there is no alt provided at all, set alt to area_url
199             if p.lower().find('alt="') == -1:
200                 html += ' alt="%s"' % area_url
201
202             html += '>'
203
204         html += '''
205 </map>
206 '''
207         # If current formatter is a HTML formatter, output image map with formatter.rawHTML().
208         # Otherwise just output image with formatter.image()
209         try:
210             request.write(formatter.rawHTML(html))
211         except:
212             request.write(formatter.image(**kw))