##// END OF EJS Templates
hgweb: refactor all pyOpenSSL references into one class
Mads Kiilerich -
r12783:191d0fd5 default
parent child Browse files
Show More
@@ -1,277 +1,286
1 1 # hgweb/server.py - The standalone hg web server.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 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 os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 10 from mercurial import util, error
11 11 from mercurial.i18n import _
12 12
13 13 def _splitURI(uri):
14 14 """ Return path and query splited from uri
15 15
16 16 Just like CGI environment, the path is unquoted, the query is
17 17 not.
18 18 """
19 19 if '?' in uri:
20 20 path, query = uri.split('?', 1)
21 21 else:
22 22 path, query = uri, ''
23 23 return urllib.unquote(path), query
24 24
25 25 class _error_logger(object):
26 26 def __init__(self, handler):
27 27 self.handler = handler
28 28 def flush(self):
29 29 pass
30 30 def write(self, str):
31 31 self.writelines(str.split('\n'))
32 32 def writelines(self, seq):
33 33 for msg in seq:
34 34 self.handler.log_error("HG error: %s", msg)
35 35
36 class _hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
36 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
37 37
38 38 url_scheme = 'http'
39 39
40 @staticmethod
41 def preparehttpserver(httpserver, ssl_cert):
42 """Prepare .socket of new HTTPServer instance"""
43 pass
44
40 45 def __init__(self, *args, **kargs):
41 46 self.protocol_version = 'HTTP/1.1'
42 47 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
43 48
44 49 def _log_any(self, fp, format, *args):
45 50 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
46 51 self.log_date_time_string(),
47 52 format % args))
48 53 fp.flush()
49 54
50 55 def log_error(self, format, *args):
51 56 self._log_any(self.server.errorlog, format, *args)
52 57
53 58 def log_message(self, format, *args):
54 59 self._log_any(self.server.accesslog, format, *args)
55 60
56 61 def do_write(self):
57 62 try:
58 63 self.do_hgweb()
59 64 except socket.error, inst:
60 65 if inst[0] != errno.EPIPE:
61 66 raise
62 67
63 68 def do_POST(self):
64 69 try:
65 70 self.do_write()
66 71 except StandardError:
67 72 self._start_response("500 Internal Server Error", [])
68 73 self._write("Internal Server Error")
69 74 tb = "".join(traceback.format_exception(*sys.exc_info()))
70 75 self.log_error("Exception happened during processing "
71 76 "request '%s':\n%s", self.path, tb)
72 77
73 78 def do_GET(self):
74 79 self.do_POST()
75 80
76 81 def do_hgweb(self):
77 82 path, query = _splitURI(self.path)
78 83
79 84 env = {}
80 85 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
81 86 env['REQUEST_METHOD'] = self.command
82 87 env['SERVER_NAME'] = self.server.server_name
83 88 env['SERVER_PORT'] = str(self.server.server_port)
84 89 env['REQUEST_URI'] = self.path
85 90 env['SCRIPT_NAME'] = self.server.prefix
86 91 env['PATH_INFO'] = path[len(self.server.prefix):]
87 92 env['REMOTE_HOST'] = self.client_address[0]
88 93 env['REMOTE_ADDR'] = self.client_address[0]
89 94 if query:
90 95 env['QUERY_STRING'] = query
91 96
92 97 if self.headers.typeheader is None:
93 98 env['CONTENT_TYPE'] = self.headers.type
94 99 else:
95 100 env['CONTENT_TYPE'] = self.headers.typeheader
96 101 length = self.headers.getheader('content-length')
97 102 if length:
98 103 env['CONTENT_LENGTH'] = length
99 104 for header in [h for h in self.headers.keys()
100 105 if h not in ('content-type', 'content-length')]:
101 106 hkey = 'HTTP_' + header.replace('-', '_').upper()
102 107 hval = self.headers.getheader(header)
103 108 hval = hval.replace('\n', '').strip()
104 109 if hval:
105 110 env[hkey] = hval
106 111 env['SERVER_PROTOCOL'] = self.request_version
107 112 env['wsgi.version'] = (1, 0)
108 113 env['wsgi.url_scheme'] = self.url_scheme
109 114 env['wsgi.input'] = self.rfile
110 115 env['wsgi.errors'] = _error_logger(self)
111 116 env['wsgi.multithread'] = isinstance(self.server,
112 117 SocketServer.ThreadingMixIn)
113 118 env['wsgi.multiprocess'] = isinstance(self.server,
114 119 SocketServer.ForkingMixIn)
115 120 env['wsgi.run_once'] = 0
116 121
117 122 self.close_connection = True
118 123 self.saved_status = None
119 124 self.saved_headers = []
120 125 self.sent_headers = False
121 126 self.length = None
122 127 for chunk in self.server.application(env, self._start_response):
123 128 self._write(chunk)
124 129
125 130 def send_headers(self):
126 131 if not self.saved_status:
127 132 raise AssertionError("Sending headers before "
128 133 "start_response() called")
129 134 saved_status = self.saved_status.split(None, 1)
130 135 saved_status[0] = int(saved_status[0])
131 136 self.send_response(*saved_status)
132 137 should_close = True
133 138 for h in self.saved_headers:
134 139 self.send_header(*h)
135 140 if h[0].lower() == 'content-length':
136 141 should_close = False
137 142 self.length = int(h[1])
138 143 # The value of the Connection header is a list of case-insensitive
139 144 # tokens separated by commas and optional whitespace.
140 145 if 'close' in [token.strip().lower() for token in
141 146 self.headers.get('connection', '').split(',')]:
142 147 should_close = True
143 148 if should_close:
144 149 self.send_header('Connection', 'close')
145 150 self.close_connection = should_close
146 151 self.end_headers()
147 152 self.sent_headers = True
148 153
149 154 def _start_response(self, http_status, headers, exc_info=None):
150 155 code, msg = http_status.split(None, 1)
151 156 code = int(code)
152 157 self.saved_status = http_status
153 158 bad_headers = ('connection', 'transfer-encoding')
154 159 self.saved_headers = [h for h in headers
155 160 if h[0].lower() not in bad_headers]
156 161 return self._write
157 162
158 163 def _write(self, data):
159 164 if not self.saved_status:
160 165 raise AssertionError("data written before start_response() called")
161 166 elif not self.sent_headers:
162 167 self.send_headers()
163 168 if self.length is not None:
164 169 if len(data) > self.length:
165 170 raise AssertionError("Content-length header sent, but more "
166 171 "bytes than specified are being written.")
167 172 self.length = self.length - len(data)
168 173 self.wfile.write(data)
169 174 self.wfile.flush()
170 175
171 class _shgwebhandler(_hgwebhandler):
176 class _httprequesthandleropenssl(_httprequesthandler):
177 """HTTPS handler based on pyOpenSSL"""
172 178
173 179 url_scheme = 'https'
174 180
181 @staticmethod
182 def preparehttpserver(httpserver, ssl_cert):
183 try:
184 import OpenSSL
185 OpenSSL.SSL.Context
186 except ImportError:
187 raise util.Abort(_("SSL support is unavailable"))
188 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
189 ctx.use_privatekey_file(ssl_cert)
190 ctx.use_certificate_file(ssl_cert)
191 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
192 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
193 httpserver.server_bind()
194 httpserver.server_activate()
195
175 196 def setup(self):
176 197 self.connection = self.request
177 198 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
178 199 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
179 200
180 201 def do_write(self):
181 from OpenSSL.SSL import SysCallError
202 import OpenSSL
182 203 try:
183 _hgwebhandler.do_write(self)
184 except SysCallError, inst:
204 _httprequesthandler.do_write(self)
205 except OpenSSL.SSL.SysCallError, inst:
185 206 if inst.args[0] != errno.EPIPE:
186 207 raise
187 208
188 209 def handle_one_request(self):
189 from OpenSSL.SSL import SysCallError, ZeroReturnError
210 import OpenSSL
190 211 try:
191 _hgwebhandler.handle_one_request(self)
192 except (SysCallError, ZeroReturnError):
212 _httprequesthandler.handle_one_request(self)
213 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
193 214 self.close_connection = True
194 215 pass
195 216
196 217 try:
197 218 from threading import activeCount
198 219 _mixin = SocketServer.ThreadingMixIn
199 220 except ImportError:
200 221 if hasattr(os, "fork"):
201 222 _mixin = SocketServer.ForkingMixIn
202 223 else:
203 224 class _mixin:
204 225 pass
205 226
206 227 def openlog(opt, default):
207 228 if opt and opt != '-':
208 229 return open(opt, 'a')
209 230 return default
210 231
211 232 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
212 233
213 234 # SO_REUSEADDR has broken semantics on windows
214 235 if os.name == 'nt':
215 236 allow_reuse_address = 0
216 237
217 238 def __init__(self, ui, app, addr, handler, **kwargs):
218 239 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
219 240 self.daemon_threads = True
220 241 self.application = app
221 242
222 ssl_cert = ui.config('web', 'certificate')
223 if ssl_cert:
224 try:
225 from OpenSSL import SSL
226 ctx = SSL.Context(SSL.SSLv23_METHOD)
227 except ImportError:
228 raise util.Abort(_("SSL support is unavailable"))
229 ctx.use_privatekey_file(ssl_cert)
230 ctx.use_certificate_file(ssl_cert)
231 sock = socket.socket(self.address_family, self.socket_type)
232 self.socket = SSL.Connection(ctx, sock)
233 self.server_bind()
234 self.server_activate()
243 handler.preparehttpserver(self, ui.config('web', 'certificate'))
235 244
236 245 prefix = ui.config('web', 'prefix', '')
237 246 if prefix:
238 247 prefix = '/' + prefix.strip('/')
239 248 self.prefix = prefix
240 249
241 250 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
242 251 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
243 252 self.accesslog = alog
244 253 self.errorlog = elog
245 254
246 255 self.addr, self.port = self.socket.getsockname()[0:2]
247 256 self.fqaddr = socket.getfqdn(addr[0])
248 257
249 258 class IPv6HTTPServer(MercurialHTTPServer):
250 259 address_family = getattr(socket, 'AF_INET6', None)
251 260 def __init__(self, *args, **kwargs):
252 261 if self.address_family is None:
253 262 raise error.RepoError(_('IPv6 is not available on this system'))
254 263 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
255 264
256 265 def create_server(ui, app):
257 266
258 267 if ui.config('web', 'certificate'):
259 handler = _shgwebhandler
268 handler = _httprequesthandleropenssl
260 269 else:
261 handler = _hgwebhandler
270 handler = _httprequesthandler
262 271
263 272 if ui.configbool('web', 'ipv6'):
264 273 cls = IPv6HTTPServer
265 274 else:
266 275 cls = MercurialHTTPServer
267 276
268 277 # ugly hack due to python issue5853 (for threaded use)
269 278 import mimetypes; mimetypes.init()
270 279
271 280 address = ui.config('web', 'address', '')
272 281 port = util.getport(ui.config('web', 'port', 8000))
273 282 try:
274 283 return cls(ui, app, (address, port), handler)
275 284 except socket.error, inst:
276 285 raise util.Abort(_("cannot start server at '%s:%d': %s")
277 286 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now