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