Rev 2948: Fix 1.1 related bugs in HTTP server, add HTTPS passing tests (by temporarily disabling pycurl certificate verification). in file:///v/home/vila/src/bzr/experimental/https/

Vincent Ladeuil v.ladeuil+lp at free.fr
Sat Jan 5 22:19:09 GMT 2008


At file:///v/home/vila/src/bzr/experimental/https/

------------------------------------------------------------
revno: 2948
revision-id:v.ladeuil+lp at free.fr-20080105221904-185q2vl2hjbeul3d
parent: v.ladeuil+lp at free.fr-20080105220947-t2kymulzeqf1g5n5
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: https
timestamp: Sat 2008-01-05 23:19:04 +0100
message:
  Fix 1.1 related bugs in HTTP server, add HTTPS passing tests (by temporarily disabling pycurl certificate verification).
  
  * doc/developers/authentication-ring.txt
  (verify_certificates): Fix typo, obviously only apply to HTTPS 
  
  * bzrlib/transport/http/ca_bundle.py:
  (get_ca_path): Fix too long lines.
  
  * bzrlib/transport/http/_pycurl.py:
  (CURLE_SSL_CACERT): New error code.
  (PyCurlTransport._set_curl_options): Temporarily disable peer
  verification to make tests pass.
  (PyCurlTransport._curl_perform): Catch CURLE_SSL_CACERT as a
  connection error.
  (get_test_permutations): Add HTTPS tests.
  
  * bzrlib/tests/https_server.py:
  (TestingHTTPSServer, TestingThreadingHTTPSServer): HTTPS test
  servers.
  (HTTPSServer_PyCurl): New class for pycurl HTTPS test server.
  
  * bzrlib/tests/http_server.py:
  (TestingHTTPRequestHandler.send_error): Overrides python version
  since we need to specify a Content-Length.
  (TestingHTTPRequestHandler.get_multiple_ranges): Sabotage !
  Off-by-one error caused a buggy comment ! Went unnoticed until
  pycurl+https hang.
  (HttpServer.create_httpd): Allow server creation overriding.
modified:
  bzrlib/tests/http_server.py    httpserver.py-20061012142527-m1yxdj1xazsf8d7s-1
  bzrlib/tests/https_server.py   https_server.py-20071121173708-aj8zczi0ziwbwz21-1
  bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
  bzrlib/transport/http/_pycurl.py pycurlhttp.py-20060110060940-4e2a705911af77a6
  bzrlib/transport/http/ca_bundle.py ca_bundle.py-20070226091335-84kb1xg1r2jjf858-1
  doc/developers/authentication-ring.txt authring.txt-20070718200437-q5tdik0ne6lor86d-1
-------------- next part --------------
=== modified file 'bzrlib/tests/http_server.py'
--- a/bzrlib/tests/http_server.py	2008-01-03 11:49:52 +0000
+++ b/bzrlib/tests/http_server.py	2008-01-05 22:19:04 +0000
@@ -38,6 +38,10 @@
         return 'path %s is not in %s' % self.args
 
 
+def _quote_html(html):
+    return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+
+
 class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
     """Handles one request.
 
@@ -126,6 +130,33 @@
         header_line = '%s: %s\r\n' % (keyword, value)
         return len(header_line)
 
+    def send_error(self, code, message=None):
+        """Overrides base implementation to work around bugs in python2.5.
+
+
+        To be 1.1 compliant, we need to specify a Content-Length or 1.1 clients
+        may hang.
+        """
+        try:
+            short, long = self.responses[code]
+        except KeyError:
+            short, long = '???', '???'
+        if message is None:
+            message = short
+        explain = long
+        self.log_error("code %d, message %s", code, message)
+        # using _quote_html to prevent Cross Site Scripting attacks (see bug
+        # #1100201)
+        content = (self.error_message_format %
+                   {'code': code, 'message': _quote_html(message),
+                    'explain': explain})
+        self.send_response(code, message)
+        self.send_header("Content-Type", "text/html")
+        self.send_header('Content-Length', len(content))
+        self.end_headers()
+        if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
+            self.wfile.write(content)
+
     def send_head(self):
         """Overrides base implementation to work around a bug in python2.5."""
         path = self.translate_path(self.path)
@@ -175,7 +206,7 @@
             content_length += self._header_line_length(
                 'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
             content_length += len('\r\n') # end headers
-            content_length += end - start # + 1
+            content_length += end - start + 1
         content_length += len(boundary_line)
         self.send_header('Content-length', content_length)
         self.end_headers()
@@ -417,9 +448,8 @@
         # Allows tests to verify number of GET requests issued
         self.GET_request_nb = 0
 
-    def create_httpd(self):
-        return TestingHTTPServer((self.host, self.port), self.request_handler,
-                                 self)
+    def create_httpd(self, serv_cls, rhandler_cls):
+        return serv_cls((self.host, self.port), self.request_handler, self)
 
     def _get_httpd(self):
         if self._httpd is None:
@@ -438,7 +468,7 @@
             if serv_cls is None:
                 raise httplib.UnknownProtocol(proto_vers)
             else:
-                self._httpd = serv_cls((self.host, self.port), rhandler, self)
+                self._httpd = self.create_httpd(serv_cls, rhandler)
             host, self.port = self._httpd.socket.getsockname()
         return self._httpd
 

=== modified file 'bzrlib/tests/https_server.py'
--- a/bzrlib/tests/https_server.py	2007-11-24 14:57:25 +0000
+++ b/bzrlib/tests/https_server.py	2008-01-05 22:19:04 +0000
@@ -16,18 +16,17 @@
 
 """HTTPS test server, available when ssl python module is available"""
 
+import ssl
+
 from bzrlib.tests import (
     http_server,
     ssl_certs,
     )
 
 
-class TestingHTTPSServer(http_server.TestingHTTPServer):
+class TestingHTTPSServerMixin:
 
-    def __init__(self, server_address, request_handler_class,
-                 test_case_server, key_file, cert_file):
-        http_server.TestingHTTPServer.__init__(
-            self, server_address, request_handler_class, test_case_server)
+    def __init__(self, key_file, cert_file):
         self.key_file = key_file
         self.cert_file = cert_file
 
@@ -37,18 +36,41 @@
         This is called in response to a connection issued to the server, we
         wrap the socket with SSL.
         """
-        import ssl
         sock, addr = self.socket.accept()
         sslconn = ssl.wrap_socket(sock, server_side=True,
                                   keyfile=self.key_file,
                                   certfile=self.cert_file)
         return sslconn, addr
 
+class TestingHTTPSServer(TestingHTTPSServerMixin,
+                         http_server.TestingHTTPServer):
+
+    def __init__(self, server_address, request_handler_class,
+                 test_case_server, key_file, cert_file):
+        TestingHTTPSServerMixin.__init__(self, key_file, cert_file)
+        http_server.TestingHTTPServer.__init__(
+            self, server_address, request_handler_class, test_case_server)
+
+
+class TestingThreadingHTTPSServer(TestingHTTPSServerMixin,
+                                  http_server.TestingThreadingHTTPServer):
+
+    def __init__(self, server_address, request_handler_class,
+                 test_case_server, key_file, cert_file):
+        TestingHTTPSServerMixin.__init__(self, key_file, cert_file)
+        http_server.TestingThreadingHTTPServer.__init__(
+            self, server_address, request_handler_class, test_case_server)
+
 
 class HTTPSServer(http_server.HttpServer):
 
     _url_protocol = 'https'
 
+    # The real servers depending on the protocol
+    http_server_class = {'HTTP/1.0': TestingHTTPSServer,
+                         'HTTP/1.1': TestingThreadingHTTPSServer,
+                         }
+
     # Provides usable defaults since an https server requires both a
     # private key and certificate to work.
     def __init__(self, request_handler=http_server.TestingHTTPRequestHandler,
@@ -59,9 +81,9 @@
         self.cert_file = cert_file
         self.temp_files = []
 
-    def create_httpd(self):
-        return TestingHTTPSServer((self.host, self.port), self.request_handler,
-                                  self, self.key_file, self.cert_file)
+    def create_httpd(self, serv_cls, rhandler_cls):
+        return serv_cls((self.host, self.port), self.request_handler,
+                        self, self.key_file, self.cert_file)
 
 
 class HTTPSServer_urllib(HTTPSServer):
@@ -74,3 +96,16 @@
     # urls returned by this server should require the urllib client impl
     _url_protocol = 'https+urllib'
 
+
+class HTTPSServer_PyCurl(HTTPSServer):
+    """Subclass of HTTPSServer that gives http+pycurl urls.
+
+    This is for use in testing: connections to this server will always go
+    through pycurl where possible.
+    """
+
+    # We don't care about checking the pycurl availability as
+    # this server will be required only when pycurl is present
+
+    # urls returned by this server should require the pycurl client impl
+    _url_protocol = 'https+pycurl'

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2008-01-03 11:49:52 +0000
+++ b/bzrlib/tests/test_http.py	2008-01-05 22:19:04 +0000
@@ -593,7 +593,6 @@
     def test_http_has(self):
         server = self.get_readonly_server()
         t = self._transport(server.get_url())
-        import pdb; pdb.set_trace()
         self.assertRaises(errors.InvalidHttpResponse, t.has, 'foo/bar')
 
     def test_http_get(self):

=== modified file 'bzrlib/transport/http/_pycurl.py'
--- a/bzrlib/transport/http/_pycurl.py	2008-01-03 11:49:52 +0000
+++ b/bzrlib/transport/http/_pycurl.py	2008-01-05 22:19:04 +0000
@@ -86,12 +86,13 @@
     """
     return pycurl.__dict__.get(symbol, default)
 
-CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
 CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7)
 CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6)
 CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5)
 CURLE_GOT_NOTHING = _get_pycurl_errcode('E_GOT_NOTHING', 52)
 CURLE_PARTIAL_FILE = _get_pycurl_errcode('E_PARTIAL_FILE', 18)
+CURLE_SSL_CACERT = _get_pycurl_errcode('E_SSL_CACERT', 60)
+CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
 
 
 class PyCurlTransport(HttpTransportBase):
@@ -310,6 +311,9 @@
             if password is not None: # '' is a valid password
                 userpass += password
             curl.setopt(pycurl.USERPWD, userpass)
+        # XXX: Temporarily disable peer verification
+#        curl.setopt(pycurl.SSL_VERIFYHOST, 2)
+        curl.setopt(pycurl.SSL_VERIFYPEER, 0)
 
     def _curl_perform(self, curl, header, more_headers=[]):
         """Perform curl operation and translate exceptions."""
@@ -327,11 +331,13 @@
             url = curl.getinfo(pycurl.EFFECTIVE_URL)
             mutter('got pycurl error: %s, %s, %s, url: %s ',
                     e[0], e[1], e, url)
-            if e[0] in (CURLE_SSL_CACERT_BADFILE,
-                        CURLE_COULDNT_RESOLVE_HOST,
+            if e[0] in (CURLE_COULDNT_RESOLVE_HOST,
+                        CURLE_COULDNT_RESOLVE_PROXY,
                         CURLE_COULDNT_CONNECT,
                         CURLE_GOT_NOTHING,
-                        CURLE_COULDNT_RESOLVE_PROXY,):
+                        CURLE_SSL_CACERT,
+                        CURLE_SSL_CACERT_BADFILE,
+                        ):
                 raise errors.ConnectionError(
                     'curl connection error (%s)\non %s' % (e[1], url))
             elif e[0] == CURLE_PARTIAL_FILE:
@@ -356,6 +362,11 @@
 
 def get_test_permutations():
     """Return the permutations to be used in testing."""
-    from bzrlib.tests.http_server import HttpServer_PyCurl
-    return [(PyCurlTransport, HttpServer_PyCurl),
-            ]
+    from bzrlib import tests
+    from bzrlib.tests import http_server
+    permutations = [(PyCurlTransport, http_server.HttpServer_PyCurl),]
+    if tests.HTTPSServerFeature.available():
+        from bzrlib.tests import https_server
+        permutations.append((PyCurlTransport,
+                             https_server.HTTPSServer_PyCurl))
+    return permutations

=== modified file 'bzrlib/transport/http/ca_bundle.py'
--- a/bzrlib/transport/http/ca_bundle.py	2007-02-27 06:57:37 +0000
+++ b/bzrlib/transport/http/ca_bundle.py	2008-01-05 22:19:04 +0000
@@ -36,13 +36,13 @@
     # from "Details on Server SSL Certificates"
     # http://curl.haxx.se/docs/sslcerts.html
     #
-    # 4. If you're using the curl command line tool, you can specify your own CA
-    #    cert path by setting the environment variable CURL_CA_BUNDLE to the path
-    #    of your choice.
+    # 4. If you're using the curl command line tool, you can specify your own
+    #    CA cert path by setting the environment variable CURL_CA_BUNDLE to the
+    #    path of your choice.
     #
-    #    If you're using the curl command line tool on Windows, curl will search
-    #    for a CA cert file named "curl-ca-bundle.crt" in these directories and in
-    #    this order:
+    #    If you're using the curl command line tool on Windows, curl will
+    #    search for a CA cert file named "curl-ca-bundle.crt" in these
+    #    directories and in this order:
     #      1. application's directory
     #      2. current working directory
     #      3. Windows System directory (e.g. C:\windows\system32)

=== modified file 'doc/developers/authentication-ring.txt'
--- a/doc/developers/authentication-ring.txt	2007-11-04 15:24:27 +0000
+++ b/doc/developers/authentication-ring.txt	2008-01-05 22:19:04 +0000
@@ -205,7 +205,7 @@
   * ``port``: the port the server is listening,
 
   * ``verify_certificates``: to control certificate verification (useful
-    for self certified hosts). This applies to HTTP[S] only. Accepted values
+    for self certified hosts). This applies to HTTPS only. Accepted values
     are yes and no, default to yes.
 
   * ``path``: the branch location,



More information about the bazaar-commits mailing list