Index: trac/attachment.py
===================================================================
--- trac/attachment.py	(revision 3288)
+++ trac/attachment.py	(working copy)
@@ -79,8 +79,13 @@
     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:
+            base = dict.pop('format') + base
+        else: # HTML preview
+            filename += '.html'
+        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 +255,20 @@
     # IRequestHandler methods
 
     def match_request(self, req):
-        match = re.match(r'^/attachment/(ticket|wiki)(?:[/:](.*))?$',
+        match = re.match(r'^/(raw|txt)?attachment/(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
             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 +281,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 +310,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 +404,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 +431,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

