Index: trac/attachment.py
===================================================================
--- trac/attachment.py	(revision 3288)
+++ trac/attachment.py	(working copy)
@@ -79,8 +79,16 @@
     path = property(_get_path)
 
     def href(self, req, *args, **dict):
-        return req.href.attachment(self.parent_type, self.parent_id,
-                                   self.filename, *args, **dict)
+        base, filename = 'attachment', self.filename
+        if 'format' in dict:
+            format = dict.pop('format')
+            base += ';format=' + format # format passed in a segment parameter
+        else: # HTML preview
+            format = 'html'
+        if format != 'raw':
+            filename += '.' + format
+        return req.href(base, self.parent_type, self.parent_id, filename,
+                        *args, **dict)
 
     def parent_href(self, req):
         return req.href(self.parent_type, self.parent_id)
@@ -250,16 +258,21 @@
     # IRequestHandler methods
 
     def match_request(self, req):
-        match = re.match(r'^/attachment/(ticket|wiki)(?:[/:](.*))?$',
+        match = re.match(r'^/attachment(;format=[^/]+)?/'
+                         '(ticket|wiki)(?:[/:](.*))?$',
                          req.path_info)
         if match:
-            req.args['type'] = match.group(1)
-            req.args['path'] = match.group(2).replace(':', '/')
+            req.args['type'] = match.group(2)
+            req.args['path'] = match.group(3).replace(':', '/')
+            format = match.group(1)
+            if format:
+                req.args['format'] = format[8:]
             return True
 
     def process_request(self, req):
         parent_type = req.args.get('type')
         path = req.args.get('path')
+        format = req.args.get('format')
         if not parent_type or not path:
             raise HTTPBadRequest('Bad request')
         if not parent_type in ['ticket', 'wiki']:
@@ -272,6 +285,8 @@
             segments = path.split('/')
             parent_id = '/'.join(segments[:-1])
             filename = segments[-1]
+            if not format and filename.endswith('.html'):
+                filename = filename[:-5]
             if len(segments) == 1 or not filename:
                 raise HTTPBadRequest('Bad request')
             attachment = Attachment(self.env, parent_type, parent_id, filename)
@@ -299,7 +314,7 @@
             self._render_form(req, attachment)
         else:
             add_link(req, 'up', parent_link, parent_text)
-            self._render_view(req, attachment)
+            self._render_view(req, attachment, format)
 
         add_stylesheet(req, 'common/css/code.css')
         return 'attachment.cs', None
@@ -393,7 +408,7 @@
         req.hdf['attachment'] = {'mode': 'new',
                                  'author': util.get_reporter_id(req)}
 
-    def _render_view(self, req, attachment):
+    def _render_view(self, req, attachment, format):
         perm_map = {'ticket': 'TICKET_VIEW', 'wiki': 'WIKI_VIEW'}
         req.perm.assert_permission(perm_map[attachment.parent_type])
 
@@ -420,7 +435,6 @@
             mime_type = mimeview.get_mimetype(attachment.filename, str_data)
 
             # Eventually send the file directly
-            format = req.args.get('format')
             if format in ('raw', 'txt'):
                 if not self.render_unsafe_content and not binary:
                     # Force browser to download HTML/SVG/etc pages that may
Index: trac/mimeview/rst.py
===================================================================
--- trac/mimeview/rst.py	(revision 3285)
+++ trac/mimeview/rst.py	(working copy)
@@ -54,7 +54,8 @@
 def _browser(href, args):
     path = args[0]
     rev = len(args) == 2 and args[1] or ''
-    return href.browser(path, rev=rev)
+    from trac.versioncontrol.web_ui.browser import BrowserModule
+    return BrowserModule(self.env).href(req, path, rev=rev)
 
 # TracLink REs and callback functions
 LINKS = [(TICKET_LINK, _ticket),
Index: trac/versioncontrol/web_ui/util.py
===================================================================
--- trac/versioncontrol/web_ui/util.py	(revision 3285)
+++ trac/versioncontrol/web_ui/util.py	(working copy)
@@ -69,7 +69,7 @@
         }
     return changes
 
-def get_path_links(href, path, rev):
+def get_path_links(browser, req, path, rev):
     links = []
     parts = path.split('/')
     if not parts[-1]:
@@ -79,7 +79,7 @@
         path = path + part + '/'
         links.append({
             'name': part or 'root',
-            'href': href.browser(path, rev=rev)
+            'href': browser.href(req, path, rev=rev)
         })
     return links
 
Index: trac/versioncontrol/web_ui/changeset.py
===================================================================
--- trac/versioncontrol/web_ui/changeset.py	(revision 3285)
+++ trac/versioncontrol/web_ui/changeset.py	(working copy)
@@ -34,6 +34,7 @@
 from trac.versioncontrol import Changeset, Node
 from trac.versioncontrol.diff import get_diff_options, hdf_diff, unified_diff
 from trac.versioncontrol.svn_authz import SubversionAuthorizer
+from trac.versioncontrol.web_ui.browser import BrowserModule
 from trac.versioncontrol.web_ui.util import render_node_property
 from trac.web import IRequestHandler
 from trac.web.chrome import INavigationContributor, add_link, add_stylesheet
@@ -264,14 +265,15 @@
 
     def _render_html(self, req, repos, chgset, restricted, diff, diff_options):
         """HTML version"""
+        browser = BrowserModule(self.env)
         req.hdf['changeset'] = {
             'chgset': chgset and True,
             'restricted': restricted,
             'href': {
                 'new_rev': req.href.changeset(diff.new_rev),
                 'old_rev': req.href.changeset(diff.old_rev),
-                'new_path': req.href.browser(diff.new_path, rev=diff.new_rev),
-                'old_path': req.href.browser(diff.old_path, rev=diff.old_rev)
+                'new_path': browser.href(req, diff.new_path, rev=diff.new_rev),
+                'old_path': browser.href(req, diff.old_path, rev=diff.old_rev)
             }
         }
 
@@ -375,8 +377,8 @@
                 info['path.old'] = old_node.path
                 info['rev.old'] = old_node.rev
                 info['shortrev.old'] = repos.short_rev(old_node.rev)
-                old_href = req.href.browser(old_node.created_path,
-                                            rev=old_node.created_rev)
+                old_href = browser.href(req, old_node.created_path,
+                                        rev=old_node.created_rev)
                 # Reminder: old_node.path may not exist at old_node.rev
                 #           as long as old_node.rev==old_node.created_rev
                 #           ... and diff.old_rev may have nothing to do
@@ -386,8 +388,8 @@
                 info['path.new'] = new_node.path
                 info['rev.new'] = new_node.rev # created rev.
                 info['shortrev.new'] = repos.short_rev(new_node.rev)
-                new_href = req.href.browser(new_node.created_path,
-                                            rev=new_node.created_rev)
+                new_href = browser.href(req, new_node.created_path,
+                                        rev=new_node.created_rev)
                 # (same remark as above)
                 info['browser_href.new'] = new_href
             return info
Index: trac/versioncontrol/web_ui/log.py
===================================================================
--- trac/versioncontrol/web_ui/log.py	(revision 3285)
+++ trac/versioncontrol/web_ui/log.py	(working copy)
@@ -24,6 +24,7 @@
 from trac.util import http_date
 from trac.util.markup import html
 from trac.versioncontrol import Changeset
+from trac.versioncontrol.web_ui.browser import BrowserModule
 from trac.versioncontrol.web_ui.changeset import ChangesetModule
 from trac.versioncontrol.web_ui.util import *
 from trac.web import IRequestHandler
@@ -78,6 +79,8 @@
             if repos.rev_older_than(rev, stop_rev):
                 rev, stop_rev = stop_rev, rev
             
+        browser = BrowserModule(self.env)
+        
         req.hdf['title'] = path + ' (log)'
         req.hdf['log'] = {
             'mode': mode,
@@ -85,12 +88,12 @@
             'rev': rev,
             'verbose': verbose,
             'stop_rev': stop_rev,
-            'browser_href': req.href.browser(path),
+            'browser_href': browser.href(req, path),
             'changeset_href': req.href.changeset(),
             'log_href': req.href.log(path, rev=rev)
         }
 
-        path_links = get_path_links(req.href, path, rev)
+        path_links = get_path_links(browser, req, path, rev)
         req.hdf['log.path'] = path_links
         if path_links:
             add_link(req, 'up', path_links[-1]['href'], 'Parent directory')
@@ -116,7 +119,7 @@
                 'rev': str(old_rev),
                 'path': old_path,
                 'log_href': req.href.log(old_path, rev=old_rev),
-                'browser_href': req.href.browser(old_path, rev=old_rev),
+                'browser_href': browser.href(req, old_path, rev=old_rev),
                 'changeset_href': req.href.changeset(old_rev),
                 'restricted_href': req.href.changeset(old_rev, new_path=old_path),
                 'change': old_chg
Index: trac/versioncontrol/web_ui/browser.py
===================================================================
--- trac/versioncontrol/web_ui/browser.py	(revision 3285)
+++ trac/versioncontrol/web_ui/browser.py	(working copy)
@@ -57,6 +57,18 @@
         glob patterns, i.e. "*" can be used as a wild card)
         (''since 0.10'')""")
 
+    def href(self, req, path, *args, **dict):
+        base = 'browser'
+        if 'format' in dict:
+            format = dict.pop('format')
+            base += ';format=' + format # format passed in a segment parameter
+        else: # HTML preview
+            format = 'html'
+        if format != 'raw':
+            path += '.' + format
+        return req.href(base, path, *args, **dict)
+        
+
     # INavigationContributor methods
 
     def get_active_navigation_item(self, req):
@@ -77,20 +89,30 @@
 
     def match_request(self, req):
         import re
-        match = re.match(r'/(browser|file)(?:(/.*))?', req.path_info)
+        match = re.match(r'/(raw|txt)?(browser|file)(?:(/.*))?', req.path_info)
         if match:
-            req.args['path'] = match.group(2) or '/'
-            if match.group(1) == 'file':
-                req.redirect(req.href.browser(req.args.get('path'),
-                                              rev=req.args.get('rev'),
-                                              format=req.args.get('format')),
+            req.args['path'] = match.group(3) or '/'
+            if match.group(2) == 'file':
+                req.redirect(self.href(req, req.args.get('path'),
+                                       rev=req.args.get('rev'),
+                                       format=req.args.get('format')),
                              permanent=True)
+            format = match.group(1)
+            if format:
+                req.args['format'] = format
             return True
 
     def process_request(self, req):
         path = req.args.get('path', '/')
         rev = req.args.get('rev') or None
+        format = req.args.get('format')
 
+        if format:
+            if path.endswith('.txt'):
+                path = path[:-4]
+        elif path.endswith('.html'):
+                path = path[:-5]
+
         # Find node for the requested path/rev
         repos = self.env.get_repository(req.authname)
         if rev:
@@ -114,14 +136,14 @@
             'path': path,
             'revision': rev,
             'props': properties,
-            'href': req.href.browser(path, rev=rev),
+            'href': self.href(req, path, rev=rev),
             'log_href': req.href.log(path, rev=rev),
             'restr_changeset_href': req.href.changeset(node.rev,
                                                        node.created_path),
             'anydiff_href': req.href.anydiff(),
         }
 
-        path_links = get_path_links(req.href, path, rev)
+        path_links = get_path_links(self, req, path, rev)
         if len(path_links) > 1:
             add_link(req, 'up', path_links[-2]['href'], 'Parent directory')
         req.hdf['browser.path'] = path_links
@@ -130,7 +152,7 @@
             req.hdf['browser.is_dir'] = True
             self._render_directory(req, repos, node, rev)
         else:
-            self._render_file(req, repos, node, rev)
+            self._render_file(req, repos, node, rev, format)
 
         add_stylesheet(req, 'common/css/browser.css')
         return 'browser.cs', None
@@ -152,7 +174,7 @@
                 'rev': entry.rev,
                 'permission': 1, # FIXME
                 'log_href': req.href.log(entry.path, rev=rev),
-                'browser_href': req.href.browser(entry.path, rev=rev)
+                'browser_href': self.href(req, entry.path, rev=rev)
             })
         changes = get_changes(self.env, repos, [i['rev'] for i in info])
 
@@ -179,8 +201,8 @@
 
         switch_ordering_hrefs = {}
         for col in ('name', 'size', 'date'):
-            switch_ordering_hrefs[col] = req.href.browser(
-                node.path, rev=rev, order=col,
+            switch_ordering_hrefs[col] = self.href(
+                req, node.path, rev=rev, order=col,
                 desc=(col == order and not desc and 1 or None))
 
         # ''Zip Archive'' alternate link
@@ -196,7 +218,7 @@
                               'items': info, 'changes': changes,
                               'order_href': switch_ordering_hrefs}
 
-    def _render_file(self, req, repos, node, rev=None):
+    def _render_file(self, req, repos, node, rev, format):
         req.perm.assert_permission('FILE_VIEW')
 
         mimeview = Mimeview(self.env)
@@ -210,7 +232,6 @@
                         mime_type or 'text/plain'
 
         # Eventually send the file directly
-        format = req.args.get('format')
         if format in ['raw', 'txt']:
             req.send_response(200)
             req.send_header('Content-Type',
@@ -247,12 +268,12 @@
 
             # add ''Plain Text'' alternate link if needed
             if not is_binary(chunk) and mime_type != 'text/plain':
-                plain_href = req.href.browser(node.path, rev=rev, format='txt')
+                plain_href = self.href(req, node.path, rev=rev, format='txt')
                 add_link(req, 'alternate', plain_href, 'Plain Text',
                          'text/plain')
 
             # add ''Original Format'' alternate link (always)
-            raw_href = req.href.browser(node.path, rev=rev, format='raw')
+            raw_href = self.href(req, node.path, rev=rev, format='raw')
             add_link(req, 'alternate', raw_href, 'Original Format', mime_type)
 
             self.log.debug("Rendering preview of node %s@%s with mime-type %s"
@@ -287,5 +308,7 @@
         else:
             anchor = ''
         label = urllib.unquote(label)
-        return html.A(href=formatter.href.browser(path, rev=rev) + anchor,
-                      class_='source')[label]
+        href = formatter.href.browser(path, rev=rev)
+        if formatter.req:
+            href = self.href(formatter.req, path, rev=rev)
+        return html.A(label, href=href+anchor, class_='source')
Index: trac/Search.py
===================================================================
--- trac/Search.py	(revision 3285)
+++ trac/Search.py	(working copy)
@@ -237,9 +237,11 @@
             return req.href.milestone(kwd[len('milestone:'):])
         # Source quickjump
         elif kwd[0] == '/':
-            return req.href.browser(kwd)
+            from trac.versioncontrol.web_ui.browser import BrowserModule
+            return BrowserModule(self.env).href(req, kwd)
         elif kwd[0:len('source:')] == 'source:':
-            return req.href.browser(kwd[len('source:'):])
+            from trac.versioncontrol.web_ui.browser import BrowserModule
+            return BrowserModule(self.env).href(req, kwd[len('source:'):])
         # Wiki quickjump
         elif kwd[0:len('wiki:')] == 'wiki:':
             r = "((^|(?<=[^A-Za-z]))[!]?[A-Z][a-z/]+(?:[A-Z][a-z/]+)+)"
Index: trac/wiki/api.py
===================================================================
--- trac/wiki/api.py	(revision 3285)
+++ trac/wiki/api.py	(working copy)
@@ -227,10 +227,13 @@
     # IWikiSyntaxProvider methods
     
     def get_wiki_syntax(self):
+        from trac.wiki.formatter import Formatter
         yield (r"!?(?<!/)\b[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+"
                 "(?:#[A-Za-z0-9]+)?(?=:?\Z|:?\s|[.,;!?\)}\]])",
                lambda x, y, z: self._format_link(x, 'wiki', y, y,
                                                  self.ignore_missing_pages))
+        yield (r"!?(\[%s\])" %  Formatter.QUOTED_STRING,
+               lambda x, y, z: self._format_link(x, 'wiki', y, y, False))
 
     def get_link_resolvers(self):
         yield ('wiki', self._format_fancy_link)
Index: trac/wiki/tests/formatter.py
===================================================================
--- trac/wiki/tests/formatter.py	(revision 3285)
+++ trac/wiki/tests/formatter.py	(working copy)
@@ -127,7 +127,9 @@
                 % (msg, self.file, self.line, self.title, formatter.flavor))
 
     def formatter(self):
-        return Formatter(self.env)
+        return Formatter(self.env, self.env)
+    # Well, in the above, we fake `req` using `self.env`, as all we care about
+    # is the `href` attribute (at least for those unit tests!)
 
     def shortDescription(self):
         return 'Test ' + self.title
Index: trac/wiki/tests/macros.py
===================================================================
--- trac/wiki/tests/macros.py	(revision 3286)
+++ trac/wiki/tests/macros.py	(working copy)
@@ -1,6 +1,7 @@
 import unittest
 
 import trac.wiki.macros
+from trac.versioncontrol.web_ui.browser import BrowserModule
 from trac.wiki.tests import formatter
 
 IMAGE_MACRO_TEST_CASES=u"""
Index: trac/wiki/formatter.py
===================================================================
--- trac/wiki/formatter.py	(revision 3285)
+++ trac/wiki/formatter.py	(working copy)
@@ -149,7 +149,7 @@
     ENDBLOCK_TOKEN = r"\}\}\}"
     ENDBLOCK = "}}}"
     
-    LINK_SCHEME = r"[\w.+-]+" # as per RFC 2396
+    LINK_SCHEME = r"[a-zA-Z][a-zA-Z0-9.+-]*" # as per RFC 3986
     INTERTRAC_SCHEME = r"[a-zA-Z.+-]*?" # no digits (support for shorthand links)
 
     QUOTED_STRING = r"'[^']+'|\"[^\"]+\""
Index: trac/wiki/macros.py
===================================================================
--- trac/wiki/macros.py	(revision 3288)
+++ trac/wiki/macros.py	(working copy)
@@ -288,9 +288,10 @@
                 raise Exception("%s module can't have attachments" % parts[0])
         elif len(parts) == 2:
             from trac.versioncontrol.web_ui import BrowserModule
+            browser = BrowserModule(self.env)
             try:
-                browser_links = [link for link,_ in 
-                                 BrowserModule(self.env).get_link_resolvers()]
+                browser_links = [link for link,_ in
+                                 browser.get_link_resolvers()]
             except Exception:
                 browser_links = []
             if parts[0] in browser_links:   # source:path
@@ -298,8 +299,8 @@
                 rev = None
                 if '@' in file:
                     file, rev = file.split('@')
-                url = self.env.href.browser(file, rev=rev)
-                raw_url = self.env.href.browser(file, rev=rev, format='raw')
+                url = browser.href(req, file, rev=rev)
+                raw_url = browser.href(req, file, rev=rev, format='raw')
                 desc = filespec
             else: # #ticket:attachment or WikiPage:attachment
                 # FIXME: do something generic about shorthand forms...

