Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/log.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/log.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/log.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/log.py Sat Nov 15 01:14:46 2014 @@ -28,8 +28,7 @@ from trac.resource import ResourceNotFou from trac.util import Ranges from trac.util.text import to_unicode, wrap from trac.util.translation import _ -from trac.versioncontrol.api import (RepositoryManager, Changeset, - NoSuchChangeset) +from trac.versioncontrol.api import Changeset, RepositoryManager from trac.versioncontrol.web_ui.changeset import ChangesetModule from trac.versioncontrol.web_ui.util import * from trac.web import IRequestHandler @@ -90,8 +89,12 @@ class LogModule(Component): reponame, repos, path = rm.get_repository_by_path(path) if not repos: - raise ResourceNotFound(_("Repository '%(repo)s' not found", - repo=reponame)) + if path == '/': + raise TracError(_("No repository specified and no default" + " repository configured.")) + else: + raise ResourceNotFound(_("Repository '%(repo)s' not found", + repo=reponame or path.strip('/'))) if reponame != repos.reponame: # Redirect alias qs = req.query_string @@ -106,11 +109,11 @@ class LogModule(Component): revranges = None if revs: try: - revranges = Ranges(revs) + revranges = self._normalize_ranges(repos, path, revs) rev = revranges.b except ValueError: pass - rev = unicode(repos.normalize_rev(rev)) + rev = repos.normalize_rev(rev) display_rev = repos.display_rev # The `history()` method depends on the mode: @@ -139,25 +142,27 @@ class LogModule(Component): node_history = list(node.get_history(2)) p, rev, chg = node_history[0] if repos.rev_older_than(rev, a): - break # simply skip, no separator + break # simply skip, no separator if 'CHANGESET_VIEW' in req.perm(cset_resource(id=rev)): if expected_next_item: # check whether we're continuing previous range np, nrev, nchg = expected_next_item - if rev != nrev: # no, we need a separator + if rev != nrev: # no, we need a separator yield (np, nrev, None) yield node_history[0] - prevpath = node_history[-1][0] # follow copy - b = repos.previous_rev(rev) if len(node_history) > 1: expected_next_item = node_history[-1] + prevpath = expected_next_item[0] # follow copy + b = expected_next_item[1] else: expected_next_item = None + break # no more older revisions if expected_next_item: yield (expected_next_item[0], expected_next_item[1], None) else: show_graph = path == '/' and not verbose \ and not repos.has_linear_changesets + def history(): node = get_existing_node(req, repos, path, rev) for h in node.get_history(): @@ -192,7 +197,7 @@ class LogModule(Component): break elif mode == 'path_history': depth -= 1 - if old_chg is None: # separator entry + if old_chg is None: # separator entry stop_limit = limit else: count += 1 @@ -200,13 +205,15 @@ class LogModule(Component): if count >= stop_limit: break previous_path = old_path - if info == []: + if not info: node = get_existing_node(req, repos, path, rev) if repos.rev_older_than(stop_rev, node.created_rev): # FIXME: we should send a 404 error here raise TracError(_("The file or directory '%(path)s' doesn't " - "exist at revision %(rev)s or at any previous revision.", - path=path, rev=display_rev(rev)), _('Nonexistent path')) + "exist at revision %(rev)s or at any " + "previous revision.", path=path, + rev=display_rev(rev)), + _('Nonexistent path')) # Generate graph data graph = {} @@ -231,7 +238,7 @@ class LogModule(Component): return req.href.log(repos.reponame or None, path, **params) if format in ('rss', 'changelog'): - info = [i for i in info if i['change']] # drop separators + info = [i for i in info if i['change']] # drop separators if info and count > limit: del info[-1] elif info and count >= limit: @@ -245,8 +252,9 @@ class LogModule(Component): older_revisions_href = make_log_href(next_path, rev=next_rev, revs=next_revranges) add_link(req, 'next', older_revisions_href, - _('Revision Log (restarting at %(path)s, rev. %(rev)s)', - path=next_path, rev=display_rev(next_rev))) + _('Revision Log (restarting at %(path)s, rev. ' + '%(rev)s)', path=next_path, + rev=display_rev(next_rev))) # only show fully 'limit' results, use `change == None` as a marker info[-1]['change'] = None @@ -275,11 +283,11 @@ class LogModule(Component): 'reponame': repos.reponame or None, 'repos': repos, 'path': path, 'rev': rev, 'stop_rev': stop_rev, 'display_rev': display_rev, 'revranges': revranges, - 'mode': mode, 'verbose': verbose, 'limit' : limit, + 'mode': mode, 'verbose': verbose, 'limit': limit, 'items': info, 'changes': changes, 'extra_changes': extra_changes, 'graph': graph, - 'wiki_format_messages': - self.config['changeset'].getbool('wiki_format_messages') + 'wiki_format_messages': self.config['changeset'] + .getbool('wiki_format_messages') } if format == 'changelog': @@ -294,8 +302,8 @@ class LogModule(Component): item_ranges = [] range = [] for item in info: - if item['change'] is None: # separator - if range: # start new range + if item['change'] is None: # separator + if range: # start new range range.append(item) item_ranges.append(range) range = [] @@ -320,7 +328,8 @@ class LogModule(Component): 'application/rss+xml', 'rss') changelog_href = make_log_href(path, format='changelog', revs=revs, stop_rev=stop_rev) - add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain') + add_link(req, 'alternate', changelog_href, _('ChangeLog'), + 'text/plain') add_ctxtnav(req, _('View Latest Revision'), href=req.href.browser(repos.reponame or None, path)) @@ -388,23 +397,27 @@ class LogModule(Component): repos = rm.get_repository(reponame) if repos: - revranges = None - if any(c for c in ':-,' if c in revs): - revranges = self._normalize_ranges(repos, path, revs) - revs = None if 'LOG_VIEW' in formatter.perm: + revranges = None + if any(c in revs for c in ':-,'): + try: + # try to parse into integer rev ranges + revranges = Ranges(revs.replace(':', '-'), + reorder=True) + revs = str(revranges) + except ValueError: + revranges = self._normalize_ranges(repos, path, + revs) if revranges: href = formatter.href.log(repos.reponame or None, path or '/', - revs=str(revranges)) + revs=revs) else: - try: - rev = repos.normalize_rev(revs) - except NoSuchChangeset: - rev = None + repos.normalize_rev(revs) # verify revision href = formatter.href.log(repos.reponame or None, - path or '/', rev=rev) - if query and (revranges or revs): + path or '/', + rev=revs or None) + if query and '?' in href: query = '&' + query[1:] return tag.a(label, class_='source', href=href + query + fragment) @@ -420,17 +433,22 @@ class LogModule(Component): LOG_LINK_RE = re.compile(r"([^@:]*)[@:]%s?" % REV_RANGE) def _normalize_ranges(self, repos, path, revs): - ranges = revs.replace(':', '-') try: # fast path; only numbers - return Ranges(ranges, reorder=True) + return Ranges(revs.replace(':', '-'), reorder=True) except ValueError: # slow path, normalize each rev - splitted_ranges = re.split(r'([-,])', ranges) + ranges = [] + for range in revs.split(','): + try: + a, b = range.replace(':', '-').split('-') + range = (a, b) + except ValueError: + range = (range,) + ranges.append('-'.join(str(repos.normalize_rev(r)) + for r in range)) + ranges = ','.join(ranges) try: - revs = [repos.normalize_rev(r) for r in splitted_ranges[::2]] - except NoSuchChangeset: + return Ranges(ranges) + except ValueError: return None - seps = splitted_ranges[1::2] + [''] - ranges = ''.join([str(rev)+sep for rev, sep in zip(revs, seps)]) - return Ranges(ranges)
Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/__init__.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/__init__.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/__init__.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/__init__.py Sat Nov 15 01:14:46 2014 @@ -1,9 +1,27 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2014 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.com/license.html. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/. + import unittest -from trac.versioncontrol.web_ui.tests import wikisyntax +from trac.versioncontrol.web_ui.tests import browser, changeset, log, \ + wikisyntax + def suite(): suite = unittest.TestSuite() + suite.addTest(browser.suite()) + suite.addTest(changeset.suite()) + suite.addTest(log.suite()) suite.addTest(wikisyntax.suite()) return suite Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/tests/wikisyntax.py Sat Nov 15 01:14:46 2014 @@ -1,28 +1,45 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.com/license.html. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/. import unittest from trac.test import Mock -from trac.versioncontrol import NoSuchChangeset, NoSuchNode from trac.versioncontrol.api import * from trac.versioncontrol.web_ui import * from trac.wiki.tests import formatter +YOUNGEST_REV = 200 + + def _get_changeset(rev): if rev == '1': return Mock(message="start", is_viewable=lambda perm: True) else: raise NoSuchChangeset(rev) + def _normalize_rev(rev): + if rev is None or rev in ('', 'head'): + return YOUNGEST_REV try: - return int(rev) + nrev = int(rev) + if nrev <= YOUNGEST_REV: + return nrev except ValueError: - if rev == 'head': - return '200' - else: - raise NoSuchChangeset(rev) + pass + raise NoSuchChangeset(rev) + def _get_node(path, rev=None): if path == 'foo': @@ -34,12 +51,14 @@ def _get_node(path, rev=None): return Mock(path=path, rev=rev, isfile=True, is_viewable=lambda resource: True) + def _get_repository(reponame): - return Mock(reponame=reponame, youngest_rev='200', + return Mock(reponame=reponame, youngest_rev=YOUNGEST_REV, get_changeset=_get_changeset, normalize_rev=_normalize_rev, get_node=_get_node) + def repository_setup(tc): setattr(tc.env, 'get_repository', _get_repository) setattr(RepositoryManager(tc.env), 'get_repository', _get_repository) @@ -177,24 +196,47 @@ LOG_TEST_CASES = u""" ============================== log: link resolver log:@12 log:trunk +log:trunk@head log:trunk@12 log:trunk@12:23 log:trunk@12-23 log:trunk:12:23 log:trunk:12-23 +log:trunk@12:head log:trunk:12-head -log:trunk:12@23 (bad, but shouldn't error out) +log:trunk:12@23 ------------------------------ <p> <a class="source" href="/log/?rev=12">log:@12</a> <a class="source" href="/log/trunk">log:trunk</a> +<a class="source" href="/log/trunk?rev=head">log:trunk@head</a> <a class="source" href="/log/trunk?rev=12">log:trunk@12</a> <a class="source" href="/log/trunk?revs=12-23">log:trunk@12:23</a> <a class="source" href="/log/trunk?revs=12-23">log:trunk@12-23</a> <a class="source" href="/log/trunk?revs=12-23">log:trunk:12:23</a> <a class="source" href="/log/trunk?revs=12-23">log:trunk:12-23</a> -<a class="source" href="/log/trunk?revs=12-200">log:trunk:12-head</a> -<a class="source" href="/log/trunk">log:trunk:12@23</a> (bad, but shouldn't error out) +<a class="source" href="/log/trunk?revs=12%3Ahead">log:trunk@12:head</a> +<a class="source" href="/log/trunk?revs=12-head">log:trunk:12-head</a> +<a class="missing source" title="No changeset 12@23 in the repository">log:trunk:12@23</a> +</p> +------------------------------ +============================== log: link resolver with missing revisions +log:@4242 +log:@4242-4243 +log:@notfound +log:@deadbeef:deadbef0 +log:trunk@4243 +log:trunk@notfound +[4242:4243] +------------------------------ +<p> +<a class="missing source" title="No changeset 4242 in the repository">log:@4242</a> +<a class="source" href="/log/?revs=4242-4243">log:@4242-4243</a> +<a class="missing source" title="No changeset notfound in the repository">log:@notfound</a> +<a class="missing source" title="No changeset deadbeef in the repository">log:@deadbeef:deadbef0</a> +<a class="missing source" title="No changeset 4243 in the repository">log:trunk@4243</a> +<a class="missing source" title="No changeset notfound in the repository">log:trunk@notfound</a> +<a class="source" href="/log/?revs=4242-4243">[4242:4243]</a> </p> ------------------------------ ============================== log: link resolver + query @@ -214,6 +256,21 @@ log:trunk@12?limit=10 <a class="source" href="/log/trunk?revs=10-20&verbose=yes&format=changelog">[10:20/trunk?verbose=yes&format=changelog]</a> </p> ------------------------------ +============================== log: link resolver + invalid ranges +log:@10-20-30 +log:@10,20-30,40-50-60 +log:@10:20:30 +[10-20-30] +[10:20:30] +------------------------------ +<p> +<a class="missing source" title="No changeset 10-20-30 in the repository">log:@10-20-30</a> +<a class="missing source" title="No changeset 40-50-60 in the repository">log:@10,20-30,40-50-60</a> +<a class="missing source" title="No changeset 10:20:30 in the repository">log:@10:20:30</a> +[10-20-30] +[10:20:30] +</p> +------------------------------ ============================== Multiple Log ranges r12:20,25,35:56,68,69,100-120 [12:20,25,35:56,68,69,100-120] Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/util.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/util.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/util.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/web_ui/util.py Sat Nov 15 01:14:46 2014 @@ -16,17 +16,21 @@ # Author: Jonas Borgström <[email protected]> # Christian Boos <[email protected]> +from StringIO import StringIO from itertools import izip +from zipfile import ZipFile, ZIP_DEFLATED from genshi.builder import tag from trac.resource import ResourceNotFound -from trac.util.datefmt import datetime, utc +from trac.util import content_disposition, create_zipinfo +from trac.util.datefmt import datetime, http_date, utc from trac.util.translation import tag_, _ from trac.versioncontrol.api import Changeset, NoSuchNode, NoSuchChangeset +from trac.web.api import RequestDone __all__ = ['get_changes', 'get_path_links', 'get_existing_node', - 'get_allowed_node', 'make_log_graph'] + 'get_allowed_node', 'make_log_graph', 'render_zip'] def get_changes(repos, revs, log=None): @@ -166,3 +170,63 @@ def make_log_graph(repos, revs): except StopIteration: pass return threads, vertices, columns + + +def render_zip(req, filename, repos, root_node, iter_nodes): + """Send a ZIP file containing the data corresponding to the `nodes` + iterable. + + :type root_node: `~trac.versioncontrol.api.Node` + :param root_node: optional ancestor for all the *nodes* + + :param iter_nodes: callable taking the optional *root_node* as input + and generating the `~trac.versioncontrol.api.Node` + for which the content should be added into the zip. + """ + req.send_response(200) + req.send_header('Content-Type', 'application/zip') + req.send_header('Content-Disposition', + content_disposition('inline', filename)) + if root_node: + req.send_header('Last-Modified', http_date(root_node.last_modified)) + root_path = root_node.path.rstrip('/') + else: + root_path = '' + if root_path: + root_path += '/' + root_name = root_node.name + '/' + else: + root_name = '' + root_len = len(root_path) + + buf = StringIO() + zipfile = ZipFile(buf, 'w', ZIP_DEFLATED) + for node in iter_nodes(root_node): + if node is root_node: + continue + path = node.path.strip('/') + assert path.startswith(root_path) + path = root_name + path[root_len:] + kwargs = {'mtime': node.last_modified} + data = None + if node.isfile: + data = node.get_processed_content(eol_hint='CRLF').read() + properties = node.get_properties() + # Subversion specific + if 'svn:special' in properties and data.startswith('link '): + data = data[5:] + kwargs['symlink'] = True + if 'svn:executable' in properties: + kwargs['executable'] = True + elif node.isdir and path: + kwargs['dir'] = True + data = '' + if data is not None: + zipfile.writestr(create_zipinfo(path, **kwargs), data) + zipfile.close() + + zip_str = buf.getvalue() + req.send_header("Content-Length", len(zip_str)) + req.end_headers() + req.write(zip_str) + raise RequestDone Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/__init__.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/__init__.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/__init__.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/__init__.py Sat Nov 15 01:14:46 2014 @@ -1,3 +1,16 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.com/license.html. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/. + # Workaround for http://bugs.python.org/issue6763 and # http://bugs.python.org/issue5853 thread issues import mimetypes Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/_fcgi.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/_fcgi.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/_fcgi.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/_fcgi.py Sat Nov 15 01:14:46 2014 @@ -1150,7 +1150,7 @@ class WSGIServer(Server): Set multithreaded to False if your application is not MT-safe. """ - if kw.has_key('handler'): + if 'handler' in kw: del kw['handler'] # Doesn't make sense to let this through super(WSGIServer, self).__init__(**kw) @@ -1278,9 +1278,9 @@ class WSGIServer(Server): def _sanitizeEnv(self, environ): """Ensure certain values are present, if required by WSGI.""" - if not environ.has_key('SCRIPT_NAME'): + if 'SCRIPT_NAME' not in environ: environ['SCRIPT_NAME'] = '' - if not environ.has_key('PATH_INFO'): + if 'PATH_INFO' not in environ: environ['PATH_INFO'] = '' # If any of these are missing, it probably signifies a broken @@ -1289,7 +1289,7 @@ class WSGIServer(Server): ('SERVER_NAME', 'localhost'), ('SERVER_PORT', '80'), ('SERVER_PROTOCOL', 'HTTP/1.0')]: - if not environ.has_key(name): + if name not in environ: environ['wsgi.errors'].write('%s: missing FastCGI param %s ' 'required by WSGI!\n' % (self.__class__.__name__, name)) Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/api.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/api.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/api.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/api.py Sat Nov 15 01:14:46 2014 @@ -23,19 +23,23 @@ from hashlib import md5 import new import mimetypes import os +import re import socket from StringIO import StringIO import sys import urlparse +from genshi.builder import Fragment from trac.core import Interface, TracError +from trac.perm import PermissionError from trac.util import get_last_traceback, unquote from trac.util.datefmt import http_date, localtz -from trac.util.text import empty, to_unicode +from trac.util.text import empty, exception_to_unicode, to_unicode from trac.util.translation import _ from trac.web.href import Href from trac.web.wsgi import _FileWrapper + class IAuthenticator(Interface): """Extension point interface for components that can provide the name of the remote user.""" @@ -129,7 +133,7 @@ HTTP_STATUS = dict([(code, reason.title( class HTTPException(Exception): def __init__(self, detail, *args): - if isinstance(detail, TracError): + if isinstance(detail, (TracError, PermissionError)): self.detail = detail.message self.reason = detail.title else: @@ -139,6 +143,35 @@ class HTTPException(Exception): Exception.__init__(self, '%s %s (%s)' % (self.code, self.reason, self.detail)) + @property + def message(self): + # The message is based on the e.detail, which can be an Exception + # object, but not a TracError one: when creating HTTPException, + # a TracError.message is directly assigned to e.detail + if isinstance(self.detail, Exception): # not a TracError or PermissionError + message = exception_to_unicode(self.detail) + elif isinstance(self.detail, Fragment): # TracError or PermissionError markup + message = self.detail + else: + message = to_unicode(self.detail) + return message + + @property + def title(self): + try: + # We first try to get localized error messages here, but we + # should ignore secondary errors if the main error was also + # due to i18n issues + title = _("Error") + if self.reason: + if title.lower() in self.reason.lower(): + title = self.reason + else: + title = _("Error: %(message)s", message=self.reason) + except Exception: + title = "Error" + return title + @classmethod def subclass(cls, name, code): """Create a new Exception class representing a HTTP status code.""" @@ -243,6 +276,10 @@ class RequestDone(Exception): """Marker exception that indicates whether request processing has completed and a response was sent. """ + iterable = None + + def __init__(self, iterable=None): + self.iterable = iterable class Cookie(SimpleCookie): @@ -356,7 +393,9 @@ class Request(object): Will be `None` if the user has not logged in using HTTP authentication. """ - return self.environ.get('REMOTE_USER') + user = self.environ.get('REMOTE_USER') + if user is not None: + return to_unicode(user) @property def scheme(self): @@ -405,11 +444,12 @@ class Request(object): `value` must either be an `unicode` string or can be converted to one (e.g. numbers, ...) """ - if name.lower() == 'content-type': + lower_name = name.lower() + if lower_name == 'content-type': ctpos = value.find('charset=') if ctpos >= 0: self._outcharset = value[ctpos + 8:].strip() - elif name.lower() == 'content-length': + elif lower_name == 'content-length': self._content_length = int(value) self._outheaders.append((name, unicode(value).encode('utf-8'))) @@ -442,7 +482,7 @@ class Request(object): extra = m.hexdigest() etag = 'W/"%s/%s/%s"' % (self.authname, http_date(datetime), extra) inm = self.get_header('If-None-Match') - if (not inm or inm != etag): + if not inm or inm != etag: self.send_header('ETag', etag) else: self.send_response(304) @@ -472,10 +512,12 @@ class Request(object): scheme, host = urlparse.urlparse(self.base_url)[:2] url = urlparse.urlunparse((scheme, host, url, None, None, None)) - # Workaround #10382, IE6+ bug when post and redirect with hash - if status == 303 and '#' in url and \ - ' MSIE ' in self.environ.get('HTTP_USER_AGENT', ''): - url = url.replace('#', '#__msie303:') + # Workaround #10382, IE6-IE9 bug when post and redirect with hash + if status == 303 and '#' in url: + match = re.search(' MSIE ([0-9]+)', + self.environ.get('HTTP_USER_AGENT', '')) + if match and int(match.group(1)) < 10: + url = url.replace('#', '#__msie303:') self.send_header('Location', url) self.send_header('Content-Type', 'text/plain') @@ -601,19 +643,17 @@ class Request(object): def write(self, data): """Write the given data to the response body. - `data` *must* be a `str` string, encoded with the charset - which has been specified in the ''Content-Type'' header - or 'utf-8' otherwise. - - Note that the ''Content-Length'' header must have been specified. - Its value either corresponds to the length of `data`, or, if there - are multiple calls to `write`, to the cumulated length of the `data` - arguments. + *data* **must** be a `str` string, encoded with the charset + which has been specified in the ``'Content-Type'`` header + or UTF-8 otherwise. + + Note that when the ``'Content-Length'`` header is specified, + its value either corresponds to the length of *data*, or, if + there are multiple calls to `write`, to the cumulative length + of the *data* arguments. """ if not self._write: self.end_headers() - if not hasattr(self, '_content_length'): - raise RuntimeError("No Content-Length header set") if isinstance(data, unicode): raise ValueError("Can't send unicode content") try: @@ -621,6 +661,12 @@ class Request(object): except (IOError, socket.error), e: if e.args[0] in (errno.EPIPE, errno.ECONNRESET, 10053, 10054): raise RequestDone + # Note that mod_wsgi raises an IOError with only a message + # if the client disconnects + if 'mod_wsgi.version' in self.environ and \ + e.args[0] in ('failed to write data', + 'client connection closed'): + raise RequestDone raise # Internal methods @@ -700,7 +746,7 @@ class Request(object): # server name and port default_port = {'http': 80, 'https': 443} if self.server_port and self.server_port != \ - default_port[self.scheme]: + default_port[self.scheme]: host = '%s:%d' % (self.server_name, self.server_port) else: host = self.server_name Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/auth.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/auth.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/auth.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/auth.py Sat Nov 15 01:14:46 2014 @@ -86,7 +86,7 @@ class LoginModule(Component): authname = None if req.remote_user: authname = req.remote_user - elif req.incookie.has_key('trac_auth'): + elif 'trac_auth' in req.incookie: authname = self._get_name_for_cookie(req, req.incookie['trac_auth']) @@ -108,7 +108,10 @@ class LoginModule(Component): yield ('metanav', 'login', _('logged in as %(user)s', user=req.authname)) yield ('metanav', 'logout', - tag.a(_('Logout'), href=req.href.logout())) + tag.form(tag.div(tag.button(_('Logout'), + name='logout', type='submit')), + action=req.href.logout(), method='post', + id='logout', class_='trac-logout')) else: yield ('metanav', 'login', tag.a(_('Login'), href=req.href.login())) @@ -155,8 +158,9 @@ class LoginModule(Component): if self.ignore_case: remote_user = remote_user.lower() - assert req.authname in ('anonymous', remote_user), \ - _('Already logged in as %(user)s.', user=req.authname) + if req.authname not in ('anonymous', remote_user): + raise TracError(_('Already logged in as %(user)s.', + user=req.authname)) with self.env.db_transaction as db: # Delete cookies older than 10 days @@ -192,6 +196,8 @@ class LoginModule(Component): Simply deletes the corresponding record from the auth_cookie table. """ + if req.method != 'POST': + return if req.authname == 'anonymous': # Not logged in return @@ -429,12 +435,12 @@ class DigestAuthentication(PasswordFileA 'nc', 'cnonce'] # Invalid response? for key in required_keys: - if not auth.has_key(key): + if key not in auth: self.send_auth_request(environ, start_response) return None # Unknown user? self.check_reload() - if not self.hash.has_key(auth['username']): + if auth['username'] not in self.hash: self.send_auth_request(environ, start_response) return None Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/chrome.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/chrome.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/chrome.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/chrome.py Sat Nov 15 01:14:46 2014 @@ -48,8 +48,8 @@ from trac.core import * from trac.env import IEnvironmentSetupParticipant, ISystemInfoProvider from trac.mimeview.api import RenderingContext, get_mimetype from trac.resource import * -from trac.util import compat, get_reporter_id, presentation, get_pkginfo, \ - pathjoin, translation +from trac.util import compat, get_reporter_id, html, presentation, \ + get_pkginfo, pathjoin, translation from trac.util.html import escape, plaintext from trac.util.text import pretty_size, obfuscate_email_address, \ shorten_line, unicode_quote_plus, to_unicode, \ @@ -135,42 +135,30 @@ def add_stylesheet(req, filename, mimety """Add a link to a style sheet to the chrome info so that it gets included in the generated HTML page. - If the filename is absolute (i.e. starts with a slash), the generated link - will be based off the application root path. If it is relative, the link - will be based off the `/chrome/` path. + If `filename` is a network-path reference (i.e. starts with a protocol + or `//`), the return value will not be modified. If `filename` is absolute + (i.e. starts with `/`), the generated link will be based off the + application root path. If it is relative, the link will be based off the + `/chrome/` path. """ - if filename.startswith(('http://', 'https://')): - href = filename - elif filename.startswith('common/') and 'htdocs_location' in req.chrome: - href = Href(req.chrome['htdocs_location'])(filename[7:]) - else: - href = req.href - if not filename.startswith('/'): - href = href.chrome - href = href(filename) + href = _chrome_resource_path(req, filename) add_link(req, 'stylesheet', href, mimetype=mimetype, media=media) def add_script(req, filename, mimetype='text/javascript', charset='utf-8', ie_if=None): """Add a reference to an external javascript file to the template. - If the filename is absolute (i.e. starts with a slash), the generated link - will be based off the application root path. If it is relative, the link - will be based off the `/chrome/` path. + If `filename` is a network-path reference (i.e. starts with a protocol + or `//`), the return value will not be modified. If `filename` is absolute + (i.e. starts with `/`), the generated link will be based off the + application root path. If it is relative, the link will be based off the + `/chrome/` path. """ scriptset = req.chrome.setdefault('scriptset', set()) if filename in scriptset: return False # Already added that script - if filename.startswith(('http://', 'https://')): - href = filename - elif filename.startswith('common/') and 'htdocs_location' in req.chrome: - href = Href(req.chrome['htdocs_location'])(filename[7:]) - else: - href = req.href - if not filename.startswith('/'): - href = href.chrome - href = href(filename) + href = _chrome_resource_path(req, filename) script = {'href': href, 'type': mimetype, 'charset': charset, 'prefix': Markup('<!--[if %s]>' % ie_if) if ie_if else None, 'suffix': Markup('<![endif]-->') if ie_if else None} @@ -190,7 +178,7 @@ def add_script_data(req, data={}, **kwar script_data.update(kwargs) def add_javascript(req, filename): - """:deprecated: use `add_script` instead.""" + """:deprecated: since 0.10, use `add_script` instead.""" add_script(req, filename, mimetype='text/javascript') def add_warning(req, msg, *args): @@ -286,6 +274,8 @@ def web_context(req, resource=None, id=F name) :return: a new rendering context :rtype: `RenderingContext` + + :since: version 1.0 """ if req: href = req.abs_href if absurls else req.href @@ -311,6 +301,24 @@ def auth_link(req, link): return link +def _chrome_resource_path(req, filename): + """Get the path for a chrome resource given its `filename`. + + If `filename` is a network-path reference (i.e. starts with a protocol + or `//`), the return value will not be modified. If `filename` is absolute + (i.e. starts with `/`), the generated link will be based off the + application root path. If it is relative, the link will be based off the + `/chrome/` path. + """ + if filename.startswith(('http://', 'https://', '//')): + return filename + elif filename.startswith('common/') and 'htdocs_location' in req.chrome: + return Href(req.chrome['htdocs_location'])(filename[7:]) + else: + href = req.href if filename.startswith('/') else req.href.chrome + return href(filename) + + def _save_messages(req, url, permanent): """Save warnings and notices in case of redirect, so that they can be displayed after the redirect.""" @@ -475,6 +483,10 @@ class Chrome(Component): """Make `<textarea>` fields resizable. Requires !JavaScript. (''since 0.12'')""") + wiki_toolbars = BoolOption('trac', 'wiki_toolbars', 'true', + """Add a simple toolbar on top of Wiki `<textarea>`s. + (''since 1.0.2'')""") + auto_preview_timeout = FloatOption('trac', 'auto_preview_timeout', 2.0, """Inactivity timeout in seconds after which the automatic wiki preview triggers an update. This option can contain floating-point values. The @@ -491,8 +503,8 @@ class Chrome(Component): templates = None - # default doctype for 'text/html' output - default_html_doctype = DocType.XHTML_STRICT + # DocType for 'text/html' output + html_doctype = DocType.XHTML_STRICT # A dictionary of default context data for templates _default_context_data = { @@ -505,6 +517,7 @@ class Chrome(Component): 'dgettext': translation.dgettext, 'dngettext': translation.dngettext, 'first_last': presentation.first_last, + 'find_element': html.find_element, 'get_reporter_id': get_reporter_id, 'gettext': translation.gettext, 'group': presentation.group, @@ -606,11 +619,13 @@ class Chrome(Component): for provider in self.template_providers: for dir in [os.path.normpath(dir[1]) for dir in provider.get_htdocs_dirs() or [] - if dir[0] == prefix]: + if dir[0] == prefix and dir[1]]: dirs.append(dir) path = os.path.normpath(os.path.join(dir, filename)) - assert os.path.commonprefix([dir, path]) == dir - if os.path.isfile(path): + if os.path.commonprefix([dir, path]) != dir: + raise TracError(_("Invalid chrome path %(path)s.", + path=filename)) + elif os.path.isfile(path): req.send_file(path, get_mimetype(path)) self.log.warning('File %s not found in any of %s', filename, dirs) @@ -679,7 +694,7 @@ class Chrome(Component): getattr(handler.__class__, 'jquery_noconflict', False): add_script(req, 'common/js/noconflict.js') add_script(req, 'common/js/babel.js') - if req.locale is not None: + if req.locale is not None and str(req.locale) != 'en_US': add_script(req, 'common/js/messages/%s.js' % req.locale) add_script(req, 'common/js/trac.js') add_script(req, 'common/js/search.js') @@ -864,7 +879,14 @@ class Chrome(Component): format = req.session.get('dateinfo', self.default_dateinfo_format) if format == 'absolute': - label = absolute + if dateonly: + label = absolute + elif req.lc_time == 'iso8601': + label = _("at %(iso8601)s", iso8601=absolute) + else: + label = _("on %(date)s at %(time)s", + date=user_time(req, format_date, date), + time=user_time(req, format_time, date)) title = _("%(relativetime)s ago", relativetime=relative) else: label = _("%(relativetime)s ago", relativetime=relative) \ @@ -952,9 +974,10 @@ class Chrome(Component): """Render the `filename` using the `data` for the context. The `content_type` argument is used to choose the kind of template - used (`NewTextTemplate` if `'text/plain'`, `MarkupTemplate` otherwise), - and tweak the rendering process. Doctype for `'text/html'` can be - specified by setting the default_html_doctype (default is XHTML Strict) + used (`NewTextTemplate` if `'text/plain'`, `MarkupTemplate` + otherwise), and tweak the rendering process. Doctype for `'text/html'` + can be specified by setting the `html_doctype` attribute (default + is `XHTML_STRICT`) When `fragment` is specified, the (filtered) Genshi stream is returned. @@ -994,8 +1017,9 @@ class Chrome(Component): stream.render('text', out=buffer, encoding='utf-8') return buffer.getvalue() - doctype = {'text/html': Chrome.default_html_doctype}.get(content_type) - if doctype: + doctype = None + if content_type == 'text/html': + doctype = self.html_doctype if req.form_token: stream |= self._add_form_token(req.form_token) if not int(req.session.get('accesskeys', 0)): @@ -1102,7 +1126,8 @@ class Chrome(Component): def add_wiki_toolbars(self, req): """Add wiki toolbars to `<textarea class="wikitext">` fields.""" - add_script(req, 'common/js/wikitoolbar.js') + if self.wiki_toolbars: + add_script(req, 'common/js/wikitoolbar.js') self.add_textarea_grips(req) def add_auto_preview(self, req): @@ -1129,8 +1154,10 @@ class Chrome(Component): 'first_week_day': get_first_week_day_jquery_ui(req), 'timepicker_separator': 'T' if is_iso8601 else ' ', 'show_timezone': is_iso8601, + # default timezone must be included 'timezone_list': get_timezone_list_jquery_ui() \ - if is_iso8601 else [], + if is_iso8601 \ + else [{'value': 'Z', 'label': '+00:00'}], 'timezone_iso8601': is_iso8601, }) add_script(req, 'common/js/jquery-ui-i18n.js') @@ -1170,4 +1197,3 @@ class Chrome(Component): def _stream_location(self, stream): for kind, data, pos in stream: return pos - Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/main.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/main.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/main.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/main.py Sat Nov 15 01:14:46 2014 @@ -28,7 +28,7 @@ from pprint import pformat, pprint import re import sys -from genshi.builder import Fragment, tag +from genshi.builder import tag from genshi.output import DocType from genshi.template import TemplateLoader @@ -41,14 +41,14 @@ from trac.loader import get_plugin_info, from trac.perm import PermissionCache, PermissionError from trac.resource import ResourceNotFound from trac.util import arity, get_frame_info, get_last_traceback, hex_entropy, \ - read_file, safe_repr, translation + read_file, safe_repr, translation, warn_setuptools_issue from trac.util.concurrency import threading from trac.util.datefmt import format_datetime, localtz, timezone, user_time from trac.util.text import exception_to_unicode, shorten_line, to_unicode from trac.util.translation import _, get_negotiated_locale, has_babel, \ safefmt, tag_ from trac.web.api import * -from trac.web.chrome import Chrome +from trac.web.chrome import Chrome, add_notice, add_warning from trac.web.href import Href from trac.web.session import Session @@ -132,11 +132,18 @@ class RequestDispatcher(Component): def authenticate(self, req): for authenticator in self.authenticators: - authname = authenticator.authenticate(req) + try: + authname = authenticator.authenticate(req) + except TracError, e: + self.log.error("Can't authenticate using %s: %s", + authenticator.__class__.__name__, + exception_to_unicode(e, traceback=True)) + add_warning(req, _("Authentication error. " + "Please contact your administrator.")) + break # don't fallback to other authenticators if authname: return authname - else: - return 'anonymous' + return 'anonymous' def dispatch(self, req): """Find a registered handler that matches the request and let @@ -175,8 +182,8 @@ class RequestDispatcher(Component): chosen_handler = self.default_handler # pre-process any incoming request, whether a handler # was found or not - chosen_handler = self._pre_process_request(req, - chosen_handler) + chosen_handler = \ + self._pre_process_request(req, chosen_handler) except TracError, e: raise HTTPInternalError(e) if not chosen_handler: @@ -249,7 +256,7 @@ class RequestDispatcher(Component): exception_to_unicode(e, traceback=True)) raise err[0], err[1], err[2] except PermissionError, e: - raise HTTPForbidden(to_unicode(e)) + raise HTTPForbidden(e) except ResourceNotFound, e: raise HTTPNotFound(e) except TracError, e: @@ -277,7 +284,8 @@ class RequestDispatcher(Component): default = self.env.config.get('trac', 'default_language', '') negotiated = get_negotiated_locale([preferred, default] + req.languages) - self.log.debug("Negotiated locale: %s -> %s", preferred, negotiated) + self.log.debug("Negotiated locale: %s -> %s", preferred, + negotiated) return negotiated def _get_lc_time(self, req): @@ -306,7 +314,7 @@ class RequestDispatcher(Component): If the the user does not have a `trac_form_token` cookie a new one is generated. """ - if req.incookie.has_key('trac_form_token'): + if 'trac_form_token' in req.incookie: return req.incookie['trac_form_token'].value else: req.outcookie['trac_form_token'] = hex_entropy(24) @@ -340,6 +348,8 @@ class RequestDispatcher(Component): f.post_process_request(req, *(None,)*extra_arg_count) return resp + +_warn_setuptools = False _slashes_re = re.compile(r'/+') @@ -350,6 +360,11 @@ def dispatch_request(environ, start_resp :param start_response: the WSGI callback for starting the response """ + global _warn_setuptools + if _warn_setuptools is False: + _warn_setuptools = True + warn_setuptools_issue(out=environ.get('wsgi.errors')) + # SCRIPT_URL is an Apache var containing the URL before URL rewriting # has been applied, so we can use it to reconstruct logical SCRIPT_NAME script_url = environ.get('SCRIPT_URL') @@ -467,7 +482,7 @@ def _dispatch_request(req, env, env_erro # fixup env.abs_href if `[trac] base_url` was not specified if env and not env.abs_href.base: - env._abs_href = req.abs_href + env.abs_href = req.abs_href try: if not env and env_error: @@ -475,12 +490,12 @@ def _dispatch_request(req, env, env_erro try: dispatcher = RequestDispatcher(env) dispatcher.dispatch(req) - except RequestDone: - pass - resp = req._response or [] + except RequestDone, req_done: + resp = req_done.iterable + resp = resp or req._response or [] except HTTPException, e: _send_user_error(req, env, e) - except Exception, e: + except Exception: send_internal_error(env, req, sys.exc_info()) return resp @@ -489,40 +504,19 @@ def _send_user_error(req, env, e): # See trac/web/api.py for the definition of HTTPException subclasses. if env: env.log.warn('[%s] %s' % (req.remote_addr, exception_to_unicode(e))) - try: - # We first try to get localized error messages here, but we - # should ignore secondary errors if the main error was also - # due to i18n issues - title = _('Error') - if e.reason: - if title.lower() in e.reason.lower(): - title = e.reason - else: - title = _('Error: %(message)s', message=e.reason) - except Exception: - title = 'Error' - # The message is based on the e.detail, which can be an Exception - # object, but not a TracError one: when creating HTTPException, - # a TracError.message is directly assigned to e.detail - if isinstance(e.detail, Exception): # not a TracError - message = exception_to_unicode(e.detail) - elif isinstance(e.detail, Fragment): # markup coming from a TracError - message = e.detail - else: - message = to_unicode(e.detail) - data = {'title': title, 'type': 'TracError', 'message': message, + data = {'title': e.title, 'type': 'TracError', 'message': e.message, 'frames': [], 'traceback': None} if e.code == 403 and req.authname == 'anonymous': # TRANSLATOR: ... not logged in, you may want to 'do so' now (link) do_so = tag.a(_("do so"), href=req.href.login()) - req.chrome['notices'].append( - tag_("You are currently not logged in. You may want to " - "%(do_so)s now.", do_so=do_so)) + add_notice(req, tag_("You are currently not logged in. You may want " + "to %(do_so)s now.", do_so=do_so)) try: req.send_error(sys.exc_info(), status=e.code, env=env, data=data) except RequestDone: pass + def send_internal_error(env, req, exc_info): if env: env.log.error("Internal Server Error: %s", @@ -539,6 +533,7 @@ def send_internal_error(env, req, exc_in pass tracker = default_tracker + tracker_args = {} if has_admin and not isinstance(exc_info[1], MemoryError): # Collect frame and plugin information frames = get_frame_info(exc_info[2]) @@ -558,13 +553,18 @@ def send_internal_error(env, req, exc_in tracker = info['trac'] elif info.get('home_page', '').startswith(th): tracker = th + plugin_name = info.get('home_page', '').rstrip('/') \ + .split('/')[-1] + tracker_args = {'component': plugin_name} def get_description(_): if env and has_admin: sys_info = "".join("|| '''`%s`''' || `%s` ||\n" % (k, v.replace('\n', '` [[br]] `')) for k, v in env.get_systeminfo()) - sys_info += "|| '''`jQuery`''' || `#JQUERY#` ||\n" + sys_info += "|| '''`jQuery`''' || `#JQUERY#` ||\n" \ + "|| '''`jQuery UI`''' || `#JQUERYUI#` ||\n" \ + "|| '''`jQuery Timepicker`''' || `#JQUERYTP#` ||\n" enabled_plugins = "".join("|| '''`%s`''' || `%s` ||\n" % (p['name'], p['version'] or _('N/A')) for p in plugins) @@ -608,9 +608,10 @@ User agent: `#USER_AGENT#` 'traceback': traceback, 'frames': frames, 'shorten_line': shorten_line, 'repr': safe_repr, 'plugins': plugins, 'faulty_plugins': faulty_plugins, - 'tracker': tracker, + 'tracker': tracker, 'tracker_args': tracker_args, 'description': description, 'description_en': description_en} + Chrome(env).add_jquery_ui(req) try: req.send_error(exc_info, status=500, env=env, data=data) except RequestDone: @@ -702,7 +703,7 @@ def get_environments(environ, warn=False paths = [path[:-1] for path in paths if path[-1] == '/' and not any(fnmatch.fnmatch(path[:-1], pattern) for pattern in ignore_patterns)] - env_paths.extend(os.path.join(env_parent_dir, project) \ + env_paths.extend(os.path.join(env_parent_dir, project) for project in paths) envs = {} for env_path in env_paths: Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/session.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/session.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/session.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/session.py Sat Nov 15 01:14:46 2014 @@ -23,19 +23,20 @@ from __future__ import with_statement import sys import time -from trac.admin.api import console_date_format +from trac.admin.api import console_date_format, get_console_locale from trac.core import TracError, Component, implements from trac.util import hex_entropy from trac.util.text import print_table from trac.util.translation import _ -from trac.util.datefmt import format_date, parse_date, to_datetime, \ - to_timestamp +from trac.util.datefmt import get_datetime_format_hint, format_date, \ + parse_date, to_datetime, to_timestamp from trac.admin.api import IAdminCommandProvider, AdminCommandError UPDATE_INTERVAL = 3600 * 24 # Update session last_visit time stamp after 1 day PURGE_AGE = 3600 * 24 * 90 # Purge session after 90 days idle COOKIE_KEY = 'trac_session' + # Note: as we often manipulate both the `session` and the # `session_attribute` tables, there's a possibility of table # deadlocks (#9705). We try to prevent them to happen by always @@ -198,14 +199,14 @@ class Session(DetachedSession): super(Session, self).__init__(env, None) self.req = req if req.authname == 'anonymous': - if not req.incookie.has_key(COOKIE_KEY): + if COOKIE_KEY not in req.incookie: self.sid = hex_entropy(24) self.bake_cookie() else: sid = req.incookie[COOKIE_KEY].value self.get_session(sid) else: - if req.incookie.has_key(COOKIE_KEY): + if COOKIE_KEY in req.incookie: sid = req.incookie[COOKIE_KEY].value self.promote_session(sid) self.get_session(req.authname, authenticated=True) @@ -313,6 +314,10 @@ class SessionAdmin(Component): implements(IAdminCommandProvider) def get_admin_commands(self): + hints = { + 'datetime': get_datetime_format_hint(get_console_locale(self.env)), + 'iso8601': get_datetime_format_hint('iso8601'), + } yield ('session list', '[sid[:0|1]] [...]', """List the name and email for the given sids @@ -353,10 +358,11 @@ class SessionAdmin(Component): self._complete_delete, self._do_delete) yield ('session purge', '<age>', - """Purge all anonymous sessions older than the given age + """Purge anonymous sessions older than the given age or date Age may be specified as a relative time like "90 days ago", or - in YYYYMMDD format.""", + as a date in the "%(datetime)s" or "%(iso8601)s" (ISO 8601) + format.""" % hints, None, self._do_purge) def _split_sid(self, sid): @@ -469,7 +475,8 @@ class SessionAdmin(Component): """, (sid, authenticated)) def _do_purge(self, age): - when = parse_date(age) + when = parse_date(age, hint='datetime', + locale=get_console_locale(self.env)) with self.env.db_transaction as db: ts = to_timestamp(when) db(""" Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/__init__.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/__init__.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/__init__.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/__init__.py Sat Nov 15 01:14:46 2014 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2005-2009 Edgewall Software +# Copyright (C) 2005-2013 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/api.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/api.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/api.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/api.py Sat Nov 15 01:14:46 2014 @@ -1,22 +1,110 @@ # -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. + +import os.path +import shutil +import sys +import tempfile +import unittest +from StringIO import StringIO -from trac.test import Mock +import trac.tests.compat +from trac import perm +from trac.core import TracError +from trac.test import EnvironmentStub, Mock, MockPerm, locale_en +from trac.util import create_file +from trac.util.datefmt import utc +from trac.util.text import shorten_line from trac.web.api import Request, RequestDone, parse_arg_list +from tracopt.perm.authz_policy import AuthzPolicy -from StringIO import StringIO -import unittest + +class RequestHandlerPermissionsTestCaseBase(unittest.TestCase): + + authz_policy = None + + def setUp(self, module_class): + self.path = tempfile.mkdtemp(prefix='trac-') + if self.authz_policy is not None: + self.authz_file = os.path.join(self.path, 'authz_policy.conf') + create_file(self.authz_file, self.authz_policy) + self.env = EnvironmentStub(enable=['trac.*', AuthzPolicy], + path=self.path) + self.env.config.set('authz_policy', 'authz_file', self.authz_file) + self.env.config.set('trac', 'permission_policies', + 'AuthzPolicy, DefaultPermissionPolicy') + else: + self.env = EnvironmentStub(path=self.path) + self.req_handler = module_class(self.env) + + def tearDown(self): + self.env.reset_db() + shutil.rmtree(self.path) + + def create_request(self, authname='anonymous', **kwargs): + kw = {'perm': perm.PermissionCache(self.env, authname), 'args': {}, + 'href': self.env.href, 'abs_href': self.env.abs_href, + 'tz': utc, 'locale': None, 'lc_time': locale_en, + 'chrome': {'notices': [], 'warnings': []}, + 'method': None, 'get_header': lambda v: None} + kw.update(kwargs) + return Mock(**kw) + + def get_navigation_items(self, req): + return self.req_handler.get_navigation_items(req) + + def grant_perm(self, username, *actions): + permsys = perm.PermissionSystem(self.env) + for action in actions: + permsys.grant_permission(username, action) + + def process_request(self, req): + self.assertTrue(self.req_handler.match_request(req)) + return self.req_handler.process_request(req) + + +def _make_environ(scheme='http', server_name='example.org', + server_port=80, method='GET', script_name='/trac', + **kwargs): + environ = {'wsgi.url_scheme': scheme, 'wsgi.input': StringIO(''), + 'REQUEST_METHOD': method, 'SERVER_NAME': server_name, + 'SERVER_PORT': server_port, 'SCRIPT_NAME': script_name} + environ.update(kwargs) + return environ + + +def _make_req(environ, start_response, args={}, arg_list=(), authname='admin', + form_token='A' * 40, chrome={'links': {}, 'scripts': []}, + perm=MockPerm(), session={}, tz=utc, locale=None, **kwargs): + req = Request(environ, start_response) + req.args = args + req.arg_list = arg_list + req.authname = authname + req.form_token = form_token + req.chrome = chrome + req.perm = perm + req.session = session + req.tz = tz + req.locale = locale + for name, value in kwargs.iteritems(): + setattr(req, name, value) + return req class RequestTestCase(unittest.TestCase): - def _make_environ(self, scheme='http', server_name='example.org', - server_port=80, method='GET', script_name='/trac', - **kwargs): - environ = {'wsgi.url_scheme': scheme, 'wsgi.input': StringIO(''), - 'REQUEST_METHOD': method, 'SERVER_NAME': server_name, - 'SERVER_PORT': server_port, 'SCRIPT_NAME': script_name} - environ.update(kwargs) - return environ + def _make_environ(self, *args, **kwargs): + return _make_environ(*args, **kwargs) def test_base_url(self): environ = self._make_environ() @@ -97,10 +185,6 @@ class RequestTestCase(unittest.TestCase) def start_response(status, headers): return write environ = self._make_environ(method='HEAD') - req = Request(environ, start_response) - req.send_header('Content-Type', 'text/plain;charset=utf-8') - # we didn't set Content-Length, so we get a RuntimeError for that - self.assertRaises(RuntimeError, req.write, u'Föö') req = Request(environ, start_response) req.send_header('Content-Type', 'text/plain;charset=utf-8') @@ -146,6 +230,118 @@ class RequestTestCase(unittest.TestCase) self.assertEqual('bar', req.args['action']) +class SendErrorTestCase(unittest.TestCase): + + def setUp(self): + self.env = EnvironmentStub() + + def tearDown(self): + self.env.reset_db() + + def test_trac_error(self): + content = self._send_error(error_klass=TracError) + self.assertIn('<p class="message">Oops!</p>', content) + self.assertNotIn('<strong>Trac detected an internal error:</strong>', + content) + self.assertNotIn('There was an internal error in Trac.', content) + + def test_internal_error_for_non_admin(self): + content = self._send_error(perm={}) + self.assertIn('There was an internal error in Trac.', content) + self.assertIn('<p>To that end, you could', content) + self.assertNotIn('This is probably a local installation issue.', + content) + self.assertNotIn('<h2>Found a bug in Trac?</h2>', content) + + def test_internal_error_with_admin_trac_for_non_admin(self): + content = self._send_error(perm={}, + admin_trac_url='http://example.org/admin') + self.assertIn('There was an internal error in Trac.', content) + self.assertIn('<p>To that end, you could', content) + self.assertIn(' action="http://example.org/admin/newticket#"', content) + self.assertNotIn('This is probably a local installation issue.', + content) + self.assertNotIn('<h2>Found a bug in Trac?</h2>', content) + + def test_internal_error_without_admin_trac_for_non_admin(self): + content = self._send_error(perm={}, admin_trac_url='') + self.assertIn('There was an internal error in Trac.', content) + self.assertNotIn('<p>To that end, you could', content) + self.assertNotIn('This is probably a local installation issue.', + content) + self.assertNotIn('<h2>Found a bug in Trac?</h2>', content) + + def test_internal_error_for_admin(self): + content = self._send_error() + self.assertNotIn('There was an internal error in Trac.', content) + self.assertIn('This is probably a local installation issue.', content) + self.assertNotIn('a ticket at the admin Trac to report', content) + self.assertIn('<h2>Found a bug in Trac?</h2>', content) + self.assertIn('<p>Otherwise, please', content) + self.assertIn(' action="http://example.org/tracker/newticket"', + content) + + def test_internal_error_with_admin_trac_for_admin(self): + content = self._send_error(admin_trac_url='http://example.org/admin') + self.assertNotIn('There was an internal error in Trac.', content) + self.assertIn('This is probably a local installation issue.', content) + self.assertIn('a ticket at the admin Trac to report', content) + self.assertIn(' action="http://example.org/admin/newticket#"', content) + self.assertIn('<h2>Found a bug in Trac?</h2>', content) + self.assertIn('<p>Otherwise, please', content) + self.assertIn(' action="http://example.org/tracker/newticket"', + content) + + def test_internal_error_without_admin_trac_for_admin(self): + content = self._send_error(admin_trac_url='') + self.assertNotIn('There was an internal error in Trac.', content) + self.assertIn('This is probably a local installation issue.', content) + self.assertNotIn('a ticket at the admin Trac to report', content) + self.assertIn('<h2>Found a bug in Trac?</h2>', content) + self.assertIn('<p>Otherwise, please', content) + self.assertIn(' action="http://example.org/tracker/newticket"', + content) + + def _send_error(self, admin_trac_url='.', perm=None, + error_klass=ValueError): + self.env.config.set('project', 'admin_trac_url', admin_trac_url) + self.assertEquals(admin_trac_url, self.env.project_admin_trac_url) + + content = StringIO() + result = {'status': None, 'headers': []} + def write(data): + content.write(data) + def start_response(status, headers, exc_info=None): + result['status'] = status + result['headers'].extend(headers) + return write + environ = _make_environ() + req = _make_req(environ, start_response) + try: + raise error_klass('Oops!') + except: + exc_info = sys.exc_info() + data = {'title': 'Internal Error', + 'type': ('internal', 'TracError')[error_klass is TracError], + 'message': 'Oops!', 'traceback': None, 'frames': [], + 'shorten_line': shorten_line, + 'plugins': [], 'faulty_plugins': [], + 'tracker': 'http://example.org/tracker', 'tracker_args': {}, + 'description': '', 'description_en': '', + 'get_systeminfo': lambda: ()} + if perm is not None: + data['perm'] = perm + + self.assertRaises(RequestDone, req.send_error, exc_info, env=self.env, + data=data) + content = content.getvalue().decode('utf-8') + self.assertIn('<!DOCTYPE ', content) + self.assertEquals('500', result['status'].split()[0]) + self.assertIn(('Content-Type', 'text/html;charset=utf-8'), + result['headers']) + return content + + class ParseArgListTestCase(unittest.TestCase): def test_qs_str(self): @@ -169,9 +365,11 @@ class ParseArgListTestCase(unittest.Test def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(RequestTestCase, 'test')) - suite.addTest(unittest.makeSuite(ParseArgListTestCase, 'test')) + suite.addTest(unittest.makeSuite(RequestTestCase)) + suite.addTest(unittest.makeSuite(SendErrorTestCase)) + suite.addTest(unittest.makeSuite(ParseArgListTestCase)) return suite + if __name__ == '__main__': - unittest.main() + unittest.main(defaultTest='suite') Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/auth.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/auth.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/auth.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/auth.py Sat Nov 15 01:14:46 2014 @@ -1,7 +1,19 @@ # -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. import os +import trac.tests.compat from trac.core import TracError from trac.test import EnvironmentStub, Mock from trac.web.auth import BasicAuthentication, LoginModule @@ -24,7 +36,7 @@ class LoginModuleTestCase(unittest.TestC req = Mock(incookie=Cookie(), href=Href('/trac.cgi'), remote_addr='127.0.0.1', remote_user=None, base_path='/trac.cgi') - self.assertEqual(None, self.module.authenticate(req)) + self.assertIsNone(self.module.authenticate(req)) def test_unknown_cookie_access(self): incookie = Cookie() @@ -33,7 +45,7 @@ class LoginModuleTestCase(unittest.TestC incookie=incookie, outcookie=Cookie(), remote_addr='127.0.0.1', remote_user=None, base_path='/trac.cgi') - self.assertEqual(None, self.module.authenticate(req)) + self.assertIsNone(self.module.authenticate(req)) def test_known_cookie_access(self): self.env.db_transaction(""" @@ -46,7 +58,7 @@ class LoginModuleTestCase(unittest.TestC href=Href('/trac.cgi'), base_path='/trac.cgi', remote_addr='127.0.0.1', remote_user=None) self.assertEqual('john', self.module.authenticate(req)) - self.failIf('auth_cookie' in req.outcookie) + self.assertNotIn('auth_cookie', req.outcookie) def test_known_cookie_ip_check_enabled(self): self.env.config.set('trac', 'check_auth_ip', 'yes') @@ -60,8 +72,8 @@ class LoginModuleTestCase(unittest.TestC incookie=incookie, outcookie=outcookie, remote_addr='192.168.0.100', remote_user=None, base_path='/trac.cgi') - self.assertEqual(None, self.module.authenticate(req)) - self.failIf('trac_auth' not in req.outcookie) + self.assertIsNone(self.module.authenticate(req)) + self.assertIn('trac_auth', req.outcookie) def test_known_cookie_ip_check_disabled(self): self.env.config.set('trac', 'check_auth_ip', 'no') @@ -75,7 +87,7 @@ class LoginModuleTestCase(unittest.TestC href=Href('/trac.cgi'), base_path='/trac.cgi', remote_addr='192.168.0.100', remote_user=None) self.assertEqual('john', self.module.authenticate(req)) - self.failIf('auth_cookie' in req.outcookie) + self.assertNotIn('auth_cookie', req.outcookie) def test_login(self): outcookie = Cookie() @@ -87,10 +99,10 @@ class LoginModuleTestCase(unittest.TestC authname='john', base_path='/trac.cgi') self.module._do_login(req) - assert outcookie.has_key('trac_auth'), '"trac_auth" Cookie not set' + self.assertIn('trac_auth', outcookie, '"trac_auth" Cookie not set') auth_cookie = outcookie['trac_auth'].value - self.assertEquals([('john', '127.0.0.1')], self.env.db_query( + self.assertEqual([('john', '127.0.0.1')], self.env.db_query( "SELECT name, ipnr FROM auth_cookie WHERE cookie=%s", (auth_cookie,))) @@ -108,9 +120,9 @@ class LoginModuleTestCase(unittest.TestC authname='anonymous', base_path='/trac.cgi') self.module._do_login(req) - assert outcookie.has_key('trac_auth'), '"trac_auth" Cookie not set' + self.assertIn('trac_auth', outcookie, '"trac_auth" Cookie not set') auth_cookie = outcookie['trac_auth'].value - self.assertEquals([('john', '127.0.0.1')], self.env.db_query( + self.assertEqual([('john', '127.0.0.1')], self.env.db_query( "SELECT name, ipnr FROM auth_cookie WHERE cookie=%s", (auth_cookie,))) @@ -140,7 +152,7 @@ class LoginModuleTestCase(unittest.TestC req = Mock(incookie=incookie, authname='john', href=Href('/trac.cgi'), base_path='/trac.cgi', remote_addr='127.0.0.1', remote_user='tom') - self.assertRaises(AssertionError, self.module._do_login, req) + self.assertRaises(TracError, self.module._do_login, req) def test_logout(self): self.env.db_transaction(""" @@ -151,20 +163,38 @@ class LoginModuleTestCase(unittest.TestC outcookie = Cookie() req = Mock(cgi_location='/trac', href=Href('/trac.cgi'), incookie=incookie, outcookie=outcookie, - remote_addr='127.0.0.1', remote_user=None, authname='john', - base_path='/trac.cgi') + remote_addr='127.0.0.1', remote_user=None, + authname='john', method='POST', base_path='/trac.cgi') self.module._do_logout(req) - self.failIf('trac_auth' not in outcookie) - self.failIf(self.env.db_query( + self.assertIn('trac_auth', outcookie) + self.assertFalse(self.env.db_query( "SELECT name, ipnr FROM auth_cookie WHERE name='john'")) def test_logout_not_logged_in(self): req = Mock(cgi_location='/trac', href=Href('/trac.cgi'), incookie=Cookie(), outcookie=Cookie(), remote_addr='127.0.0.1', remote_user=None, - authname='anonymous', base_path='/trac.cgi') + authname='anonymous', method='POST', base_path='/trac.cgi') self.module._do_logout(req) # this shouldn't raise an error + def test_logout_protect(self): + self.env.db_transaction(""" + INSERT INTO auth_cookie (cookie, name, ipnr) + VALUES ('123', 'john', '127.0.0.1')""") + incookie = Cookie() + incookie['trac_auth'] = '123' + outcookie = Cookie() + req = Mock(cgi_location='/trac', href=Href('/trac.cgi'), + incookie=incookie, outcookie=outcookie, + remote_addr='127.0.0.1', remote_user=None, + authname='john', method='GET', base_path='/trac.cgi') + self.module._do_logout(req) + self.assertNotIn('trac_auth', outcookie) + self.assertEqual( + [('john', '127.0.0.1')], + self.env.db_query("SELECT name, ipnr FROM auth_cookie " + "WHERE cookie='123'")) + class BasicAuthenticationTestCase(unittest.TestCase): def setUp(self): @@ -175,23 +205,24 @@ class BasicAuthenticationTestCase(unitte self.auth = None def test_crypt(self): - self.assert_(self.auth.test('crypt', 'crypt')) - self.assert_(not self.auth.test('crypt', 'other')) + self.assertTrue(self.auth.test('crypt', 'crypt')) + self.assertFalse(self.auth.test('crypt', 'other')) def test_md5(self): - self.assert_(self.auth.test('md5', 'md5')) - self.assert_(not self.auth.test('md5', 'other')) + self.assertTrue(self.auth.test('md5', 'md5')) + self.assertFalse(self.auth.test('md5', 'other')) def test_sha(self): - self.assert_(self.auth.test('sha', 'sha')) - self.assert_(not self.auth.test('sha', 'other')) + self.assertTrue(self.auth.test('sha', 'sha')) + self.assertFalse(self.auth.test('sha', 'other')) def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(LoginModuleTestCase, 'test')) - suite.addTest(unittest.makeSuite(BasicAuthenticationTestCase, 'test')) + suite.addTest(unittest.makeSuite(LoginModuleTestCase)) + suite.addTest(unittest.makeSuite(BasicAuthenticationTestCase)) return suite + if __name__ == '__main__': - unittest.main() + unittest.main(defaultTest='suite') Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/cgi_frontend.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/cgi_frontend.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/cgi_frontend.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/web/tests/cgi_frontend.py Sat Nov 15 01:14:46 2014 @@ -1,3 +1,16 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. + #from trac.web.cgi_frontend import CGIRequest import unittest @@ -8,7 +21,8 @@ class CGIRequestTestCase(unittest.TestCa def suite(): - return unittest.makeSuite(CGIRequestTestCase, 'test') + return unittest.makeSuite(CGIRequestTestCase) + if __name__ == '__main__': - unittest.main() + unittest.main(defaultTest='suite')
