##// END OF EJS Templates
hgweb: give ErrorResponse a descriptive string/Exception representation...
Mads Kiilerich -
r13444:75f5f312 stable
parent child Browse files
Show More
@@ -1,164 +1,163 b''
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import errno, mimetypes, os
10 10
11 11 HTTP_OK = 200
12 12 HTTP_NOT_MODIFIED = 304
13 13 HTTP_BAD_REQUEST = 400
14 14 HTTP_UNAUTHORIZED = 401
15 15 HTTP_FORBIDDEN = 403
16 16 HTTP_NOT_FOUND = 404
17 17 HTTP_METHOD_NOT_ALLOWED = 405
18 18 HTTP_SERVER_ERROR = 500
19 19
20 20 # Hooks for hgweb permission checks; extensions can add hooks here. Each hook
21 21 # is invoked like this: hook(hgweb, request, operation), where operation is
22 22 # either read, pull or push. Hooks should either raise an ErrorResponse
23 23 # exception, or just return.
24 24 # It is possible to do both authentication and authorization through this.
25 25 permhooks = []
26 26
27 27 def checkauthz(hgweb, req, op):
28 28 '''Check permission for operation based on request data (including
29 29 authentication info). Return if op allowed, else raise an ErrorResponse
30 30 exception.'''
31 31
32 32 user = req.env.get('REMOTE_USER')
33 33
34 34 deny_read = hgweb.configlist('web', 'deny_read')
35 35 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
36 36 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
37 37
38 38 allow_read = hgweb.configlist('web', 'allow_read')
39 39 result = (not allow_read) or (allow_read == ['*'])
40 40 if not (result or user in allow_read):
41 41 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
42 42
43 43 if op == 'pull' and not hgweb.allowpull:
44 44 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
45 45 elif op == 'pull' or op is None: # op is None for interface requests
46 46 return
47 47
48 48 # enforce that you can only push using POST requests
49 49 if req.env['REQUEST_METHOD'] != 'POST':
50 50 msg = 'push requires POST request'
51 51 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
52 52
53 53 # require ssl by default for pushing, auth info cannot be sniffed
54 54 # and replayed
55 55 scheme = req.env.get('wsgi.url_scheme')
56 56 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
57 57 raise ErrorResponse(HTTP_OK, 'ssl required')
58 58
59 59 deny = hgweb.configlist('web', 'deny_push')
60 60 if deny and (not user or deny == ['*'] or user in deny):
61 61 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
62 62
63 63 allow = hgweb.configlist('web', 'allow_push')
64 64 result = allow and (allow == ['*'] or user in allow)
65 65 if not result:
66 66 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
67 67
68 68 # Add the default permhook, which provides simple authorization.
69 69 permhooks.append(checkauthz)
70 70
71 71
72 72 class ErrorResponse(Exception):
73 73 def __init__(self, code, message=None, headers=[]):
74 Exception.__init__(self)
74 if message is None:
75 message = _statusmessage(code)
76 Exception.__init__(self, code, message)
75 77 self.code = code
78 self.message = message
76 79 self.headers = headers
77 if message is not None:
78 self.message = message
79 else:
80 self.message = _statusmessage(code)
81 80
82 81 def _statusmessage(code):
83 82 from BaseHTTPServer import BaseHTTPRequestHandler
84 83 responses = BaseHTTPRequestHandler.responses
85 84 return responses.get(code, ('Error', 'Unknown error'))[0]
86 85
87 86 def statusmessage(code, message=None):
88 87 return '%d %s' % (code, message or _statusmessage(code))
89 88
90 89 def get_mtime(spath):
91 90 cl_path = os.path.join(spath, "00changelog.i")
92 91 if os.path.exists(cl_path):
93 92 return os.stat(cl_path).st_mtime
94 93 else:
95 94 return os.stat(spath).st_mtime
96 95
97 96 def staticfile(directory, fname, req):
98 97 """return a file inside directory with guessed Content-Type header
99 98
100 99 fname always uses '/' as directory separator and isn't allowed to
101 100 contain unusual path components.
102 101 Content-Type is guessed using the mimetypes module.
103 102 Return an empty string if fname is illegal or file not found.
104 103
105 104 """
106 105 parts = fname.split('/')
107 106 for part in parts:
108 107 if (part in ('', os.curdir, os.pardir) or
109 108 os.sep in part or os.altsep is not None and os.altsep in part):
110 109 return ""
111 110 fpath = os.path.join(*parts)
112 111 if isinstance(directory, str):
113 112 directory = [directory]
114 113 for d in directory:
115 114 path = os.path.join(d, fpath)
116 115 if os.path.exists(path):
117 116 break
118 117 try:
119 118 os.stat(path)
120 119 ct = mimetypes.guess_type(path)[0] or "text/plain"
121 120 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
122 121 fp = open(path, 'rb')
123 122 data = fp.read()
124 123 fp.close()
125 124 return data
126 125 except TypeError:
127 126 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
128 127 except OSError, err:
129 128 if err.errno == errno.ENOENT:
130 129 raise ErrorResponse(HTTP_NOT_FOUND)
131 130 else:
132 131 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
133 132
134 133 def paritygen(stripecount, offset=0):
135 134 """count parity of horizontal stripes for easier reading"""
136 135 if stripecount and offset:
137 136 # account for offset, e.g. due to building the list in reverse
138 137 count = (stripecount + offset) % stripecount
139 138 parity = (stripecount + offset) / stripecount & 1
140 139 else:
141 140 count = 0
142 141 parity = 0
143 142 while True:
144 143 yield parity
145 144 count += 1
146 145 if stripecount and count >= stripecount:
147 146 parity = 1 - parity
148 147 count = 0
149 148
150 149 def get_contact(config):
151 150 """Return repo contact information or empty string.
152 151
153 152 web.contact is the primary source, but if that is not set, try
154 153 ui.username or $EMAIL as a fallback to display something useful.
155 154 """
156 155 return (config("web", "contact") or
157 156 config("ui", "username") or
158 157 os.environ.get("EMAIL") or "")
159 158
160 159 def caching(web, req):
161 160 tag = str(web.mtime)
162 161 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
163 162 raise ErrorResponse(HTTP_NOT_MODIFIED)
164 163 req.headers.append(('ETag', tag))
General Comments 0
You need to be logged in to leave comments. Login now