mailman  2.1.39
About: Mailman 2 - The GNU Mailing List Management System.
  Fossies Dox: mailman-2.1.39.tgz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

edithtml.py
Go to the documentation of this file.
1# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
2#
3# This program is free software; you can redistribute it and/or
4# modify it under the terms of the GNU General Public License
5# as published by the Free Software Foundation; either version 2
6# of the License, or (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16# USA.
17
18"""Script which implements admin editing of the list's html templates."""
19
20import os
21import cgi
22import errno
23import re
24
25from Mailman import Utils
26from Mailman import MailList
27from Mailman.htmlformat import *
28from Mailman.HTMLFormatter import HTMLFormatter
29from Mailman import Errors
30from Mailman.Cgi import Auth
31from Mailman.Logging.Syslog import syslog
32from Mailman import i18n
33from Mailman.CSRFcheck import csrf_check
34
35_ = i18n._
36
37AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin)
38
39
40
41def main():
42 # Trick out pygettext since we want to mark template_data as translatable,
43 # but we don't want to actually translate it here.
44 def _(s):
45 return s
46
47 template_data = (
48 ('listinfo.html', _('General list information page')),
49 ('subscribe.html', _('Subscribe results page')),
50 ('options.html', _('User specific options page')),
51 ('subscribeack.txt', _('Welcome email text file')),
52 ('masthead.txt', _('Digest masthead')),
53 ('postheld.txt', _('User notice of held post')),
54 ('approve.txt', _('User notice of held subscription')),
55 ('refuse.txt', _('Notice of post refused by moderator')),
56 ('invite.txt', _('Invitation to join list')),
57 ('verify.txt', _('Request to confirm subscription')),
58 ('unsub.txt', _('Request to confirm unsubscription')),
59 ('nomoretoday.txt', _('User notice of autoresponse limit')),
60 ('postack.txt', _('User post acknowledgement')),
61 ('disabled.txt', _('Subscription disabled by bounce warning')),
62 ('admlogin.html', _('Admin/moderator login page')),
63 ('private.html', _('Private archive login page')),
64 ('userpass.txt', _('On demand password reminder')),
65 )
66
67 _ = i18n._
68 doc = Document()
69
70 # Set up the system default language
71 i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
72 doc.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
73
74 parts = Utils.GetPathPieces()
75 if not parts:
76 doc.AddItem(Header(2, _("List name is required.")))
77 print doc.Format()
78 return
79
80 listname = parts[0].lower()
81 try:
82 mlist = MailList.MailList(listname, lock=0)
83 except Errors.MMListError, e:
84 # Avoid cross-site scripting attacks
85 safelistname = Utils.websafe(listname)
86 doc.AddItem(Header(2, _('No such list <em>%(safelistname)s</em>')))
87 # Send this with a 404 status.
88 print 'Status: 404 Not Found'
89 print doc.Format()
90 syslog('error', 'edithtml: No such list "%s": %s', listname, e)
91 return
92
93 # Now that we have a valid list, set the language to its default
94 i18n.set_language(mlist.preferred_language)
95 doc.set_language(mlist.preferred_language)
96
97 # Must be authenticated to get any farther
98 cgidata = cgi.FieldStorage()
99 try:
100 cgidata.getfirst('adminpw', '')
101 except TypeError:
102 # Someone crafted a POST with a bad Content-Type:.
103 doc.AddItem(Header(2, _("Error")))
104 doc.AddItem(Bold(_('Invalid options to CGI script.')))
105 # Send this with a 400 status.
106 print 'Status: 400 Bad Request'
107 print doc.Format()
108 return
109
110 # CSRF check
111 safe_params = ['VARHELP', 'adminpw', 'admlogin']
112 params = cgidata.keys()
113 if set(params) - set(safe_params):
114 csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'),
115 'admin')
116 else:
117 csrf_checked = True
118 # if password is present, void cookie to force password authentication.
119 if cgidata.getfirst('adminpw'):
120 os.environ['HTTP_COOKIE'] = ''
121 csrf_checked = True
122
123 # Editing the html for a list is limited to the list admin and site admin.
124 if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
125 mm_cfg.AuthSiteAdmin),
126 cgidata.getfirst('adminpw', '')):
127 if cgidata.has_key('admlogin'):
128 # This is a re-authorization attempt
129 msg = Bold(FontSize('+1', _('Authorization failed.'))).Format()
130 remote = os.environ.get('HTTP_FORWARDED_FOR',
131 os.environ.get('HTTP_X_FORWARDED_FOR',
132 os.environ.get('REMOTE_ADDR',
133 'unidentified origin')))
134 syslog('security',
135 'Authorization failed (edithtml): list=%s: remote=%s',
136 listname, remote)
137 else:
138 msg = ''
139 Auth.loginpage(mlist, 'admin', msg=msg)
140 return
141
142 # See if the user want to see this page in other language
143 language = cgidata.getfirst('language', '')
144 if language not in mlist.GetAvailableLanguages():
145 language = mlist.preferred_language
146 i18n.set_language(language)
147 doc.set_language(language)
148
149 realname = mlist.real_name
150 if len(parts) > 1:
151 template_name = parts[1]
152 for (template, info) in template_data:
153 if template == template_name:
154 template_info = _(info)
155 doc.SetTitle(_(
156 '%(realname)s -- Edit html for %(template_info)s'))
157 break
158 else:
159 # Avoid cross-site scripting attacks
160 safetemplatename = Utils.websafe(template_name)
161 doc.SetTitle(_('Edit HTML : Error'))
162 doc.AddItem(Header(2, _("%(safetemplatename)s: Invalid template")))
163 doc.AddItem(mlist.GetMailmanFooter())
164 print doc.Format()
165 return
166 else:
167 doc.SetTitle(_('%(realname)s -- HTML Page Editing'))
168 doc.AddItem(Header(1, _('%(realname)s -- HTML Page Editing')))
169 doc.AddItem(Header(2, _('Select page to edit:')))
170 template_list = UnorderedList()
171 for (template, info) in template_data:
172 l = Link(mlist.GetScriptURL('edithtml') + '/' + template, _(info))
173 template_list.AddItem(l)
174 doc.AddItem(FontSize("+2", template_list))
175 doc.AddItem(mlist.GetMailmanFooter())
176 print doc.Format()
177 return
178
179 try:
180 if cgidata.keys() and not cgidata.has_key('langform'):
181 if csrf_checked:
182 ChangeHTML(mlist, cgidata, template_name, doc, lang=language)
183 else:
184 doc.addError(
185 _('The form lifetime has expired. (request forgery check)'))
186 FormatHTML(mlist, doc, template_name, template_info, lang=language)
187 finally:
188 doc.AddItem(mlist.GetMailmanFooter())
189 print doc.Format()
190
191
192
193def FormatHTML(mlist, doc, template_name, template_info, lang=None):
194 if lang not in mlist.GetAvailableLanguages():
195 lang = mlist.preferred_language
196 lcset = Utils.GetCharSet(lang)
197 doc.AddItem(Header(1,'%s:' % mlist.real_name))
198 doc.AddItem(Header(1, template_info))
199 doc.AddItem('<hr>')
200
201 link = Link(mlist.GetScriptURL('admin'),
202 _('View or edit the list configuration information.'))
203 backlink = Link(mlist.GetScriptURL('edithtml'),
204 _('Edit the public HTML pages and text files'))
205
206 doc.AddItem(FontSize("+1", link))
207 doc.AddItem('<br>')
208 doc.AddItem(FontSize("+1", backlink))
209 doc.AddItem('<p>')
210 doc.AddItem('<hr>')
211 if len(mlist.GetAvailableLanguages()) > 1:
212 langform = Form(mlist.GetScriptURL('edithtml') + '/' + template_name,
213 mlist=mlist, contexts=AUTH_CONTEXTS)
214 langform.AddItem(
215 mlist.FormatButton('editlang-button',
216 text = _("Edit this template for")))
217 langform.AddItem(mlist.GetLangSelectBox(lang))
218 langform.AddItem(Hidden('langform', 'True'))
219 doc.AddItem(langform)
220 doc.AddItem('<hr>')
221 form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name,
222 mlist=mlist, contexts=AUTH_CONTEXTS)
223 text = Utils.maketext(template_name, raw=1, lang=lang, mlist=mlist)
224 # MAS: Don't websafe twice. TextArea does it.
225 form.AddItem(TextArea('html_code', text, rows=40, cols=75))
226 form.AddItem('<p>' + _('When you are done making changes...'))
227 if lang != mlist.preferred_language:
228 form.AddItem(Hidden('language', lang))
229 form.AddItem(SubmitButton('submit', _('Submit Changes')))
230 doc.AddItem(form)
231
232
233
234def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None):
235 if lang not in mlist.GetAvailableLanguages():
236 lang = mlist.preferred_language
237 if not cgi_info.has_key('html_code'):
238 doc.AddItem(Header(3,_("Can't have empty html page.")))
239 doc.AddItem(Header(3,_("HTML Unchanged.")))
240 doc.AddItem('<hr>')
241 return
242 code = cgi_info['html_code'].value
243 if Utils.suspiciousHTML(code):
244 doc.AddItem(Header(3,
245 _("""The page you saved contains suspicious HTML that could
246potentially expose your users to cross-site scripting attacks. This change
247has therefore been rejected. If you still want to make these changes, you
248must have shell access to your Mailman server.
249 """)))
250 doc.AddItem(_('See '))
251 doc.AddItem(Link(
252'http://wiki.list.org/x/jYA9',
253 _('FAQ 4.48.')))
254 doc.AddItem(Header(3,_("Page Unchanged.")))
255 doc.AddItem('<hr>')
256 return
257 langdir = os.path.join(mlist.fullpath(), lang)
258 # Make sure the directory exists
259 omask = os.umask(0)
260 try:
261 try:
262 os.mkdir(langdir, 02775)
263 except OSError, e:
264 if e.errno <> errno.EEXIST: raise
265 finally:
266 os.umask(omask)
267 fp = open(os.path.join(langdir, template_name), 'w')
268 try:
269 fp.write(code)
270 finally:
271 fp.close()
272 doc.AddItem(Header(3, _('HTML successfully updated.')))
273 doc.AddItem('<hr>')
def csrf_check(mlist, token, cgi_user=None)
Definition: CSRFcheck.py:58
def ChangeHTML(mlist, cgi_info, template_name, doc, lang=None)
Definition: edithtml.py:234
def FormatHTML(mlist, doc, template_name, template_info, lang=None)
Definition: edithtml.py:193