mercurial/hgweb.py
changeset 1830 4ced57680ce7
parent 1829 b0f6af327fd4
parent 1825 a9343f9d7365
child 1834 24881eaebee3
--- a/mercurial/hgweb.py
+++ b/mercurial/hgweb.py
@@ -7,6 +7,7 @@
 # of the GNU General Public License, incorporated herein by reference.
 
 import os, cgi, sys, urllib
+import mimetypes
 from demandload import demandload
 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
@@ -18,7 +19,7 @@ def templatepath():
     for f in "templates", "../templates":
         p = os.path.join(os.path.dirname(__file__), f)
         if os.path.isdir(p):
-            return p
+            return os.path.normpath(p)
 
 def age(x):
     def plural(t, c):
@@ -71,6 +72,30 @@ def get_mtime(repo_path):
     else:
         return os.stat(hg_path).st_mtime
 
+def staticfile(directory, fname):
+    """return a file inside directory with guessed content-type header
+
+    fname always uses '/' as directory separator and isn't allowed to
+    contain unusual path components.
+    Content-type is guessed using the mimetypes module.
+    Return an empty string if fname is illegal or file not found.
+
+    """
+    parts = fname.split('/')
+    path = directory
+    for part in parts:
+        if (part in ('', os.curdir, os.pardir) or
+            os.sep in part or os.altsep is not None and os.altsep in part):
+            return ""
+        path = os.path.join(path, part)
+    try:
+        os.stat(path)
+        ct = mimetypes.guess_type(path)[0] or "text/plain"
+        return "Content-type: %s\n\n%s" % (ct, file(path).read())
+    except (TypeError, OSError):
+        # illegal fname or unreadable file
+        return ""
+
 class hgrequest(object):
     def __init__(self, inp=None, out=None, env=None):
         self.inp = inp or sys.stdin
@@ -660,9 +685,10 @@ class hgweb(object):
         i = self.repo.tagslist()
         i.reverse()
 
-        def entries(**map):
+        def entries(notip=False, **map):
             parity = 0
             for k,n in i:
+                if notip and k == "tip": continue
                 yield {"parity": parity,
                        "tag": k,
                        "tagmanifest": hex(cl.read(n)[0]),
@@ -672,7 +698,8 @@ class hgweb(object):
 
         yield self.t("tags",
                      manifest=hex(mf),
-                     entries=entries)
+                     entries=lambda **x: entries(False, **x),
+                     entriesnotip=lambda **x: entries(True, **x))
 
     def summary(self):
         cl = self.repo.changelog
@@ -843,6 +870,7 @@ class hgweb(object):
                 'ca': [('cmd', ['archive']), ('node', None)],
                 'tags': [('cmd', ['tags'])],
                 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
+                'static': [('cmd', ['static']), ('file', None)]
             }
 
             for k in shortcuts.iterkeys():
@@ -858,6 +886,7 @@ class hgweb(object):
         expand_form(req.form)
 
         t = self.repo.ui.config("web", "templates", templatepath())
+        static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
         m = os.path.join(t, "map")
         style = self.repo.ui.config("web", "style", "")
         if req.form.has_key('style'):
@@ -962,7 +991,7 @@ class hgweb(object):
                 nodes = map(bin, req.form['roots'][0].split(" "))
 
             z = zlib.compressobj()
-            f = self.repo.changegroup(nodes)
+            f = self.repo.changegroup(nodes, 'serve')
             while 1:
                 chunk = f.read(4096)
                 if not chunk:
@@ -981,6 +1010,11 @@ class hgweb(object):
 
             req.write(self.t("error"))
 
+        elif req.form['cmd'][0] == 'static':
+            fname = req.form['file'][0]
+            req.write(staticfile(static, fname)
+                      or self.t("error", error="%r not found" % fname))
+
         else:
             req.write(self.t("error"))
 
@@ -1152,4 +1186,10 @@ class hgwebdir(object):
             else:
                 req.write(tmpl("notfound", repo=virtual))
         else:
-            req.write(tmpl("index", entries=entries))
+            if req.form.has_key('static'):
+                static = os.path.join(templatepath(), "static")
+                fname = req.form['static'][0]
+                req.write(staticfile(static, fname)
+                          or tmpl("error", error="%r not found" % fname))
+            else:
+                req.write(tmpl("index", entries=entries))