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