mirror of
https://github.com/vitalif/viewvc-4intranet
synced 2019-04-16 04:14:59 +03:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b97d2d49fa | ||
![]() |
3f43d12ee4 |
4
CHANGES
4
CHANGES
@@ -1,7 +1,3 @@
|
|||||||
Version 1.1.5 (released 29-Mar-2010)
|
|
||||||
|
|
||||||
* security fix: escape user-provided search_re input to avoid XSS attack
|
|
||||||
|
|
||||||
Version 1.1.4 (released 10-Mar-2010)
|
Version 1.1.4 (released 10-Mar-2010)
|
||||||
|
|
||||||
* security fix: escape user-provided query form input to avoid XSS attack
|
* security fix: escape user-provided query form input to avoid XSS attack
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<blockquote>
|
<blockquote>
|
||||||
|
|
||||||
<p><strong>Copyright © 1999-2010 The ViewCVS Group. All rights
|
<p><strong>Copyright © 1999-2009 The ViewCVS Group. All rights
|
||||||
reserved.</strong></p>
|
reserved.</strong></p>
|
||||||
|
|
||||||
<p>By using ViewVC, you agree to the terms and conditions set forth
|
<p>By using ViewVC, you agree to the terms and conditions set forth
|
||||||
@@ -60,7 +60,6 @@
|
|||||||
<li>April 10, 2007 — copyright years updated</li>
|
<li>April 10, 2007 — copyright years updated</li>
|
||||||
<li>February 22, 2008 — copyright years updated</li>
|
<li>February 22, 2008 — copyright years updated</li>
|
||||||
<li>March 18, 2009 — copyright years updated</li>
|
<li>March 18, 2009 — copyright years updated</li>
|
||||||
<li>March 29, 2010 — copyright years updated</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*-python-*-
|
# -*-python-*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 1999-2008 The ViewCVS Group. All Rights Reserved.
|
||||||
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
# Copyright (C) 2000 Curt Hagenlocher <curt@hagenlocher.org>
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
@@ -32,8 +32,9 @@ import os
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import math
|
import math
|
||||||
|
import cgi
|
||||||
import vclib
|
import vclib
|
||||||
import sapi
|
|
||||||
|
|
||||||
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
re_includes = re.compile('\\#(\\s*)include(\\s*)"(.*?)"')
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ class HTMLBlameSource:
|
|||||||
diff_url = None
|
diff_url = None
|
||||||
if item.prev_rev:
|
if item.prev_rev:
|
||||||
diff_url = '%sr1=%s&r2=%s' % (self.diff_url, item.prev_rev, item.rev)
|
diff_url = '%sr1=%s&r2=%s' % (self.diff_url, item.prev_rev, item.rev)
|
||||||
thisline = link_includes(sapi.escape(item.text), self.repos,
|
thisline = link_includes(cgi.escape(item.text), self.repos,
|
||||||
self.path_parts, self.include_url)
|
self.path_parts, self.include_url)
|
||||||
return _item(text=thisline, line_number=item.line_number,
|
return _item(text=thisline, line_number=item.line_number,
|
||||||
rev=item.rev, prev_rev=item.prev_rev,
|
rev=item.rev, prev_rev=item.prev_rev,
|
||||||
|
20
lib/idiff.py
20
lib/idiff.py
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-python-*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||||
@@ -20,7 +20,7 @@ import difflib
|
|||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import ezt
|
import ezt
|
||||||
import sapi
|
import cgi
|
||||||
|
|
||||||
def sidebyside(fromlines, tolines, context):
|
def sidebyside(fromlines, tolines, context):
|
||||||
"""Generate side by side diff"""
|
"""Generate side by side diff"""
|
||||||
@@ -49,18 +49,18 @@ def _mdiff_split(flag, (line_number, text)):
|
|||||||
while True:
|
while True:
|
||||||
m = _re_mdiff.search(text, pos)
|
m = _re_mdiff.search(text, pos)
|
||||||
if not m:
|
if not m:
|
||||||
segments.append(_item(text=sapi.escape(text[pos:]), type=None))
|
segments.append(_item(text=cgi.escape(text[pos:]), type=None))
|
||||||
break
|
break
|
||||||
|
|
||||||
if m.start() > pos:
|
if m.start() > pos:
|
||||||
segments.append(_item(text=sapi.escape(text[pos:m.start()]), type=None))
|
segments.append(_item(text=cgi.escape(text[pos:m.start()]), type=None))
|
||||||
|
|
||||||
if m.group(1) == "+":
|
if m.group(1) == "+":
|
||||||
segments.append(_item(text=sapi.escape(m.group(2)), type="add"))
|
segments.append(_item(text=cgi.escape(m.group(2)), type="add"))
|
||||||
elif m.group(1) == "-":
|
elif m.group(1) == "-":
|
||||||
segments.append(_item(text=sapi.escape(m.group(2)), type="remove"))
|
segments.append(_item(text=cgi.escape(m.group(2)), type="remove"))
|
||||||
elif m.group(1) == "^":
|
elif m.group(1) == "^":
|
||||||
segments.append(_item(text=sapi.escape(m.group(2)), type="change"))
|
segments.append(_item(text=cgi.escape(m.group(2)), type="change"))
|
||||||
|
|
||||||
pos = m.end()
|
pos = m.end()
|
||||||
|
|
||||||
@@ -166,12 +166,12 @@ def _differ_split(row, guide):
|
|||||||
|
|
||||||
for m in _re_differ.finditer(guide, pos):
|
for m in _re_differ.finditer(guide, pos):
|
||||||
if m.start() > pos:
|
if m.start() > pos:
|
||||||
segments.append(_item(text=sapi.escape(line[pos:m.start()]), type=None))
|
segments.append(_item(text=cgi.escape(line[pos:m.start()]), type=None))
|
||||||
segments.append(_item(text=sapi.escape(line[m.start():m.end()]),
|
segments.append(_item(text=cgi.escape(line[m.start():m.end()]),
|
||||||
type="change"))
|
type="change"))
|
||||||
pos = m.end()
|
pos = m.end()
|
||||||
|
|
||||||
segments.append(_item(text=sapi.escape(line[pos:]), type=None))
|
segments.append(_item(text=cgi.escape(line[pos:]), type=None))
|
||||||
|
|
||||||
return _item(gap=ezt.boolean(gap), type=type, segments=segments,
|
return _item(gap=ezt.boolean(gap), type=type, segments=segments,
|
||||||
left_number=left_number, right_number=right_number)
|
left_number=left_number, right_number=right_number)
|
||||||
|
12
lib/query.py
12
lib/query.py
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*-python-*-
|
# -*-python-*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 1999-2009 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||||
@@ -439,11 +439,11 @@ def main(server, cfg, viewvc_link):
|
|||||||
'cfg' : cfg,
|
'cfg' : cfg,
|
||||||
'address' : cfg.general.address,
|
'address' : cfg.general.address,
|
||||||
'vsn' : viewvc.__version__,
|
'vsn' : viewvc.__version__,
|
||||||
'repository' : server.escape(form_data.repository),
|
'repository' : server.escape(form_data.repository, 1),
|
||||||
'branch' : server.escape(form_data.branch),
|
'branch' : server.escape(form_data.branch, 1),
|
||||||
'directory' : server.escape(form_data.directory),
|
'directory' : server.escape(form_data.directory, 1),
|
||||||
'file' : server.escape(form_data.file),
|
'file' : server.escape(form_data.file, 1),
|
||||||
'who' : server.escape(form_data.who),
|
'who' : server.escape(form_data.who, 1),
|
||||||
'docroot' : cfg.options.docroot is None \
|
'docroot' : cfg.options.docroot is None \
|
||||||
and viewvc_link + '/' + viewvc.docroot_magic_path \
|
and viewvc_link + '/' + viewvc.docroot_magic_path \
|
||||||
or cfg.options.docroot,
|
or cfg.options.docroot,
|
||||||
|
33
lib/sapi.py
33
lib/sapi.py
@@ -1,6 +1,6 @@
|
|||||||
# -*-python-*-
|
# -*-python-*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 1999-2010 The ViewCVS Group. All Rights Reserved.
|
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
||||||
#
|
#
|
||||||
# By using this file, you agree to the terms and conditions set forth in
|
# By using this file, you agree to the terms and conditions set forth in
|
||||||
# the LICENSE.html file which can be found at the top level of the ViewVC
|
# the LICENSE.html file which can be found at the top level of the ViewVC
|
||||||
@@ -20,7 +20,6 @@ import string
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
import cgi
|
|
||||||
|
|
||||||
|
|
||||||
# global server object. It will be either a CgiServer or a proxy to
|
# global server object. It will be either a CgiServer or a proxy to
|
||||||
@@ -28,18 +27,6 @@ import cgi
|
|||||||
server = None
|
server = None
|
||||||
|
|
||||||
|
|
||||||
# Simple HTML string escaping. Note that we always escape the
|
|
||||||
# double-quote character -- ViewVC shouldn't ever need to preserve
|
|
||||||
# that character as-is, and sometimes needs to embed escaped values
|
|
||||||
# into HTML attributes.
|
|
||||||
def escape(s):
|
|
||||||
s = string.replace(s, '&', '&')
|
|
||||||
s = string.replace(s, '>', '>')
|
|
||||||
s = string.replace(s, '<', '<')
|
|
||||||
s = string.replace(s, '"', """)
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pageGlobals = {}
|
self.pageGlobals = {}
|
||||||
@@ -47,9 +34,6 @@ class Server:
|
|||||||
def self(self):
|
def self(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def escape(self, s):
|
|
||||||
return escape(s)
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -145,6 +129,9 @@ class CgiServer(Server):
|
|||||||
global server
|
global server
|
||||||
server = self
|
server = self
|
||||||
|
|
||||||
|
global cgi
|
||||||
|
import cgi
|
||||||
|
|
||||||
def addheader(self, name, value):
|
def addheader(self, name, value):
|
||||||
self.headers.append((name, value))
|
self.headers.append((name, value))
|
||||||
|
|
||||||
@@ -173,6 +160,9 @@ class CgiServer(Server):
|
|||||||
self.header(status='301 Moved')
|
self.header(status='301 Moved')
|
||||||
sys.stdout.write('This document is located <a href="%s">here</a>.\n' % url)
|
sys.stdout.write('This document is located <a href="%s">here</a>.\n' % url)
|
||||||
|
|
||||||
|
def escape(self, s, quote = None):
|
||||||
|
return cgi.escape(s, quote)
|
||||||
|
|
||||||
def getenv(self, name, value=None):
|
def getenv(self, name, value=None):
|
||||||
ret = os.environ.get(name, value)
|
ret = os.environ.get(name, value)
|
||||||
if self.iis and name == 'PATH_INFO' and ret:
|
if self.iis and name == 'PATH_INFO' and ret:
|
||||||
@@ -229,6 +219,9 @@ class AspServer(ThreadedServer):
|
|||||||
def redirect(self, url):
|
def redirect(self, url):
|
||||||
self.response.Redirect(url)
|
self.response.Redirect(url)
|
||||||
|
|
||||||
|
def escape(self, s, quote = None):
|
||||||
|
return self.server.HTMLEncode(str(s))
|
||||||
|
|
||||||
def getenv(self, name, value = None):
|
def getenv(self, name, value = None):
|
||||||
ret = self.request.ServerVariables(name)()
|
ret = self.request.ServerVariables(name)()
|
||||||
if not type(ret) is types.UnicodeType:
|
if not type(ret) is types.UnicodeType:
|
||||||
@@ -290,6 +283,9 @@ class ModPythonServer(ThreadedServer):
|
|||||||
self.request = request
|
self.request = request
|
||||||
self.headerSent = 0
|
self.headerSent = 0
|
||||||
|
|
||||||
|
global cgi
|
||||||
|
import cgi
|
||||||
|
|
||||||
def addheader(self, name, value):
|
def addheader(self, name, value):
|
||||||
self.request.headers_out.add(name, value)
|
self.request.headers_out.add(name, value)
|
||||||
|
|
||||||
@@ -312,6 +308,9 @@ class ModPythonServer(ThreadedServer):
|
|||||||
self.request.write("You are being redirected to <a href=\"%s\">%s</a>"
|
self.request.write("You are being redirected to <a href=\"%s\">%s</a>"
|
||||||
% (url, url))
|
% (url, url))
|
||||||
|
|
||||||
|
def escape(self, s, quote = None):
|
||||||
|
return cgi.escape(s, quote)
|
||||||
|
|
||||||
def getenv(self, name, value = None):
|
def getenv(self, name, value = None):
|
||||||
try:
|
try:
|
||||||
return self.request.subprocess_env[name]
|
return self.request.subprocess_env[name]
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
||||||
__version__ = '1.1.5'
|
__version__ = '1.1.4'
|
||||||
|
|
||||||
# this comes from our library; measure the startup time
|
# this comes from our library; measure the startup time
|
||||||
import debug
|
import debug
|
||||||
@@ -24,6 +24,7 @@ debug.t_start('imports')
|
|||||||
# standard modules that we know are in the path or builtin
|
# standard modules that we know are in the path or builtin
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import cgi
|
||||||
import gzip
|
import gzip
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
@@ -431,8 +432,7 @@ class Request:
|
|||||||
action = self.server.escape(urllib.quote(url, _URL_SAFE_CHARS))
|
action = self.server.escape(urllib.quote(url, _URL_SAFE_CHARS))
|
||||||
hidden_values = []
|
hidden_values = []
|
||||||
for name, value in params.items():
|
for name, value in params.items():
|
||||||
hidden_values.append(_item(name=self.server.escape(name),
|
hidden_values.append(_item(name=name, value=value))
|
||||||
value=self.server.escape(value)))
|
|
||||||
return action, hidden_values
|
return action, hidden_values
|
||||||
|
|
||||||
def get_link(self, view_func=None, where=None, pathtype=None, params=None):
|
def get_link(self, view_func=None, where=None, pathtype=None, params=None):
|
||||||
@@ -1075,6 +1075,9 @@ def get_file_view_info(request, where, rev=None, mime_type=None, pathrev=-1):
|
|||||||
revision_href=revision_href,
|
revision_href=revision_href,
|
||||||
prefer_markup=ezt.boolean(prefer_markup))
|
prefer_markup=ezt.boolean(prefer_markup))
|
||||||
|
|
||||||
|
def htmlify(html):
|
||||||
|
return html and cgi.escape(html) or html
|
||||||
|
|
||||||
|
|
||||||
# Matches URLs
|
# Matches URLs
|
||||||
_re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
|
_re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)'
|
||||||
@@ -1107,8 +1110,8 @@ class HtmlFormatter:
|
|||||||
"""
|
"""
|
||||||
s = mobj.group(0)
|
s = mobj.group(0)
|
||||||
trunc_s = maxlen and s[:maxlen] or s
|
trunc_s = maxlen and s[:maxlen] or s
|
||||||
return '<a href="%s">%s</a>' % (sapi.escape(s),
|
return '<a href="%s">%s</a>' % (cgi.escape(s),
|
||||||
sapi.escape(trunc_s)), \
|
cgi.escape(trunc_s)), \
|
||||||
len(trunc_s)
|
len(trunc_s)
|
||||||
|
|
||||||
def format_email(self, mobj, userdata, maxlen=0):
|
def format_email(self, mobj, userdata, maxlen=0):
|
||||||
@@ -1159,7 +1162,7 @@ class HtmlFormatter:
|
|||||||
- the number of characters returned.
|
- the number of characters returned.
|
||||||
"""
|
"""
|
||||||
trunc_s = maxlen and s[:maxlen] or s
|
trunc_s = maxlen and s[:maxlen] or s
|
||||||
return sapi.escape(trunc_s), len(trunc_s)
|
return cgi.escape(trunc_s), len(trunc_s)
|
||||||
|
|
||||||
def add_formatter(self, regexp, conv, userdata=None):
|
def add_formatter(self, regexp, conv, userdata=None):
|
||||||
"""Register a formatter which finds instances of strings matching
|
"""Register a formatter which finds instances of strings matching
|
||||||
@@ -1464,7 +1467,7 @@ def copy_stream(src, dst, htmlize=0):
|
|||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
if htmlize:
|
if htmlize:
|
||||||
chunk = sapi.escape(chunk)
|
chunk = htmlify(chunk)
|
||||||
dst.write(chunk)
|
dst.write(chunk)
|
||||||
|
|
||||||
class MarkupPipeWrapper:
|
class MarkupPipeWrapper:
|
||||||
@@ -1493,7 +1496,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename, mime_type):
|
|||||||
blame_source = []
|
blame_source = []
|
||||||
if blame_data:
|
if blame_data:
|
||||||
for i in blame_data:
|
for i in blame_data:
|
||||||
i.text = sapi.escape(i.text)
|
i.text = cgi.escape(i.text)
|
||||||
i.diff_href = None
|
i.diff_href = None
|
||||||
if i.prev_rev:
|
if i.prev_rev:
|
||||||
i.diff_href = request.get_url(view_func=view_diff,
|
i.diff_href = request.get_url(view_func=view_diff,
|
||||||
@@ -1556,7 +1559,7 @@ def markup_stream_pygments(request, cfg, blame_data, fp, filename, mime_type):
|
|||||||
if not line:
|
if not line:
|
||||||
break
|
break
|
||||||
line_no = line_no + 1
|
line_no = line_no + 1
|
||||||
line = sapi.escape(string.expandtabs(line, cfg.options.tabsize))
|
line = cgi.escape(string.expandtabs(line, cfg.options.tabsize))
|
||||||
item = vclib.Annotation(line, line_no, None, None, None, None)
|
item = vclib.Annotation(line, line_no, None, None, None, None)
|
||||||
item.diff_href = None
|
item.diff_href = None
|
||||||
lines.append(item)
|
lines.append(item)
|
||||||
@@ -2031,7 +2034,7 @@ def view_directory(request):
|
|||||||
'entries' : rows,
|
'entries' : rows,
|
||||||
'sortby' : sortby,
|
'sortby' : sortby,
|
||||||
'sortdir' : sortdir,
|
'sortdir' : sortdir,
|
||||||
'search_re' : request.server.escape(search_re),
|
'search_re' : htmlify(search_re),
|
||||||
'dir_pagestart' : None,
|
'dir_pagestart' : None,
|
||||||
'sortby_file_href' : request.get_url(params={'sortby': 'file',
|
'sortby_file_href' : request.get_url(params={'sortby': 'file',
|
||||||
'sortdir': None},
|
'sortdir': None},
|
||||||
@@ -2763,7 +2766,7 @@ class DiffSource:
|
|||||||
hr_breakable = self.cfg.options.hr_breakable
|
hr_breakable = self.cfg.options.hr_breakable
|
||||||
|
|
||||||
# in the code below, "\x01" will be our stand-in for "&". We don't want
|
# in the code below, "\x01" will be our stand-in for "&". We don't want
|
||||||
# to insert "&" because it would get escaped by sapi.escape(). Similarly,
|
# to insert "&" because it would get escaped by htmlify(). Similarly,
|
||||||
# we use "\x02" as a stand-in for "<br>"
|
# we use "\x02" as a stand-in for "<br>"
|
||||||
|
|
||||||
if hr_breakable > 1 and len(text) > hr_breakable:
|
if hr_breakable > 1 and len(text) > hr_breakable:
|
||||||
@@ -2773,7 +2776,7 @@ class DiffSource:
|
|||||||
text = string.replace(text, ' ', ' \x01nbsp;')
|
text = string.replace(text, ' ', ' \x01nbsp;')
|
||||||
else:
|
else:
|
||||||
text = string.replace(text, ' ', '\x01nbsp;')
|
text = string.replace(text, ' ', '\x01nbsp;')
|
||||||
text = sapi.escape(text)
|
text = htmlify(text)
|
||||||
text = string.replace(text, '\x01', '&')
|
text = string.replace(text, '\x01', '&')
|
||||||
text = string.replace(text, '\x02',
|
text = string.replace(text, '\x02',
|
||||||
'<span style="color:red">\</span><br />')
|
'<span style="color:red">\</span><br />')
|
||||||
@@ -3154,7 +3157,7 @@ def view_diff(request):
|
|||||||
else:
|
else:
|
||||||
changes = DiffSource(fp, cfg)
|
changes = DiffSource(fp, cfg)
|
||||||
else:
|
else:
|
||||||
raw_diff_fp = MarkupPipeWrapper(fp, request.server.escape(headers), None, 1)
|
raw_diff_fp = MarkupPipeWrapper(fp, htmlify(headers), None, 1)
|
||||||
|
|
||||||
no_format_params = request.query_dict.copy()
|
no_format_params = request.query_dict.copy()
|
||||||
no_format_params['diff_format'] = None
|
no_format_params['diff_format'] = None
|
||||||
@@ -3701,7 +3704,7 @@ def english_query(request):
|
|||||||
ret.append('on all branches ')
|
ret.append('on all branches ')
|
||||||
comment = request.query_dict.get('comment', '')
|
comment = request.query_dict.get('comment', '')
|
||||||
if comment:
|
if comment:
|
||||||
ret.append('with comment <i>%s</i> ' % request.server.escape(comment))
|
ret.append('with comment <i>%s</i> ' % htmlify(comment))
|
||||||
if who:
|
if who:
|
||||||
ret.append('by <em>%s</em> ' % request.server.escape(who))
|
ret.append('by <em>%s</em> ' % request.server.escape(who))
|
||||||
date = request.query_dict.get('date', 'hours')
|
date = request.query_dict.get('date', 'hours')
|
||||||
|
Reference in New Issue
Block a user