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&amp;verbose=yes&amp;format=changelog">[10:20/trunk?verbose=yes&amp;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')


Reply via email to