{
    "file_diffs": [
        {
            "old_file_content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function\n\nimport collections\nimport functools\nimport logging\nimport pycurl  # type: ignore\nimport threading\nimport time\nfrom io import BytesIO\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\n\ncurl_log = logging.getLogger('tornado.curl_httpclient')\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None\n        self._multi = None\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if (request.proxy_auth_mode is None or\n                    request.proxy_auth_mode == \"basic\"):\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line.decode('latin1'))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n",
            "new_file_content": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function\n\nimport collections\nimport functools\nimport logging\nimport pycurl  # type: ignore\nimport threading\nimport time\nfrom io import BytesIO\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\n\ncurl_log = logging.getLogger('tornado.curl_httpclient')\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None\n        self._multi = None\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if (request.proxy_auth_mode is None or\n                    request.proxy_auth_mode == \"basic\"):\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, utf8(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line.decode('latin1'))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n",
            "header": {
                "file": {
                    "path": "tornado/curl_httpclient.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "54fc5b36",
                "new_commit_hash": "c4ddbbbb",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/curl_httpclient.py"
            },
            "plus_file": {
                "path": "b/tornado/curl_httpclient.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 450,
                            "length": 7
                        },
                        "new_range": {
                            "start": 450,
                            "length": 7
                        },
                        "section": "class CurlAsyncHTTPClient(AsyncHTTPClient):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            else:",
                                "type": "context"
                            },
                            {
                                "content": "                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "            curl.setopt(pycurl.USERPWD, native_str(userpwd))",
                                "type": "deleted"
                            },
                            {
                                "content": "            curl.setopt(pycurl.USERPWD, utf8(userpwd))",
                                "type": "added"
                            },
                            {
                                "content": "            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,",
                                "type": "context"
                            },
                            {
                                "content": "                           request.auth_username)",
                                "type": "context"
                            },
                            {
                                "content": "        else:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/curl_httpclient.py",
                            "type": "method",
                            "name": "CurlAsyncHTTPClient._curl_setup_request",
                            "content": "class CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None\n        self._multi = None\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if (request.proxy_auth_mode is None or\n                    request.proxy_auth_mode == \"basic\"):\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line.decode('latin1'))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 292,
                            "end_lineno": 480,
                            "parent": {
                                "file_name": "tornado/curl_httpclient.py",
                                "type": "class",
                                "name": "CurlAsyncHTTPClient",
                                "content": "class CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None\n        self._multi = None\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if (request.proxy_auth_mode is None or\n                    request.proxy_auth_mode == \"basic\"):\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line.decode('latin1'))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 38,
                                "end_lineno": 510,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "tornado/curl_httpclient.py",
                            "type": "class",
                            "name": "CurlAsyncHTTPClient",
                            "content": "class CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None\n        self._multi = None\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if (request.proxy_auth_mode is None or\n                    request.proxy_auth_mode == \"basic\"):\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line.decode('latin1'))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 38,
                            "end_lineno": 510,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# coding: utf-8\nfrom __future__ import absolute_import, division, print_function\n\nfrom hashlib import md5\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest, HTTPClientError\nfrom tornado.locks import Event\nfrom tornado.stack_context import ExceptionStackContext\nfrom tornado.testing import AsyncHTTPTestCase, gen_test\nfrom tornado.test import httpclient_test\nfrom tornado.test.util import unittest, ignore_deprecation\nfrom tornado.web import Application, RequestHandler\n\n\ntry:\n    import pycurl  # type: ignore\nexcept ImportError:\n    pycurl = None\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)\n",
            "new_file_content": "# coding: utf-8\nfrom __future__ import absolute_import, division, print_function\n\nfrom hashlib import md5\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest, HTTPClientError\nfrom tornado.locks import Event\nfrom tornado.stack_context import ExceptionStackContext\nfrom tornado.testing import AsyncHTTPTestCase, gen_test\nfrom tornado.test import httpclient_test\nfrom tornado.test.util import unittest, ignore_deprecation\nfrom tornado.web import Application, RequestHandler\n\n\ntry:\n    import pycurl  # type: ignore\nexcept ImportError:\n    pycurl = None\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def initialize(self, username, password):\n        self.username = username\n        self.password = password\n\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == self.username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (self.username, realm, self.password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler, {'username': 'foo', 'password': 'bar'}),\n            ('/digest_non_ascii', DigestAuthHandler, {'username': 'foo', 'password': 'barユ£'}),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)\n\n    def test_digest_auth_non_ascii(self):\n        response = self.fetch('/digest_non_ascii', auth_mode='digest',\n                              auth_username='foo', auth_password='barユ£')\n        self.assertEqual(response.body, b'ok')\n",
            "header": {
                "file": {
                    "path": "tornado/test/curl_httpclient_test.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "b7a85952",
                "new_commit_hash": "4230d4cd",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/test/curl_httpclient_test.py"
            },
            "plus_file": {
                "path": "b/tornado/test/curl_httpclient_test.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 32,
                            "length": 13
                        },
                        "new_range": {
                            "start": 32,
                            "length": 15
                        },
                        "section": "class CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "class DigestAuthHandler(RequestHandler):",
                                "type": "context"
                            },
                            {
                                "content": "    def initialize(self, username, password):",
                                "type": "added"
                            },
                            {
                                "content": "        self.username = username",
                                "type": "added"
                            },
                            {
                                "content": "        self.password = password",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def get(self):",
                                "type": "context"
                            },
                            {
                                "content": "        realm = 'test'",
                                "type": "context"
                            },
                            {
                                "content": "        opaque = 'asdf'",
                                "type": "context"
                            },
                            {
                                "content": "        # Real implementations would use a random nonce.",
                                "type": "context"
                            },
                            {
                                "content": "        nonce = \"1234\"",
                                "type": "context"
                            },
                            {
                                "content": "        username = 'foo'",
                                "type": "deleted"
                            },
                            {
                                "content": "        password = 'bar'",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        auth_header = self.request.headers.get('Authorization', None)",
                                "type": "context"
                            },
                            {
                                "content": "        if auth_header is not None:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "DigestAuthHandler.get",
                            "content": "class DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 35,
                            "end_lineno": 70,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "DigestAuthHandler",
                                "content": "class DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 34,
                                "end_lineno": 70,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "DigestAuthHandler",
                            "content": "class DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 34,
                            "end_lineno": 70,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "DigestAuthHandler.initialize",
                            "content": "class DigestAuthHandler(RequestHandler):\n    def initialize(self, username, password):\n        self.username = username\n        self.password = password\n\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == self.username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (self.username, realm, self.password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 35,
                            "end_lineno": 37,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "DigestAuthHandler",
                                "content": "class DigestAuthHandler(RequestHandler):\n    def initialize(self, username, password):\n        self.username = username\n        self.password = password\n\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == self.username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (self.username, realm, self.password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 34,
                                "end_lineno": 72,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 53,
                            "length": 9
                        },
                        "new_range": {
                            "start": 55,
                            "length": 9
                        },
                        "section": "class DigestAuthHandler(RequestHandler):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            assert param_dict['realm'] == realm",
                                "type": "context"
                            },
                            {
                                "content": "            assert param_dict['opaque'] == opaque",
                                "type": "context"
                            },
                            {
                                "content": "            assert param_dict['nonce'] == nonce",
                                "type": "context"
                            },
                            {
                                "content": "            assert param_dict['username'] == username",
                                "type": "deleted"
                            },
                            {
                                "content": "            assert param_dict['username'] == self.username",
                                "type": "added"
                            },
                            {
                                "content": "            assert param_dict['uri'] == self.request.path",
                                "type": "context"
                            },
                            {
                                "content": "            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()",
                                "type": "deleted"
                            },
                            {
                                "content": "            h1 = md5(utf8('%s:%s:%s' % (self.username, realm, self.password))).hexdigest()",
                                "type": "added"
                            },
                            {
                                "content": "            h2 = md5(utf8('%s:%s' % (self.request.method,",
                                "type": "context"
                            },
                            {
                                "content": "                                     self.request.path))).hexdigest()",
                                "type": "context"
                            },
                            {
                                "content": "            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "DigestAuthHandler.get",
                            "content": "class DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 35,
                            "end_lineno": 70,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "DigestAuthHandler",
                                "content": "class DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 34,
                                "end_lineno": 70,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "DigestAuthHandler",
                            "content": "class DigestAuthHandler(RequestHandler):\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n        username = 'foo'\n        password = 'bar'\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (username, realm, password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 34,
                            "end_lineno": 70,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 88,
                            "length": 7
                        },
                        "new_range": {
                            "start": 90,
                            "length": 8
                        },
                        "section": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def get_app(self):",
                                "type": "context"
                            },
                            {
                                "content": "        return Application([",
                                "type": "context"
                            },
                            {
                                "content": "            ('/digest', DigestAuthHandler),",
                                "type": "deleted"
                            },
                            {
                                "content": "            ('/digest', DigestAuthHandler, {'username': 'foo', 'password': 'bar'}),",
                                "type": "added"
                            },
                            {
                                "content": "            ('/digest_non_ascii', DigestAuthHandler, {'username': 'foo', 'password': 'barユ£'}),",
                                "type": "added"
                            },
                            {
                                "content": "            ('/custom_reason', CustomReasonHandler),",
                                "type": "context"
                            },
                            {
                                "content": "            ('/custom_fail_reason', CustomFailReasonHandler),",
                                "type": "context"
                            },
                            {
                                "content": "        ])",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "CurlHTTPClientTestCase",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 84,
                            "end_lineno": 145,
                            "parent": null
                        },
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "CurlHTTPClientTestCase.get_app",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 89,
                            "end_lineno": 94,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "CurlHTTPClientTestCase",
                                "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 84,
                                "end_lineno": 145,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 143,
                            "length": 3
                        },
                        "new_range": {
                            "start": 146,
                            "length": 8
                        },
                        "section": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                # during the setup phase doesn't lead the request to",
                                "type": "context"
                            },
                            {
                                "content": "                # be dropped on the floor.",
                                "type": "context"
                            },
                            {
                                "content": "                response = self.fetch(u'/ユニコード', raise_error=True)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_digest_auth_non_ascii(self):",
                                "type": "added"
                            },
                            {
                                "content": "        response = self.fetch('/digest_non_ascii', auth_mode='digest',",
                                "type": "added"
                            },
                            {
                                "content": "                              auth_username='foo', auth_password='barユ£')",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(response.body, b'ok')",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "class",
                            "name": "CurlHTTPClientTestCase",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler, {'username': 'foo', 'password': 'bar'}),\n            ('/digest_non_ascii', DigestAuthHandler, {'username': 'foo', 'password': 'barユ£'}),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)\n\n    def test_digest_auth_non_ascii(self):\n        response = self.fetch('/digest_non_ascii', auth_mode='digest',\n                              auth_username='foo', auth_password='barユ£')\n        self.assertEqual(response.body, b'ok')",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 86,
                            "end_lineno": 153,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "tornado/test/curl_httpclient_test.py",
                            "type": "method",
                            "name": "CurlHTTPClientTestCase.test_digest_auth_non_ascii",
                            "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler, {'username': 'foo', 'password': 'bar'}),\n            ('/digest_non_ascii', DigestAuthHandler, {'username': 'foo', 'password': 'barユ£'}),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)\n\n    def test_digest_auth_non_ascii(self):\n        response = self.fetch('/digest_non_ascii', auth_mode='digest',\n                              auth_username='foo', auth_password='barユ£')\n        self.assertEqual(response.body, b'ok')",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 150,
                            "end_lineno": 153,
                            "parent": {
                                "file_name": "tornado/test/curl_httpclient_test.py",
                                "type": "class",
                                "name": "CurlHTTPClientTestCase",
                                "content": "class CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler, {'username': 'foo', 'password': 'bar'}),\n            ('/digest_non_ascii', DigestAuthHandler, {'username': 'foo', 'password': 'barユ£'}),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)\n\n    def test_digest_auth_non_ascii(self):\n        response = self.fetch('/digest_non_ascii', auth_mode='digest',\n                              auth_username='foo', auth_password='barユ£')\n        self.assertEqual(response.body, b'ok')",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 86,
                                "end_lineno": 153,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "cc3ca857a0d5d92309c655a37a60160abc51679b^",
    "new_commit_hash": "cc3ca857a0d5d92309c655a37a60160abc51679b",
    "commit_message": "Fix non-ascii characters support in curl_httpclient username and password",
    "commit_date": "2018-05-20T16:23:03-04:00",
    "metadata": {}
}
{
    "repo_name": "tornado",
    "new_commit_hash": "cc3ca857a0d5d92309c655a37a60160abc51679b",
    "test_file_codes": [
        "# coding: utf-8\nfrom __future__ import absolute_import, division, print_function\n\nfrom hashlib import md5\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest, HTTPClientError\nfrom tornado.locks import Event\nfrom tornado.stack_context import ExceptionStackContext\nfrom tornado.testing import AsyncHTTPTestCase, gen_test\nfrom tornado.test import httpclient_test\nfrom tornado.test.util import unittest, ignore_deprecation\nfrom tornado.web import Application, RequestHandler\n\n\ntry:\n    import pycurl  # type: ignore\nexcept ImportError:\n    pycurl = None\n\nif pycurl is not None:\n    from tornado.curl_httpclient import CurlAsyncHTTPClient\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientCommonTestCase(httpclient_test.HTTPClientCommonTestCase):\n    def get_http_client(self):\n        client = CurlAsyncHTTPClient(defaults=dict(allow_ipv6=False))\n        # make sure AsyncHTTPClient magic doesn't give us the wrong class\n        self.assertTrue(isinstance(client, CurlAsyncHTTPClient))\n        return client\n\n\nclass DigestAuthHandler(RequestHandler):\n    def initialize(self, username, password):\n        self.username = username\n        self.password = password\n\n    def get(self):\n        realm = 'test'\n        opaque = 'asdf'\n        # Real implementations would use a random nonce.\n        nonce = \"1234\"\n\n        auth_header = self.request.headers.get('Authorization', None)\n        if auth_header is not None:\n            auth_mode, params = auth_header.split(' ', 1)\n            assert auth_mode == 'Digest'\n            param_dict = {}\n            for pair in params.split(','):\n                k, v = pair.strip().split('=', 1)\n                if v[0] == '\"' and v[-1] == '\"':\n                    v = v[1:-1]\n                param_dict[k] = v\n            assert param_dict['realm'] == realm\n            assert param_dict['opaque'] == opaque\n            assert param_dict['nonce'] == nonce\n            assert param_dict['username'] == self.username\n            assert param_dict['uri'] == self.request.path\n            h1 = md5(utf8('%s:%s:%s' % (self.username, realm, self.password))).hexdigest()\n            h2 = md5(utf8('%s:%s' % (self.request.method,\n                                     self.request.path))).hexdigest()\n            digest = md5(utf8('%s:%s:%s' % (h1, nonce, h2))).hexdigest()\n            if digest == param_dict['response']:\n                self.write('ok')\n            else:\n                self.write('fail')\n        else:\n            self.set_status(401)\n            self.set_header('WWW-Authenticate',\n                            'Digest realm=\"%s\", nonce=\"%s\", opaque=\"%s\"' %\n                            (realm, nonce, opaque))\n\n\nclass CustomReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(200, \"Custom reason\")\n\n\nclass CustomFailReasonHandler(RequestHandler):\n    def get(self):\n        self.set_status(400, \"Custom reason\")\n\n\n@unittest.skipIf(pycurl is None, \"pycurl module not present\")\nclass CurlHTTPClientTestCase(AsyncHTTPTestCase):\n    def setUp(self):\n        super(CurlHTTPClientTestCase, self).setUp()\n        self.http_client = self.create_client()\n\n    def get_app(self):\n        return Application([\n            ('/digest', DigestAuthHandler, {'username': 'foo', 'password': 'bar'}),\n            ('/digest_non_ascii', DigestAuthHandler, {'username': 'foo', 'password': 'barユ£'}),\n            ('/custom_reason', CustomReasonHandler),\n            ('/custom_fail_reason', CustomFailReasonHandler),\n        ])\n\n    def create_client(self, **kwargs):\n        return CurlAsyncHTTPClient(force_instance=True,\n                                   defaults=dict(allow_ipv6=False),\n                                   **kwargs)\n\n    @gen_test\n    def test_prepare_curl_callback_stack_context(self):\n        exc_info = []\n        error_event = Event()\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            error_event.set()\n            return True\n\n        with ignore_deprecation():\n            with ExceptionStackContext(error_handler):\n                request = HTTPRequest(self.get_url('/custom_reason'),\n                                      prepare_curl_callback=lambda curl: 1 / 0)\n        yield [error_event.wait(), self.http_client.fetch(request)]\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_digest_auth(self):\n        response = self.fetch('/digest', auth_mode='digest',\n                              auth_username='foo', auth_password='bar')\n        self.assertEqual(response.body, b'ok')\n\n    def test_custom_reason(self):\n        response = self.fetch('/custom_reason')\n        self.assertEqual(response.reason, \"Custom reason\")\n\n    def test_fail_custom_reason(self):\n        response = self.fetch('/custom_fail_reason')\n        self.assertEqual(str(response.error), \"HTTP 400: Custom reason\")\n\n    def test_failed_setup(self):\n        self.http_client = self.create_client(max_clients=1)\n        for i in range(5):\n            with ignore_deprecation():\n                response = self.fetch(u'/ユニコード')\n            self.assertIsNot(response.error, None)\n\n            with self.assertRaises((UnicodeEncodeError, HTTPClientError)):\n                # This raises UnicodeDecodeError on py3 and\n                # HTTPClientError(404) on py2. The main motivation of\n                # this test is to ensure that the UnicodeEncodeError\n                # during the setup phase doesn't lead the request to\n                # be dropped on the floor.\n                response = self.fetch(u'/ユニコード', raise_error=True)\n\n    def test_digest_auth_non_ascii(self):\n        response = self.fetch('/digest_non_ascii', auth_mode='digest',\n                              auth_username='foo', auth_password='barユ£')\n        self.assertEqual(response.body, b'ok')\n",
        "#!/usr/bin/env python\nimport unittest\nimport sys\nimport time\nimport traceback\nimport platform\nimport os\n\n\nclass PytestLikeResult(unittest.TestResult):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.start_time = None\n        # Store (test, outcome, info) for each test:\n        #   outcome ∈ { \"passed\", \"failed\", \"error\", \"skipped\" }\n        #   info can hold the exception or skip reason, etc.\n        self.results = []\n\n    def startTestRun(self):\n        \"\"\"Called once before all tests.\"\"\"\n        self.start_time = time.time()\n        # Print a header similar to pytest\n        print(\"=\" * 29 + \" test session starts \" + \"=\" * 29)\n        # You can customize the platform/versions/paths as you like:\n        print(\n            f\"platform {platform.system().lower()} -- Python {platform.python_version()}\"\n        )\n        print(f\"rootdir: {os.path.abspath(os.curdir)}\")\n\n    def stopTestRun(self):\n        \"\"\"Called once after all tests are done.\"\"\"\n        elapsed = time.time() - self.start_time\n        print()  # blank line after final dot\n\n        # Summarize\n        ntests = len(self.results)\n        print(f\"collected {ntests} items\")\n\n        # Show details of failures/errors\n        failures = [r for r in self.results if r[1] == \"failed\"]\n        errors = [r for r in self.results if r[1] == \"error\"]\n\n        if failures:\n            print(\"\\n\" + \"=\" * 35 + \" FAILURES \" + \"=\" * 35)\n            for test, outcome, err_info in failures:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n        if errors:\n            print(\"\\n\" + \"=\" * 35 + \" ERRORS \" + \"=\" * 37)\n            for test, outcome, err_info in errors:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n\n        # Optional short summary lines for passes/fails/skips\n        print(\"\\n==================== short test summary info ====================\")\n        for test, outcome, info in self.results:\n            if outcome in (\"passed\", \"failed\", \"error\", \"skipped\"):\n                print(f\"{outcome.upper()} {test}\")\n\n        # Final one-line summary: “1 failed, 1 errors, 10 passed in 2.04s”\n        passed_count = sum(1 for r in self.results if r[1] == \"passed\")\n        failed_count = len(failures)\n        error_count = len(errors)\n        skipped_count = sum(1 for r in self.results if r[1] == \"skipped\")\n\n        summary_bits = []\n        if failed_count:\n            summary_bits.append(f\"{failed_count} failed\")\n        if error_count:\n            summary_bits.append(f\"{error_count} error{'s' if error_count != 1 else ''}\")\n        if passed_count:\n            summary_bits.append(f\"{passed_count} passed\")\n        if skipped_count:\n            summary_bits.append(f\"{skipped_count} skipped\")\n\n        summary_str = \", \".join(summary_bits) if summary_bits else \"no tests run\"\n        print(\n            f\"=================== {summary_str} in {elapsed:.2f}s ===================\"\n        )\n\n    def startTest(self, test):\n        \"\"\"Called right before each test method.\"\"\"\n        super().startTest(test)\n        # Print a dot or some indicator\n        print(\".\", end=\"\", flush=True)\n\n    def addSuccess(self, test):\n        super().addSuccess(test)\n        self.results.append((self._test_id(test), \"passed\", None))\n\n    def addFailure(self, test, err):\n        super().addFailure(test, err)\n        self.results.append((self._test_id(test), \"failed\", err))\n\n    def addError(self, test, err):\n        super().addError(test, err)\n        self.results.append((self._test_id(test), \"error\", err))\n\n    def addSkip(self, test, reason):\n        super().addSkip(test, reason)\n        self.results.append((self._test_id(test), \"skipped\", reason))\n\n    def _print_traceback(self, err_info):\n        \"\"\"Helper to print the traceback like pytest does.\"\"\"\n        if isinstance(err_info, tuple) and len(err_info) == 3:\n            exc_type, exc_value, tb = err_info\n            traceback.print_exception(exc_type, exc_value, tb, file=sys.stdout)\n        else:\n            print(str(err_info))\n\n    def _test_id(self, test):\n        try:\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}::{test._testMethodName}\"\n        except AttributeError:\n            # Fallback for _ErrorHolder objects\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}\"\n\n\nclass PytestLikeRunner(unittest.TextTestRunner):\n    \"\"\"A custom TextTestRunner that uses the PytestLikeResult.\"\"\"\n\n    resultclass = PytestLikeResult\n\n    def run(self, test):\n        result = self._makeResult()\n        # Overriding run to ensure we call startTestRun/stopTestRun\n        result.startTestRun()\n        test(result)\n        result.stopTestRun()\n        return result\n\n    def tearDown(self):\n        # Reset signal handlers\n        from twisted.internet import reactor\n\n        reactor.removeAll()\n        reactor.stop()\n        super().tearDown()\n\n\ndef main():\n    loader = unittest.TestLoader()\n    suite = loader.discover(\".\", pattern=\"test_*.py\")  # discover tests in r2e_tests/\n    runner = PytestLikeRunner(verbosity=0)\n    result = runner.run(suite)\n    # Exit with code 0 if all tests passed/skipped, 1 if there were fails/errors\n    sys.exit(not result.wasSuccessful())\n\n\nif __name__ == \"__main__\":\n    main()\n"
    ],
    "test_file_names": [
        "test_1.py",
        "tornado_unittest_runner.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 1 package in 841ms\nPrepared 1 package in 1.14s\nInstalled 1 package in 1ms\n + tornado==5.1.dev1 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_cc3ca857a0d5d92309c655a37a60160abc51679b)\nResolved 29 packages in 11ms\nInstalled 29 packages in 100ms\n + attrs==24.3.0\n + automat==24.8.1\n + cachetools==5.5.0\n + cffi==1.17.1\n + chardet==5.2.0\n + colorama==0.4.6\n + constantly==23.10.4\n + distlib==0.3.9\n + exceptiongroup==1.2.2\n + filelock==3.16.1\n + hyperlink==21.0.0\n + idna==3.10\n + incremental==24.7.2\n + iniconfig==2.0.0\n + packaging==24.2\n + platformdirs==4.3.6\n + pluggy==1.5.0\n + pycares==4.5.0\n + pycparser==2.22\n + pycurl==7.45.4\n + pyproject-api==1.8.0\n + pytest==8.3.4\n + setuptools==75.6.0\n + tomli==2.2.1\n + tox==4.23.2\n + twisted==24.11.0\n + typing-extensions==4.12.2\n + virtualenv==20.28.0\n + zope-interface==7.2\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_cc3ca857a0d5d92309c655a37a60160abc51679b\n...................................\ncollected 35 items\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_304_with_content_length\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_all_methods\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth_explicit_mode\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_encoding\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_sanity_checks\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked_close\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_configure_defaults\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_credentials_in_url\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_final_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_follow_redirect\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error_no_raise\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_interface\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_hello_world\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_multi_line_headers\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_non_ascii_header\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_patch_receives_payload\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_post\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_put_307\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_reuse_request_from_response\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_unsupported_auth_mode\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_digest_auth\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_digest_auth_non_ascii\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_fail_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_failed_setup\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_prepare_curl_callback_stack_context\n=================== 35 passed in 0.20s ===================\n",
    "new_commit_res_stderr": "WARNING:tornado.access:405 HEAD /all_methods (127.0.0.1) 0.28ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.21ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.19ms\nWARNING:tornado.access:401 GET /digest (127.0.0.1) 0.29ms\nWARNING:tornado.access:401 GET /digest_non_ascii (127.0.0.1) 0.25ms\nWARNING:tornado.access:400 GET /custom_fail_reason (127.0.0.1) 0.24ms\n",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_cc3ca857a0d5d92309c655a37a60160abc51679b\n...................................\ncollected 35 items\n\n=================================== FAILURES ===================================\n__________ CurlHTTPClientTestCase.test_digest_auth_non_ascii __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_cc3ca857a0d5d92309c655a37a60160abc51679b/tornado/testing.py\", line 125, in __call__\n    result = self.orig_method(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_cc3ca857a0d5d92309c655a37a60160abc51679b/r2e_tests/test_1.py\", line 153, in test_digest_auth_non_ascii\n    self.assertEqual(response.body, b'ok')\nAssertionError: None != b'ok'\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_304_with_content_length\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_all_methods\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_basic_auth_explicit_mode\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_encoding\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_body_sanity_checks\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_chunked_close\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_configure_defaults\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_credentials_in_url\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_final_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_follow_redirect\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_http_error_no_raise\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_future_interface\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_callback_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_header_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_hello_world\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_multi_line_headers\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_non_ascii_header\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_patch_receives_payload\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_post\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_put_307\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_reuse_request_from_response\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_callback\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_streaming_stack_context\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_types\nPASSED r2e_tests.test_1::CurlHTTPClientCommonTestCase::test_unsupported_auth_mode\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_digest_auth\nFAILED r2e_tests.test_1::CurlHTTPClientTestCase::test_digest_auth_non_ascii\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_fail_custom_reason\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_failed_setup\nPASSED r2e_tests.test_1::CurlHTTPClientTestCase::test_prepare_curl_callback_stack_context\n=================== 1 failed, 34 passed in 0.15s ===================\n",
    "old_commit_res_stderr": "WARNING:tornado.access:405 HEAD /all_methods (127.0.0.1) 0.17ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.20ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.19ms\nWARNING:tornado.access:401 GET /digest (127.0.0.1) 0.17ms\nWARNING:tornado.access:400 GET /custom_fail_reason (127.0.0.1) 0.16ms\n"
}
tornado/curl_httpclient.py tornado/test/curl_httpclient_test.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** CurlAsyncHTTPClient Fails to Handle Non-ASCII Characters in Username and Password

**Description:**
When using non-ASCII characters in the username or password with `CurlAsyncHTTPClient`, authentication fails unexpectedly.

**Example Code:**
```python
request = HTTPRequest(
    url='http://example.com/protected',
    auth_mode='digest',
    auth_username='foo',
    auth_password='barユ£'
)
response = http_client.fetch(request)
print(response.body)  # Expected: b'ok', Actual: None
```

**Expected Behavior:**
The client should correctly handle non-ASCII characters in the username and password, allowing successful authentication and receiving the expected response body (`b'ok'`).

**Actual Behavior:**
Using non-ASCII characters in the credentials results in authentication failure, and the response body is `None` instead of the expected `b'ok'`.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### tornado/curl_httpclient.py
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Non-blocking HTTP client implementation using pycurl."""

from __future__ import absolute_import, division, print_function

import collections
import functools
import logging
import pycurl  # type: ignore
import threading
import time
from io import BytesIO

from tornado import httputil
from tornado import ioloop
from tornado import stack_context

from tornado.escape import utf8, native_str
from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main

curl_log = logging.getLogger('tornado.curl_httpclient')


class CurlAsyncHTTPClient(AsyncHTTPClient):
    def initialize(self, max_clients=10, defaults=None):
        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)
        self._multi = pycurl.CurlMulti()
        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
        self._curls = [self._curl_create() for i in range(max_clients)]
        self._free_list = self._curls[:]
        self._requests = collections.deque()
        self._fds = {}
        self._timeout = None

        # libcurl has bugs that sometimes cause it to not report all
        # relevant file descriptors and timeouts to TIMERFUNCTION/
        # SOCKETFUNCTION.  Mitigate the effects of such bugs by
        # forcing a periodic scan of all active requests.
        self._force_timeout_callback = ioloop.PeriodicCallback(
            self._handle_force_timeout, 1000)
        self._force_timeout_callback.start()

        # Work around a bug in libcurl 7.29.0: Some fields in the curl
        # multi object are initialized lazily, and its destructor will
        # segfault if it is destroyed without having been used.  Add
        # and remove a dummy handle to make sure everything is
        # initialized.
        dummy_curl_handle = pycurl.Curl()
        self._multi.add_handle(dummy_curl_handle)
        self._multi.remove_handle(dummy_curl_handle)

    def close(self):
        self._force_timeout_callback.stop()
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        for curl in self._curls:
            curl.close()
        self._multi.close()
        super(CurlAsyncHTTPClient, self).close()

        # Set below properties to None to reduce the reference count of current
        # instance, because those properties hold some methods of current
        # instance that will case circular reference.
        self._force_timeout_callback = None
        self._multi = None

    def fetch_impl(self, request, callback):
        self._requests.append((request, callback))
        self._process_queue()
        self._set_timeout(0)

    def _handle_socket(self, event, fd, multi, data):
        """Called by libcurl when it wants to change the file descriptors
        it cares about.
        """
        event_map = {
            pycurl.POLL_NONE: ioloop.IOLoop.NONE,
            pycurl.POLL_IN: ioloop.IOLoop.READ,
            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,
            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE
        }
        if event == pycurl.POLL_REMOVE:
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
                del self._fds[fd]
        else:
            ioloop_event = event_map[event]
            # libcurl sometimes closes a socket and then opens a new
            # one using the same FD without giving us a POLL_NONE in
            # between.  This is a problem with the epoll IOLoop,
            # because the kernel can tell when a socket is closed and
            # removes it from the epoll automatically, causing future
            # update_handler calls to fail.  Since we can't tell when
            # this has happened, always use remove and re-add
            # instead of update.
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
            self.io_loop.add_handler(fd, self._handle_events,
                                     ioloop_event)
            self._fds[fd] = ioloop_event

    def _set_timeout(self, msecs):
        """Called by libcurl to schedule a timeout."""
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        self._timeout = self.io_loop.add_timeout(
            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)

    def _handle_events(self, fd, events):
        """Called by IOLoop when there is activity on one of our
        file descriptors.
        """
        action = 0
        if events & ioloop.IOLoop.READ:
            action |= pycurl.CSELECT_IN
        if events & ioloop.IOLoop.WRITE:
            action |= pycurl.CSELECT_OUT
        while True:
            try:
                ret, num_handles = self._multi.socket_action(fd, action)
            except pycurl.error as e:
                ret = e.args[0]
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break
        self._finish_pending_requests()

    def _handle_timeout(self):
        """Called by IOLoop when the requested timeout has passed."""
        with stack_context.NullContext():
            self._timeout = None
            while True:
                try:
                    ret, num_handles = self._multi.socket_action(
                        pycurl.SOCKET_TIMEOUT, 0)
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

        # In theory, we shouldn't have to do this because curl will
        # call _set_timeout whenever the timeout changes.  However,
        # sometimes after _handle_timeout we will need to reschedule
        # immediately even though nothing has changed from curl's
        # perspective.  This is because when socket_action is
        # called with SOCKET_TIMEOUT, libcurl decides internally which
        # timeouts need to be processed by using a monotonic clock
        # (where available) while tornado uses python's time.time()
        # to decide when timeouts have occurred.  When those clocks
        # disagree on elapsed time (as they will whenever there is an
        # NTP adjustment), tornado might call _handle_timeout before
        # libcurl is ready.  After each timeout, resync the scheduled
        # timeout with libcurl's current state.
        new_timeout = self._multi.timeout()
        if new_timeout >= 0:
            self._set_timeout(new_timeout)

    def _handle_force_timeout(self):
        """Called by IOLoop periodically to ask libcurl to process any
        events it may have forgotten about.
        """
        with stack_context.NullContext():
            while True:
                try:
                    ret, num_handles = self._multi.socket_all()
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

    def _finish_pending_requests(self):
        """Process any requests that were completed by the last
        call to multi.socket_action.
        """
        while True:
            num_q, ok_list, err_list = self._multi.info_read()
            for curl in ok_list:
                self._finish(curl)
            for curl, errnum, errmsg in err_list:
                self._finish(curl, errnum, errmsg)
            if num_q == 0:
                break
        self._process_queue()

    def _process_queue(self):
        with stack_context.NullContext():
            while True:
                started = 0
                while self._free_list and self._requests:
                    started += 1
                    curl = self._free_list.pop()
                    (request, callback) = self._requests.popleft()
                    curl.info = {
                        "headers": httputil.HTTPHeaders(),
                        "buffer": BytesIO(),
                        "request": request,
                        "callback": callback,
                        "curl_start_time": time.time(),
                    }
                    try:
                        self._curl_setup_request(
                            curl, request, curl.info["buffer"],
                            curl.info["headers"])
                    except Exception as e:
                        # If there was an error in setup, pass it on
                        # to the callback. Note that allowing the
                        # error to escape here will appear to work
                        # most of the time since we are still in the
                        # caller's original stack frame, but when
                        # _process_queue() is called from
                        # _finish_pending_requests the exceptions have
                        # nowhere to go.
                        self._free_list.append(curl)
                        callback(HTTPResponse(
                            request=request,
                            code=599,
                            error=e))
                    else:
                        self._multi.add_handle(curl)

                if not started:
                    break

    def _finish(self, curl, curl_error=None, curl_message=None):
        info = curl.info
        curl.info = None
        self._multi.remove_handle(curl)
        self._free_list.append(curl)
        buffer = info["buffer"]
        if curl_error:
            error = CurlError(curl_error, curl_message)
            code = error.code
            effective_url = None
            buffer.close()
            buffer = None
        else:
            error = None
            code = curl.getinfo(pycurl.HTTP_CODE)
            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)
            buffer.seek(0)
        # the various curl timings are documented at
        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
        time_info = dict(
            queue=info["curl_start_time"] - info["request"].start_time,
            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),
            connect=curl.getinfo(pycurl.CONNECT_TIME),
            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),
            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),
            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),
            total=curl.getinfo(pycurl.TOTAL_TIME),
            redirect=curl.getinfo(pycurl.REDIRECT_TIME),
        )
        try:
            info["callback"](HTTPResponse(
                request=info["request"], code=code, headers=info["headers"],
                buffer=buffer, effective_url=effective_url, error=error,
                reason=info['headers'].get("X-Http-Reason", None),
                request_time=time.time() - info["curl_start_time"],
                time_info=time_info))
        except Exception:
            self.handle_callback_exception(info["callback"])

    def handle_callback_exception(self, callback):
        self.io_loop.handle_callback_exception(callback)

    def _curl_create(self):
        curl = pycurl.Curl()
        if curl_log.isEnabledFor(logging.DEBUG):
            curl.setopt(pycurl.VERBOSE, 1)
            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
        return curl

    def _curl_setup_request(self, curl, request, buffer, headers):
        curl.setopt(pycurl.URL, native_str(request.url))

        # libcurl's magic "Expect: 100-continue" behavior causes delays
        # with servers that don't support it (which include, among others,
        # Google's OpenID endpoint).  Additionally, this behavior has
        # a bug in conjunction with the curl_multi_socket_action API
        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),
        # which increases the delays.  It's more trouble than it's worth,
        # so just turn off the feature (yes, setting Expect: to an empty
        # value is the official way to disable this)
        if "Expect" not in request.headers:
            request.headers["Expect"] = ""

        # libcurl adds Pragma: no-cache by default; disable that too
        if "Pragma" not in request.headers:
            request.headers["Pragma"] = ""

        curl.setopt(pycurl.HTTPHEADER,
                    ["%s: %s" % (native_str(k), native_str(v))
                     for k, v in request.headers.get_all()])

        curl.setopt(pycurl.HEADERFUNCTION,
                    functools.partial(self._curl_header_callback,
                                      headers, request.header_callback))
        if request.streaming_callback:
            def write_function(chunk):
                self.io_loop.add_callback(request.streaming_callback, chunk)
        else:
            write_function = buffer.write
        if bytes is str:  # py2
            curl.setopt(pycurl.WRITEFUNCTION, write_function)
        else:  # py3
            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes
            # a fork/port.  That version has a bug in which it passes unicode
            # strings instead of bytes to the WRITEFUNCTION.  This means that
            # if you use a WRITEFUNCTION (which tornado always does), you cannot
            # download arbitrary binary data.  This needs to be fixed in the
            # ported pycurl package, but in the meantime this lambda will
            # make it work for downloading (utf8) text.
            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))
        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))
        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))
        if request.user_agent:
            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))
        else:
            curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
        if request.network_interface:
            curl.setopt(pycurl.INTERFACE, request.network_interface)
        if request.decompress_response:
            curl.setopt(pycurl.ENCODING, "gzip,deflate")
        else:
            curl.setopt(pycurl.ENCODING, "none")
        if request.proxy_host and request.proxy_port:
            curl.setopt(pycurl.PROXY, request.proxy_host)
            curl.setopt(pycurl.PROXYPORT, request.proxy_port)
            if request.proxy_username:
                credentials = '%s:%s' % (request.proxy_username,
                                         request.proxy_password)
                curl.setopt(pycurl.PROXYUSERPWD, credentials)

            if (request.proxy_auth_mode is None or
                    request.proxy_auth_mode == "basic"):
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.proxy_auth_mode == "digest":
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError(
                    "Unsupported proxy_auth_mode %s" % request.proxy_auth_mode)
        else:
            curl.setopt(pycurl.PROXY, '')
            curl.unsetopt(pycurl.PROXYUSERPWD)
        if request.validate_cert:
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        else:
            curl.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        if request.ca_certs is not None:
            curl.setopt(pycurl.CAINFO, request.ca_certs)
        else:
            # There is no way to restore pycurl.CAINFO to its default value
            # (Using unsetopt makes it reject all certificates).
            # I don't see any way to read the default value from python so it
            # can be restored later.  We'll have to just leave CAINFO untouched
            # if no ca_certs file was specified, and require that if any
            # request uses a custom ca_certs file, they all must.
            pass

        if request.allow_ipv6 is False:
            # Curl behaves reasonably when DNS resolution gives an ipv6 address
            # that we can't reach, so allow ipv6 unless the user asks to disable.
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
        else:
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)

        # Set the request method through curl's irritating interface which makes
        # up names for almost every single method
        curl_options = {
            "GET": pycurl.HTTPGET,
            "POST": pycurl.POST,
            "PUT": pycurl.UPLOAD,
            "HEAD": pycurl.NOBODY,
        }
        custom_methods = set(["DELETE", "OPTIONS", "PATCH"])
        for o in curl_options.values():
            curl.setopt(o, False)
        if request.method in curl_options:
            curl.unsetopt(pycurl.CUSTOMREQUEST)
            curl.setopt(curl_options[request.method], True)
        elif request.allow_nonstandard_methods or request.method in custom_methods:
            curl.setopt(pycurl.CUSTOMREQUEST, request.method)
        else:
            raise KeyError('unknown method ' + request.method)

        body_expected = request.method in ("POST", "PATCH", "PUT")
        body_present = request.body is not None
        if not request.allow_nonstandard_methods:
            # Some HTTP methods nearly always have bodies while others
            # almost never do. Fail in this case unless the user has
            # opted out of sanity checks with allow_nonstandard_methods.
            if ((body_expected and not body_present) or
                    (body_present and not body_expected)):
                raise ValueError(
                    'Body must %sbe None for method %s (unless '
                    'allow_nonstandard_methods is true)' %
                    ('not ' if body_expected else '', request.method))

        if body_expected or body_present:
            if request.method == "GET":
                # Even with `allow_nonstandard_methods` we disallow
                # GET with a body (because libcurl doesn't allow it
                # unless we use CUSTOMREQUEST). While the spec doesn't
                # forbid clients from sending a body, it arguably
                # disallows the server from doing anything with them.
                raise ValueError('Body must be None for GET request')
            request_buffer = BytesIO(utf8(request.body or ''))

            def ioctl(cmd):
                if cmd == curl.IOCMD_RESTARTREAD:
                    request_buffer.seek(0)
            curl.setopt(pycurl.READFUNCTION, request_buffer.read)
            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
            if request.method == "POST":
                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))
            else:
                curl.setopt(pycurl.UPLOAD, True)
                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))

        if request.auth_username is not None:
            userpwd = "%s:%s" % (request.auth_username, request.auth_password or '')

            if request.auth_mode is None or request.auth_mode == "basic":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.auth_mode == "digest":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported auth_mode %s" % request.auth_mode)

            curl.setopt(pycurl.USERPWD, native_str(userpwd))
            curl_log.debug("%s %s (username: %r)", request.method, request.url,
                           request.auth_username)
        else:
            curl.unsetopt(pycurl.USERPWD)
            curl_log.debug("%s %s", request.method, request.url)

        if request.client_cert is not None:
            curl.setopt(pycurl.SSLCERT, request.client_cert)

        if request.client_key is not None:
            curl.setopt(pycurl.SSLKEY, request.client_key)

        if request.ssl_options is not None:
            raise ValueError("ssl_options not supported in curl_httpclient")

        if threading.activeCount() > 1:
            # libcurl/pycurl is not thread-safe by default.  When multiple threads
            # are used, signals should be disabled.  This has the side effect
            # of disabling DNS timeouts in some environments (when libcurl is
            # not linked against ares), so we don't do it when there is only one
            # thread.  Applications that use many short-lived threads may need
            # to set NOSIGNAL manually in a prepare_curl_callback since
            # there may not be any other threads running at the time we call
            # threading.activeCount.
            curl.setopt(pycurl.NOSIGNAL, 1)
        if request.prepare_curl_callback is not None:
            request.prepare_curl_callback(curl)

    def _curl_header_callback(self, headers, header_callback, header_line):
        header_line = native_str(header_line.decode('latin1'))
        if header_callback is not None:
            self.io_loop.add_callback(header_callback, header_line)
        # header_line as returned by curl includes the end-of-line characters.
        # whitespace at the start should be preserved to allow multi-line headers
        header_line = header_line.rstrip()
        if header_line.startswith("HTTP/"):
            headers.clear()
            try:
                (__, __, reason) = httputil.parse_response_start_line(header_line)
                header_line = "X-Http-Reason: %s" % reason
            except httputil.HTTPInputError:
                return
        if not header_line:
            return
        headers.parse_line(header_line)

    def _curl_debug(self, debug_type, debug_msg):
        debug_types = ('I', '<', '>', '<', '>')
        if debug_type == 0:
            debug_msg = native_str(debug_msg)
            curl_log.debug('%s', debug_msg.strip())
        elif debug_type in (1, 2):
            debug_msg = native_str(debug_msg)
            for line in debug_msg.splitlines():
                curl_log.debug('%s %s', debug_types[debug_type], line)
        elif debug_type == 4:
            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)


class CurlError(HTTPError):
    def __init__(self, errno, message):
        HTTPError.__init__(self, 599, message)
        self.errno = errno


if __name__ == "__main__":
    AsyncHTTPClient.configure(CurlAsyncHTTPClient)
    main()

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** CurlAsyncHTTPClient Fails to Handle Non-ASCII Characters in Username and Password

**Description:**
When using non-ASCII characters in the username or password with `CurlAsyncHTTPClient`, authentication fails unexpectedly.

**Example Code:**
```python
request = HTTPRequest(
    url='http://example.com/protected',
    auth_mode='digest',
    auth_username='foo',
    auth_password='barユ£'
)
response = http_client.fetch(request)
print(response.body)  # Expected: b'ok', Actual: None
```

**Expected Behavior:**
The client should correctly handle non-ASCII characters in the username and password, allowing successful authentication and receiving the expected response body (`b'ok'`).

**Actual Behavior:**
Using non-ASCII characters in the credentials results in authentication failure, and the response body is `None` instead of the expected `b'ok'`.
[/ISSUE]
{
    "CurlHTTPClientCommonTestCase.test_304_with_content_length": "PASSED",
    "CurlHTTPClientCommonTestCase.test_all_methods": "PASSED",
    "CurlHTTPClientCommonTestCase.test_basic_auth": "PASSED",
    "CurlHTTPClientCommonTestCase.test_basic_auth_explicit_mode": "PASSED",
    "CurlHTTPClientCommonTestCase.test_body_encoding": "PASSED",
    "CurlHTTPClientCommonTestCase.test_body_sanity_checks": "PASSED",
    "CurlHTTPClientCommonTestCase.test_chunked": "PASSED",
    "CurlHTTPClientCommonTestCase.test_chunked_close": "PASSED",
    "CurlHTTPClientCommonTestCase.test_configure_defaults": "PASSED",
    "CurlHTTPClientCommonTestCase.test_credentials_in_url": "PASSED",
    "CurlHTTPClientCommonTestCase.test_final_callback_stack_context": "PASSED",
    "CurlHTTPClientCommonTestCase.test_follow_redirect": "PASSED",
    "CurlHTTPClientCommonTestCase.test_future_http_error": "PASSED",
    "CurlHTTPClientCommonTestCase.test_future_http_error_no_raise": "PASSED",
    "CurlHTTPClientCommonTestCase.test_future_interface": "PASSED",
    "CurlHTTPClientCommonTestCase.test_header_callback": "PASSED",
    "CurlHTTPClientCommonTestCase.test_header_callback_stack_context": "PASSED",
    "CurlHTTPClientCommonTestCase.test_header_types": "PASSED",
    "CurlHTTPClientCommonTestCase.test_hello_world": "PASSED",
    "CurlHTTPClientCommonTestCase.test_multi_line_headers": "PASSED",
    "CurlHTTPClientCommonTestCase.test_non_ascii_header": "PASSED",
    "CurlHTTPClientCommonTestCase.test_patch_receives_payload": "PASSED",
    "CurlHTTPClientCommonTestCase.test_post": "PASSED",
    "CurlHTTPClientCommonTestCase.test_put_307": "PASSED",
    "CurlHTTPClientCommonTestCase.test_reuse_request_from_response": "PASSED",
    "CurlHTTPClientCommonTestCase.test_streaming_callback": "PASSED",
    "CurlHTTPClientCommonTestCase.test_streaming_stack_context": "PASSED",
    "CurlHTTPClientCommonTestCase.test_types": "PASSED",
    "CurlHTTPClientCommonTestCase.test_unsupported_auth_mode": "PASSED",
    "CurlHTTPClientTestCase.test_custom_reason": "PASSED",
    "CurlHTTPClientTestCase.test_digest_auth": "PASSED",
    "CurlHTTPClientTestCase.test_digest_auth_non_ascii": "PASSED",
    "CurlHTTPClientTestCase.test_fail_custom_reason": "PASSED",
    "CurlHTTPClientTestCase.test_failed_setup": "PASSED",
    "CurlHTTPClientTestCase.test_prepare_curl_callback_stack_context": "PASSED"
}
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index 54fc5b36..c4ddbbbb 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -450,7 +450,7 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
             else:
                 raise ValueError("Unsupported auth_mode %s" % request.auth_mode)
 
-            curl.setopt(pycurl.USERPWD, native_str(userpwd))
+            curl.setopt(pycurl.USERPWD, utf8(userpwd))
             curl_log.debug("%s %s (username: %r)", request.method, request.url,
                            request.auth_username)
         else:

{"tornado/curl_httpclient.py": "#\n# Copyright 2009 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function\n\nimport collections\nimport functools\nimport logging\nimport pycurl  # type: ignore\nimport threading\nimport time\nfrom io import BytesIO\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\n\ncurl_log = logging.getLogger('tornado.curl_httpclient')\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [self._curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        super(CurlAsyncHTTPClient, self).close()\n\n        # Set below properties to None to reduce the reference count of current\n        # instance, because those properties hold some methods of current\n        # instance that will case circular reference.\n        self._force_timeout_callback = None\n        self._multi = None\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._multi.socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    try:\n                        self._curl_setup_request(\n                            curl, request, curl.info[\"buffer\"],\n                            curl.info[\"headers\"])\n                    except Exception as e:\n                        # If there was an error in setup, pass it on\n                        # to the callback. Note that allowing the\n                        # error to escape here will appear to work\n                        # most of the time since we are still in the\n                        # caller's original stack frame, but when\n                        # _process_queue() is called from\n                        # _finish_pending_requests the exceptions have\n                        # nowhere to go.\n                        self._free_list.append(curl)\n                        callback(HTTPResponse(\n                            request=request,\n                            code=599,\n                            error=e))\n                    else:\n                        self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                reason=info['headers'].get(\"X-Http-Reason\", None),\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n    def _curl_create(self):\n        curl = pycurl.Curl()\n        if curl_log.isEnabledFor(logging.DEBUG):\n            curl.setopt(pycurl.VERBOSE, 1)\n            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)\n        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)\n            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n        return curl\n\n    def _curl_setup_request(self, curl, request, buffer, headers):\n        curl.setopt(pycurl.URL, native_str(request.url))\n\n        # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n        # with servers that don't support it (which include, among others,\n        # Google's OpenID endpoint).  Additionally, this behavior has\n        # a bug in conjunction with the curl_multi_socket_action API\n        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n        # which increases the delays.  It's more trouble than it's worth,\n        # so just turn off the feature (yes, setting Expect: to an empty\n        # value is the official way to disable this)\n        if \"Expect\" not in request.headers:\n            request.headers[\"Expect\"] = \"\"\n\n        # libcurl adds Pragma: no-cache by default; disable that too\n        if \"Pragma\" not in request.headers:\n            request.headers[\"Pragma\"] = \"\"\n\n        curl.setopt(pycurl.HTTPHEADER,\n                    [\"%s: %s\" % (native_str(k), native_str(v))\n                     for k, v in request.headers.get_all()])\n\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    functools.partial(self._curl_header_callback,\n                                      headers, request.header_callback))\n        if request.streaming_callback:\n            def write_function(chunk):\n                self.io_loop.add_callback(request.streaming_callback, chunk)\n        else:\n            write_function = buffer.write\n        if bytes is str:  # py2\n            curl.setopt(pycurl.WRITEFUNCTION, write_function)\n        else:  # py3\n            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n            # a fork/port.  That version has a bug in which it passes unicode\n            # strings instead of bytes to the WRITEFUNCTION.  This means that\n            # if you use a WRITEFUNCTION (which tornado always does), you cannot\n            # download arbitrary binary data.  This needs to be fixed in the\n            # ported pycurl package, but in the meantime this lambda will\n            # make it work for downloading (utf8) text.\n            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n        if request.user_agent:\n            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n        else:\n            curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n        if request.network_interface:\n            curl.setopt(pycurl.INTERFACE, request.network_interface)\n        if request.decompress_response:\n            curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n        else:\n            curl.setopt(pycurl.ENCODING, \"none\")\n        if request.proxy_host and request.proxy_port:\n            curl.setopt(pycurl.PROXY, request.proxy_host)\n            curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n            if request.proxy_username:\n                credentials = '%s:%s' % (request.proxy_username,\n                                         request.proxy_password)\n                curl.setopt(pycurl.PROXYUSERPWD, credentials)\n\n            if (request.proxy_auth_mode is None or\n                    request.proxy_auth_mode == \"basic\"):\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.proxy_auth_mode == \"digest\":\n                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\n                    \"Unsupported proxy_auth_mode %s\" % request.proxy_auth_mode)\n        else:\n            curl.setopt(pycurl.PROXY, '')\n            curl.unsetopt(pycurl.PROXYUSERPWD)\n        if request.validate_cert:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n        else:\n            curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n            curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n        if request.ca_certs is not None:\n            curl.setopt(pycurl.CAINFO, request.ca_certs)\n        else:\n            # There is no way to restore pycurl.CAINFO to its default value\n            # (Using unsetopt makes it reject all certificates).\n            # I don't see any way to read the default value from python so it\n            # can be restored later.  We'll have to just leave CAINFO untouched\n            # if no ca_certs file was specified, and require that if any\n            # request uses a custom ca_certs file, they all must.\n            pass\n\n        if request.allow_ipv6 is False:\n            # Curl behaves reasonably when DNS resolution gives an ipv6 address\n            # that we can't reach, so allow ipv6 unless the user asks to disable.\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n        else:\n            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n        # Set the request method through curl's irritating interface which makes\n        # up names for almost every single method\n        curl_options = {\n            \"GET\": pycurl.HTTPGET,\n            \"POST\": pycurl.POST,\n            \"PUT\": pycurl.UPLOAD,\n            \"HEAD\": pycurl.NOBODY,\n        }\n        custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n        for o in curl_options.values():\n            curl.setopt(o, False)\n        if request.method in curl_options:\n            curl.unsetopt(pycurl.CUSTOMREQUEST)\n            curl.setopt(curl_options[request.method], True)\n        elif request.allow_nonstandard_methods or request.method in custom_methods:\n            curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n        else:\n            raise KeyError('unknown method ' + request.method)\n\n        body_expected = request.method in (\"POST\", \"PATCH\", \"PUT\")\n        body_present = request.body is not None\n        if not request.allow_nonstandard_methods:\n            # Some HTTP methods nearly always have bodies while others\n            # almost never do. Fail in this case unless the user has\n            # opted out of sanity checks with allow_nonstandard_methods.\n            if ((body_expected and not body_present) or\n                    (body_present and not body_expected)):\n                raise ValueError(\n                    'Body must %sbe None for method %s (unless '\n                    'allow_nonstandard_methods is true)' %\n                    ('not ' if body_expected else '', request.method))\n\n        if body_expected or body_present:\n            if request.method == \"GET\":\n                # Even with `allow_nonstandard_methods` we disallow\n                # GET with a body (because libcurl doesn't allow it\n                # unless we use CUSTOMREQUEST). While the spec doesn't\n                # forbid clients from sending a body, it arguably\n                # disallows the server from doing anything with them.\n                raise ValueError('Body must be None for GET request')\n            request_buffer = BytesIO(utf8(request.body or ''))\n\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            if request.method == \"POST\":\n                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))\n            else:\n                curl.setopt(pycurl.UPLOAD, True)\n                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))\n\n        if request.auth_username is not None:\n            userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n            if request.auth_mode is None or request.auth_mode == \"basic\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n            elif request.auth_mode == \"digest\":\n                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n            else:\n                raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n            curl.setopt(pycurl.USERPWD, native_str(userpwd))\n            curl_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                           request.auth_username)\n        else:\n            curl.unsetopt(pycurl.USERPWD)\n            curl_log.debug(\"%s %s\", request.method, request.url)\n\n        if request.client_cert is not None:\n            curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n        if request.client_key is not None:\n            curl.setopt(pycurl.SSLKEY, request.client_key)\n\n        if request.ssl_options is not None:\n            raise ValueError(\"ssl_options not supported in curl_httpclient\")\n\n        if threading.activeCount() > 1:\n            # libcurl/pycurl is not thread-safe by default.  When multiple threads\n            # are used, signals should be disabled.  This has the side effect\n            # of disabling DNS timeouts in some environments (when libcurl is\n            # not linked against ares), so we don't do it when there is only one\n            # thread.  Applications that use many short-lived threads may need\n            # to set NOSIGNAL manually in a prepare_curl_callback since\n            # there may not be any other threads running at the time we call\n            # threading.activeCount.\n            curl.setopt(pycurl.NOSIGNAL, 1)\n        if request.prepare_curl_callback is not None:\n            request.prepare_curl_callback(curl)\n\n    def _curl_header_callback(self, headers, header_callback, header_line):\n        header_line = native_str(header_line.decode('latin1'))\n        if header_callback is not None:\n            self.io_loop.add_callback(header_callback, header_line)\n        # header_line as returned by curl includes the end-of-line characters.\n        # whitespace at the start should be preserved to allow multi-line headers\n        header_line = header_line.rstrip()\n        if header_line.startswith(\"HTTP/\"):\n            headers.clear()\n            try:\n                (__, __, reason) = httputil.parse_response_start_line(header_line)\n                header_line = \"X-Http-Reason: %s\" % reason\n            except httputil.HTTPInputError:\n                return\n        if not header_line:\n            return\n        headers.parse_line(header_line)\n\n    def _curl_debug(self, debug_type, debug_msg):\n        debug_types = ('I', '<', '>', '<', '>')\n        if debug_type == 0:\n            debug_msg = native_str(debug_msg)\n            curl_log.debug('%s', debug_msg.strip())\n        elif debug_type in (1, 2):\n            debug_msg = native_str(debug_msg)\n            for line in debug_msg.splitlines():\n                curl_log.debug('%s %s', debug_types[debug_type], line)\n        elif debug_type == 4:\n            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** CurlAsyncHTTPClient Fails to Handle Non-ASCII Characters in Username and Password

**Description:**
When using non-ASCII characters in the username or password with `CurlAsyncHTTPClient`, authentication fails unexpectedly.

**Example Code:**
```python
request = HTTPRequest(
    url='http://example.com/protected',
    auth_mode='digest',
    auth_username='foo',
    auth_password='barユ£'
)
response = http_client.fetch(request)
print(response.body)  # Expected: b'ok', Actual: None
```

**Expected Behavior:**
The client should correctly handle non-ASCII characters in the username and password, allowing successful authentication and receiving the expected response body (`b'ok'`).

**Actual Behavior:**
Using non-ASCII characters in the credentials results in authentication failure, and the response body is `None` instead of the expected `b'ok'`.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### tornado/curl_httpclient.py
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Non-blocking HTTP client implementation using pycurl."""

from __future__ import absolute_import, division, print_function

import collections
import functools
import logging
import pycurl  # type: ignore
import threading
import time
from io import BytesIO

from tornado import httputil
from tornado import ioloop
from tornado import stack_context

from tornado.escape import utf8, native_str
from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main

curl_log = logging.getLogger('tornado.curl_httpclient')


class CurlAsyncHTTPClient(AsyncHTTPClient):
    def initialize(self, max_clients=10, defaults=None):
        super(CurlAsyncHTTPClient, self).initialize(defaults=defaults)
        self._multi = pycurl.CurlMulti()
        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
        self._curls = [self._curl_create() for i in range(max_clients)]
        self._free_list = self._curls[:]
        self._requests = collections.deque()
        self._fds = {}
        self._timeout = None

        # libcurl has bugs that sometimes cause it to not report all
        # relevant file descriptors and timeouts to TIMERFUNCTION/
        # SOCKETFUNCTION.  Mitigate the effects of such bugs by
        # forcing a periodic scan of all active requests.
        self._force_timeout_callback = ioloop.PeriodicCallback(
            self._handle_force_timeout, 1000)
        self._force_timeout_callback.start()

        # Work around a bug in libcurl 7.29.0: Some fields in the curl
        # multi object are initialized lazily, and its destructor will
        # segfault if it is destroyed without having been used.  Add
        # and remove a dummy handle to make sure everything is
        # initialized.
        dummy_curl_handle = pycurl.Curl()
        self._multi.add_handle(dummy_curl_handle)
        self._multi.remove_handle(dummy_curl_handle)

    def close(self):
        self._force_timeout_callback.stop()
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        for curl in self._curls:
            curl.close()
        self._multi.close()
        super(CurlAsyncHTTPClient, self).close()

        # Set below properties to None to reduce the reference count of current
        # instance, because those properties hold some methods of current
        # instance that will case circular reference.
        self._force_timeout_callback = None
        self._multi = None

    def fetch_impl(self, request, callback):
        self._requests.append((request, callback))
        self._process_queue()
        self._set_timeout(0)

    def _handle_socket(self, event, fd, multi, data):
        """Called by libcurl when it wants to change the file descriptors
        it cares about.
        """
        event_map = {
            pycurl.POLL_NONE: ioloop.IOLoop.NONE,
            pycurl.POLL_IN: ioloop.IOLoop.READ,
            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,
            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE
        }
        if event == pycurl.POLL_REMOVE:
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
                del self._fds[fd]
        else:
            ioloop_event = event_map[event]
            # libcurl sometimes closes a socket and then opens a new
            # one using the same FD without giving us a POLL_NONE in
            # between.  This is a problem with the epoll IOLoop,
            # because the kernel can tell when a socket is closed and
            # removes it from the epoll automatically, causing future
            # update_handler calls to fail.  Since we can't tell when
            # this has happened, always use remove and re-add
            # instead of update.
            if fd in self._fds:
                self.io_loop.remove_handler(fd)
            self.io_loop.add_handler(fd, self._handle_events,
                                     ioloop_event)
            self._fds[fd] = ioloop_event

    def _set_timeout(self, msecs):
        """Called by libcurl to schedule a timeout."""
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
        self._timeout = self.io_loop.add_timeout(
            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)

    def _handle_events(self, fd, events):
        """Called by IOLoop when there is activity on one of our
        file descriptors.
        """
        action = 0
        if events & ioloop.IOLoop.READ:
            action |= pycurl.CSELECT_IN
        if events & ioloop.IOLoop.WRITE:
            action |= pycurl.CSELECT_OUT
        while True:
            try:
                ret, num_handles = self._multi.socket_action(fd, action)
            except pycurl.error as e:
                ret = e.args[0]
            if ret != pycurl.E_CALL_MULTI_PERFORM:
                break
        self._finish_pending_requests()

    def _handle_timeout(self):
        """Called by IOLoop when the requested timeout has passed."""
        with stack_context.NullContext():
            self._timeout = None
            while True:
                try:
                    ret, num_handles = self._multi.socket_action(
                        pycurl.SOCKET_TIMEOUT, 0)
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

        # In theory, we shouldn't have to do this because curl will
        # call _set_timeout whenever the timeout changes.  However,
        # sometimes after _handle_timeout we will need to reschedule
        # immediately even though nothing has changed from curl's
        # perspective.  This is because when socket_action is
        # called with SOCKET_TIMEOUT, libcurl decides internally which
        # timeouts need to be processed by using a monotonic clock
        # (where available) while tornado uses python's time.time()
        # to decide when timeouts have occurred.  When those clocks
        # disagree on elapsed time (as they will whenever there is an
        # NTP adjustment), tornado might call _handle_timeout before
        # libcurl is ready.  After each timeout, resync the scheduled
        # timeout with libcurl's current state.
        new_timeout = self._multi.timeout()
        if new_timeout >= 0:
            self._set_timeout(new_timeout)

    def _handle_force_timeout(self):
        """Called by IOLoop periodically to ask libcurl to process any
        events it may have forgotten about.
        """
        with stack_context.NullContext():
            while True:
                try:
                    ret, num_handles = self._multi.socket_all()
                except pycurl.error as e:
                    ret = e.args[0]
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
            self._finish_pending_requests()

    def _finish_pending_requests(self):
        """Process any requests that were completed by the last
        call to multi.socket_action.
        """
        while True:
            num_q, ok_list, err_list = self._multi.info_read()
            for curl in ok_list:
                self._finish(curl)
            for curl, errnum, errmsg in err_list:
                self._finish(curl, errnum, errmsg)
            if num_q == 0:
                break
        self._process_queue()

    def _process_queue(self):
        with stack_context.NullContext():
            while True:
                started = 0
                while self._free_list and self._requests:
                    started += 1
                    curl = self._free_list.pop()
                    (request, callback) = self._requests.popleft()
                    curl.info = {
                        "headers": httputil.HTTPHeaders(),
                        "buffer": BytesIO(),
                        "request": request,
                        "callback": callback,
                        "curl_start_time": time.time(),
                    }
                    try:
                        self._curl_setup_request(
                            curl, request, curl.info["buffer"],
                            curl.info["headers"])
                    except Exception as e:
                        # If there was an error in setup, pass it on
                        # to the callback. Note that allowing the
                        # error to escape here will appear to work
                        # most of the time since we are still in the
                        # caller's original stack frame, but when
                        # _process_queue() is called from
                        # _finish_pending_requests the exceptions have
                        # nowhere to go.
                        self._free_list.append(curl)
                        callback(HTTPResponse(
                            request=request,
                            code=599,
                            error=e))
                    else:
                        self._multi.add_handle(curl)

                if not started:
                    break

    def _finish(self, curl, curl_error=None, curl_message=None):
        info = curl.info
        curl.info = None
        self._multi.remove_handle(curl)
        self._free_list.append(curl)
        buffer = info["buffer"]
        if curl_error:
            error = CurlError(curl_error, curl_message)
            code = error.code
            effective_url = None
            buffer.close()
            buffer = None
        else:
            error = None
            code = curl.getinfo(pycurl.HTTP_CODE)
            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)
            buffer.seek(0)
        # the various curl timings are documented at
        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
        time_info = dict(
            queue=info["curl_start_time"] - info["request"].start_time,
            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),
            connect=curl.getinfo(pycurl.CONNECT_TIME),
            appconnect=curl.getinfo(pycurl.APPCONNECT_TIME),
            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),
            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),
            total=curl.getinfo(pycurl.TOTAL_TIME),
            redirect=curl.getinfo(pycurl.REDIRECT_TIME),
        )
        try:
            info["callback"](HTTPResponse(
                request=info["request"], code=code, headers=info["headers"],
                buffer=buffer, effective_url=effective_url, error=error,
                reason=info['headers'].get("X-Http-Reason", None),
                request_time=time.time() - info["curl_start_time"],
                time_info=time_info))
        except Exception:
            self.handle_callback_exception(info["callback"])

    def handle_callback_exception(self, callback):
        self.io_loop.handle_callback_exception(callback)

    def _curl_create(self):
        curl = pycurl.Curl()
        if curl_log.isEnabledFor(logging.DEBUG):
            curl.setopt(pycurl.VERBOSE, 1)
            curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
        if hasattr(pycurl, 'PROTOCOLS'):  # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
            curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
            curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
        return curl

    def _curl_setup_request(self, curl, request, buffer, headers):
        curl.setopt(pycurl.URL, native_str(request.url))

        # libcurl's magic "Expect: 100-continue" behavior causes delays
        # with servers that don't support it (which include, among others,
        # Google's OpenID endpoint).  Additionally, this behavior has
        # a bug in conjunction with the curl_multi_socket_action API
        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),
        # which increases the delays.  It's more trouble than it's worth,
        # so just turn off the feature (yes, setting Expect: to an empty
        # value is the official way to disable this)
        if "Expect" not in request.headers:
            request.headers["Expect"] = ""

        # libcurl adds Pragma: no-cache by default; disable that too
        if "Pragma" not in request.headers:
            request.headers["Pragma"] = ""

        curl.setopt(pycurl.HTTPHEADER,
                    ["%s: %s" % (native_str(k), native_str(v))
                     for k, v in request.headers.get_all()])

        curl.setopt(pycurl.HEADERFUNCTION,
                    functools.partial(self._curl_header_callback,
                                      headers, request.header_callback))
        if request.streaming_callback:
            def write_function(chunk):
                self.io_loop.add_callback(request.streaming_callback, chunk)
        else:
            write_function = buffer.write
        if bytes is str:  # py2
            curl.setopt(pycurl.WRITEFUNCTION, write_function)
        else:  # py3
            # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes
            # a fork/port.  That version has a bug in which it passes unicode
            # strings instead of bytes to the WRITEFUNCTION.  This means that
            # if you use a WRITEFUNCTION (which tornado always does), you cannot
            # download arbitrary binary data.  This needs to be fixed in the
            # ported pycurl package, but in the meantime this lambda will
            # make it work for downloading (utf8) text.
            curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))
        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
        curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))
        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))
        if request.user_agent:
            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))
        else:
            curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
        if request.network_interface:
            curl.setopt(pycurl.INTERFACE, request.network_interface)
        if request.decompress_response:
            curl.setopt(pycurl.ENCODING, "gzip,deflate")
        else:
            curl.setopt(pycurl.ENCODING, "none")
        if request.proxy_host and request.proxy_port:
            curl.setopt(pycurl.PROXY, request.proxy_host)
            curl.setopt(pycurl.PROXYPORT, request.proxy_port)
            if request.proxy_username:
                credentials = '%s:%s' % (request.proxy_username,
                                         request.proxy_password)
                curl.setopt(pycurl.PROXYUSERPWD, credentials)

            if (request.proxy_auth_mode is None or
                    request.proxy_auth_mode == "basic"):
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.proxy_auth_mode == "digest":
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError(
                    "Unsupported proxy_auth_mode %s" % request.proxy_auth_mode)
        else:
            curl.setopt(pycurl.PROXY, '')
            curl.unsetopt(pycurl.PROXYUSERPWD)
        if request.validate_cert:
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        else:
            curl.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        if request.ca_certs is not None:
            curl.setopt(pycurl.CAINFO, request.ca_certs)
        else:
            # There is no way to restore pycurl.CAINFO to its default value
            # (Using unsetopt makes it reject all certificates).
            # I don't see any way to read the default value from python so it
            # can be restored later.  We'll have to just leave CAINFO untouched
            # if no ca_certs file was specified, and require that if any
            # request uses a custom ca_certs file, they all must.
            pass

        if request.allow_ipv6 is False:
            # Curl behaves reasonably when DNS resolution gives an ipv6 address
            # that we can't reach, so allow ipv6 unless the user asks to disable.
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
        else:
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)

        # Set the request method through curl's irritating interface which makes
        # up names for almost every single method
        curl_options = {
            "GET": pycurl.HTTPGET,
            "POST": pycurl.POST,
            "PUT": pycurl.UPLOAD,
            "HEAD": pycurl.NOBODY,
        }
        custom_methods = set(["DELETE", "OPTIONS", "PATCH"])
        for o in curl_options.values():
            curl.setopt(o, False)
        if request.method in curl_options:
            curl.unsetopt(pycurl.CUSTOMREQUEST)
            curl.setopt(curl_options[request.method], True)
        elif request.allow_nonstandard_methods or request.method in custom_methods:
            curl.setopt(pycurl.CUSTOMREQUEST, request.method)
        else:
            raise KeyError('unknown method ' + request.method)

        body_expected = request.method in ("POST", "PATCH", "PUT")
        body_present = request.body is not None
        if not request.allow_nonstandard_methods:
            # Some HTTP methods nearly always have bodies while others
            # almost never do. Fail in this case unless the user has
            # opted out of sanity checks with allow_nonstandard_methods.
            if ((body_expected and not body_present) or
                    (body_present and not body_expected)):
                raise ValueError(
                    'Body must %sbe None for method %s (unless '
                    'allow_nonstandard_methods is true)' %
                    ('not ' if body_expected else '', request.method))

        if body_expected or body_present:
            if request.method == "GET":
                # Even with `allow_nonstandard_methods` we disallow
                # GET with a body (because libcurl doesn't allow it
                # unless we use CUSTOMREQUEST). While the spec doesn't
                # forbid clients from sending a body, it arguably
                # disallows the server from doing anything with them.
                raise ValueError('Body must be None for GET request')
            request_buffer = BytesIO(utf8(request.body or ''))

            def ioctl(cmd):
                if cmd == curl.IOCMD_RESTARTREAD:
                    request_buffer.seek(0)
            curl.setopt(pycurl.READFUNCTION, request_buffer.read)
            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
            if request.method == "POST":
                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ''))
            else:
                curl.setopt(pycurl.UPLOAD, True)
                curl.setopt(pycurl.INFILESIZE, len(request.body or ''))

        if request.auth_username is not None:
            userpwd = "%s:%s" % (request.auth_username, request.auth_password or '')

            if request.auth_mode is None or request.auth_mode == "basic":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.auth_mode == "digest":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported auth_mode %s" % request.auth_mode)

            curl.setopt(pycurl.USERPWD, native_str(userpwd))
            curl_log.debug("%s %s (username: %r)", request.method, request.url,
                           request.auth_username)
        else:
            curl.unsetopt(pycurl.USERPWD)
            curl_log.debug("%s %s", request.method, request.url)

        if request.client_cert is not None:
            curl.setopt(pycurl.SSLCERT, request.client_cert)

        if request.client_key is not None:
            curl.setopt(pycurl.SSLKEY, request.client_key)

        if request.ssl_options is not None:
            raise ValueError("ssl_options not supported in curl_httpclient")

        if threading.activeCount() > 1:
            # libcurl/pycurl is not thread-safe by default.  When multiple threads
            # are used, signals should be disabled.  This has the side effect
            # of disabling DNS timeouts in some environments (when libcurl is
            # not linked against ares), so we don't do it when there is only one
            # thread.  Applications that use many short-lived threads may need
            # to set NOSIGNAL manually in a prepare_curl_callback since
            # there may not be any other threads running at the time we call
            # threading.activeCount.
            curl.setopt(pycurl.NOSIGNAL, 1)
        if request.prepare_curl_callback is not None:
            request.prepare_curl_callback(curl)

    def _curl_header_callback(self, headers, header_callback, header_line):
        header_line = native_str(header_line.decode('latin1'))
        if header_callback is not None:
            self.io_loop.add_callback(header_callback, header_line)
        # header_line as returned by curl includes the end-of-line characters.
        # whitespace at the start should be preserved to allow multi-line headers
        header_line = header_line.rstrip()
        if header_line.startswith("HTTP/"):
            headers.clear()
            try:
                (__, __, reason) = httputil.parse_response_start_line(header_line)
                header_line = "X-Http-Reason: %s" % reason
            except httputil.HTTPInputError:
                return
        if not header_line:
            return
        headers.parse_line(header_line)

    def _curl_debug(self, debug_type, debug_msg):
        debug_types = ('I', '<', '>', '<', '>')
        if debug_type == 0:
            debug_msg = native_str(debug_msg)
            curl_log.debug('%s', debug_msg.strip())
        elif debug_type in (1, 2):
            debug_msg = native_str(debug_msg)
            for line in debug_msg.splitlines():
                curl_log.debug('%s %s', debug_types[debug_type], line)
        elif debug_type == 4:
            curl_log.debug('%s %r', debug_types[debug_type], debug_msg)


class CurlError(HTTPError):
    def __init__(self, errno, message):
        HTTPError.__init__(self, 599, message)
        self.errno = errno


if __name__ == "__main__":
    AsyncHTTPClient.configure(CurlAsyncHTTPClient)
    main()

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user