protected function exec()
    {
        static $loops = 0;
        static $maxLoops = 20;

        if ($loops++ >= $maxLoops) {
            $loops = 0;
            throw new ConnectionErrorException('Too many redirects in request');
        }

        $curl_return = curl_exec($this->connection);
        if ($curl_return === false) {
            throw new CurlErrorException(curl_error($this->connection));
        }

        $response = explode("\r\n\r\n", $curl_return, 2);
        $this->httpStatus = curl_getinfo($this->connection, CURLINFO_HTTP_CODE);

        $header = isset($response[0]) ? $response[0] : '';
        $data = isset($response[1]) ? $response[1] : '';

        // Ignore Continue header
        if ($header == "HTTP/1.1 100 Continue") {
            $response = explode("\r\n\r\n", $data, 2);
            $header = isset($response[0]) ? $response[0] : '';
            $data = isset($response[1]) ? $response[1] : '';
        }

        $this->lastHeader = $header;
        $this->response = $data;

        if ($this->httpStatus == 301 || $this->httpStatus == 302) {
            $matches = array();
            preg_match('/Location:(.*?)\n/', $header, $matches);
            $url = @parse_url(trim(array_pop($matches)));
            if (!$url) {
                //couldn't process the url to redirect to
                $loops = 0;
                throw new ConnectionErrorException('Invalid redirect');
            }

            $this->setConnectionOptions(array(
                CURLOPT_URL => $url['scheme'] . '://' . $url['host'] . $url['path'] . (!empty($url['query']) ? '?' . $url['query'] : '')
            ));

            $this->exec();
        }

        $loops = 0;

        $this->handleError();

        return $this;
    }