135
|
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
|
|
2 #
|
|
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
|
|
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
|
|
5 #
|
|
6 # This software may be used and distributed according to the terms of the
|
|
7 # GNU General Public License version 2, incorporated herein by reference.
|
|
8
|
|
9 import errno, mimetypes, os
|
|
10
|
|
11 HTTP_OK = 200
|
|
12 HTTP_BAD_REQUEST = 400
|
|
13 HTTP_UNAUTHORIZED = 401
|
|
14 HTTP_FORBIDDEN = 403
|
|
15 HTTP_NOT_FOUND = 404
|
|
16 HTTP_METHOD_NOT_ALLOWED = 405
|
|
17 HTTP_SERVER_ERROR = 500
|
|
18
|
|
19 class ErrorResponse(Exception):
|
|
20 def __init__(self, code, message=None, headers=[]):
|
|
21 Exception.__init__(self)
|
|
22 self.code = code
|
|
23 self.headers = headers
|
|
24 if message is not None:
|
|
25 self.message = message
|
|
26 else:
|
|
27 self.message = _statusmessage(code)
|
|
28
|
|
29 def _statusmessage(code):
|
|
30 from BaseHTTPServer import BaseHTTPRequestHandler
|
|
31 responses = BaseHTTPRequestHandler.responses
|
|
32 return responses.get(code, ('Error', 'Unknown error'))[0]
|
|
33
|
|
34 def statusmessage(code):
|
|
35 return '%d %s' % (code, _statusmessage(code))
|
|
36
|
|
37 def get_mtime(repo_path):
|
|
38 store_path = os.path.join(repo_path, ".hg")
|
|
39 if not os.path.isdir(os.path.join(store_path, "data")):
|
|
40 store_path = os.path.join(store_path, "store")
|
|
41 cl_path = os.path.join(store_path, "00changelog.i")
|
|
42 if os.path.exists(cl_path):
|
|
43 return os.stat(cl_path).st_mtime
|
|
44 else:
|
|
45 return os.stat(store_path).st_mtime
|
|
46
|
|
47 def staticfile(directory, fname, req):
|
|
48 """return a file inside directory with guessed Content-Type header
|
|
49
|
|
50 fname always uses '/' as directory separator and isn't allowed to
|
|
51 contain unusual path components.
|
|
52 Content-Type is guessed using the mimetypes module.
|
|
53 Return an empty string if fname is illegal or file not found.
|
|
54
|
|
55 """
|
|
56 parts = fname.split('/')
|
|
57 for part in parts:
|
|
58 if (part in ('', os.curdir, os.pardir) or
|
|
59 os.sep in part or os.altsep is not None and os.altsep in part):
|
|
60 return ""
|
|
61 fpath = os.path.join(*parts)
|
|
62 if isinstance(directory, str):
|
|
63 directory = [directory]
|
|
64 for d in directory:
|
|
65 path = os.path.join(d, fpath)
|
|
66 if os.path.exists(path):
|
|
67 break
|
|
68 try:
|
|
69 os.stat(path)
|
|
70 ct = mimetypes.guess_type(path)[0] or "text/plain"
|
|
71 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
|
|
72 return file(path, 'rb').read()
|
|
73 except TypeError:
|
|
74 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
|
|
75 except OSError, err:
|
|
76 if err.errno == errno.ENOENT:
|
|
77 raise ErrorResponse(HTTP_NOT_FOUND)
|
|
78 else:
|
|
79 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
|
|
80
|
|
81 def paritygen(stripecount, offset=0):
|
|
82 """count parity of horizontal stripes for easier reading"""
|
|
83 if stripecount and offset:
|
|
84 # account for offset, e.g. due to building the list in reverse
|
|
85 count = (stripecount + offset) % stripecount
|
|
86 parity = (stripecount + offset) / stripecount & 1
|
|
87 else:
|
|
88 count = 0
|
|
89 parity = 0
|
|
90 while True:
|
|
91 yield parity
|
|
92 count += 1
|
|
93 if stripecount and count >= stripecount:
|
|
94 parity = 1 - parity
|
|
95 count = 0
|
|
96
|
|
97 def get_contact(config):
|
|
98 """Return repo contact information or empty string.
|
|
99
|
|
100 web.contact is the primary source, but if that is not set, try
|
|
101 ui.username or $EMAIL as a fallback to display something useful.
|
|
102 """
|
|
103 return (config("web", "contact") or
|
|
104 config("ui", "username") or
|
|
105 os.environ.get("EMAIL") or "")
|