##// END OF EJS Templates
hgweb: drop use of super() for Exception base class...
Matt Mackall -
r13640:19f8629e default
parent child Browse files
Show More
@@ -1,182 +1,182 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 74 if message is None:
75 75 message = _statusmessage(code)
76 super(Exception, self).__init__()
76 Exception.__init__(self)
77 77 self.code = code
78 78 self.message = message
79 79 self.headers = headers
80 80 def __str__(self):
81 81 return self.message
82 82
83 83 class continuereader(object):
84 84 def __init__(self, f, write):
85 85 self.f = f
86 86 self._write = write
87 87 self.continued = False
88 88
89 89 def read(self, amt=-1):
90 90 if not self.continued:
91 91 self.continued = True
92 92 self._write('HTTP/1.1 100 Continue\r\n\r\n')
93 93 return self.f.read(amt)
94 94
95 95 def __getattr__(self, attr):
96 96 if attr in ('close', 'readline', 'readlines', '__iter__'):
97 97 return getattr(self.f, attr)
98 98 raise AttributeError()
99 99
100 100 def _statusmessage(code):
101 101 from BaseHTTPServer import BaseHTTPRequestHandler
102 102 responses = BaseHTTPRequestHandler.responses
103 103 return responses.get(code, ('Error', 'Unknown error'))[0]
104 104
105 105 def statusmessage(code, message=None):
106 106 return '%d %s' % (code, message or _statusmessage(code))
107 107
108 108 def get_mtime(spath):
109 109 cl_path = os.path.join(spath, "00changelog.i")
110 110 if os.path.exists(cl_path):
111 111 return os.stat(cl_path).st_mtime
112 112 else:
113 113 return os.stat(spath).st_mtime
114 114
115 115 def staticfile(directory, fname, req):
116 116 """return a file inside directory with guessed Content-Type header
117 117
118 118 fname always uses '/' as directory separator and isn't allowed to
119 119 contain unusual path components.
120 120 Content-Type is guessed using the mimetypes module.
121 121 Return an empty string if fname is illegal or file not found.
122 122
123 123 """
124 124 parts = fname.split('/')
125 125 for part in parts:
126 126 if (part in ('', os.curdir, os.pardir) or
127 127 os.sep in part or os.altsep is not None and os.altsep in part):
128 128 return ""
129 129 fpath = os.path.join(*parts)
130 130 if isinstance(directory, str):
131 131 directory = [directory]
132 132 for d in directory:
133 133 path = os.path.join(d, fpath)
134 134 if os.path.exists(path):
135 135 break
136 136 try:
137 137 os.stat(path)
138 138 ct = mimetypes.guess_type(path)[0] or "text/plain"
139 139 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
140 140 fp = open(path, 'rb')
141 141 data = fp.read()
142 142 fp.close()
143 143 return data
144 144 except TypeError:
145 145 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
146 146 except OSError, err:
147 147 if err.errno == errno.ENOENT:
148 148 raise ErrorResponse(HTTP_NOT_FOUND)
149 149 else:
150 150 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
151 151
152 152 def paritygen(stripecount, offset=0):
153 153 """count parity of horizontal stripes for easier reading"""
154 154 if stripecount and offset:
155 155 # account for offset, e.g. due to building the list in reverse
156 156 count = (stripecount + offset) % stripecount
157 157 parity = (stripecount + offset) / stripecount & 1
158 158 else:
159 159 count = 0
160 160 parity = 0
161 161 while True:
162 162 yield parity
163 163 count += 1
164 164 if stripecount and count >= stripecount:
165 165 parity = 1 - parity
166 166 count = 0
167 167
168 168 def get_contact(config):
169 169 """Return repo contact information or empty string.
170 170
171 171 web.contact is the primary source, but if that is not set, try
172 172 ui.username or $EMAIL as a fallback to display something useful.
173 173 """
174 174 return (config("web", "contact") or
175 175 config("ui", "username") or
176 176 os.environ.get("EMAIL") or "")
177 177
178 178 def caching(web, req):
179 179 tag = str(web.mtime)
180 180 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
181 181 raise ErrorResponse(HTTP_NOT_MODIFIED)
182 182 req.headers.append(('ETag', tag))
General Comments 0
You need to be logged in to leave comments. Login now