/** * Send the HTTP request and return an HTTP response object * * @param string $method * @return Zend_Http_Response * @throws Zend_Http_Client_Exception */ public function request($method = null) { if (!$this->uri instanceof Sabel_Http_Uri) { $message = __METHOD__ . "() No valid URI has been passed to the client."; throw new Sabel_Exception_Runtime($message); } if ($method) { $this->setMethod($method); } $this->redirectCounter = 0; $response = null; // Make sure the adapter is loaded if ($this->adapter == null) { $this->setAdapter($this->config["adapter"]); } // Send the first request. If redirected, continue. do { // Clone the URI and add the additional GET parameters to it $uri = clone $this->uri; $query = array(); if (!empty($uri->query)) { parse_str($uri->query, $query); } if (!empty($this->paramsGet)) { $query = array_merge($query, $this->paramsGet); } if (!empty($query)) { $uri->query = http_build_query($query, null, "&"); } $body = $this->_prepareBody(); $headers = $this->_prepareHeaders(); // Open the connection, send the request and read the response $this->adapter->connect($uri->host, $uri->port, $uri->scheme === "https" ? true : false); $this->last_request = $this->adapter->write($this->method, $uri, $this->config["httpversion"], $headers, $body); $response = $this->adapter->read(); if (!$response) { $message = __METHOD__ . "() Unable to read response, or response is empty."; throw new Sabel_Exception_Runtime($message); } $response = Sabel_Http_Response::fromString($response); if ($this->config["storeresponse"]) { $this->last_response = $response; } // Load cookies into cookie jar if (isset($this->cookiejar)) { $this->cookiejar->addCookiesFromResponse($response, $uri); } // If we got redirected, look for the Location header if ($response->isRedirect() && ($location = $response->getHeader("location"))) { // Check whether we send the exact same request again, // or drop the parameters and send a GET request if ($response->getStatus() == 303 || !$this->config["strictredirects"] && ($response->getStatus() == 302 || $response->getStatus() == 301)) { $this->resetParameters(); $this->setMethod(self::GET); } // If we got a well formed absolute URI $_uri = @parse_url($location); if (isset($_uri["scheme"]) && isset($_uri["host"])) { $this->setHeaders("host", null); $this->setUri($location); } else { // Split into path and query and set the query if (strpos($location, "?") === false) { $query = ""; } else { list($location, $query) = explode("?", $location, 2); } $this->uri->query = $query; // Else, if we got just an absolute path, set it if (strpos($location, "/") === 0) { $this->uri->path = $location; } else { // Get the current path directory, removing any trailing slashes $path = $this->uri->path; $path = rtrim(substr($path, 0, strrpos($path, "/")), "/"); $this->uri->path = $path . "/" . $location; } } ++$this->redirectCounter; } else { // If we didn't get any location, stop redirecting break; } } while ($this->redirectCounter < $this->config["maxredirects"]); return $response; }
protected function _setCookie(Sabel_Http_Response $response, $host) { $cookies = $response->getHeader("Set-Cookie"); if ($cookies === "") { return; } foreach (is_string($cookies) ? array($cookies) : $cookies as $cookie) { $parts = array_map("trim", explode(";", $cookie)); $first = array_shift($parts); list($key, $value) = explode("=", $first); $values = array("name" => $key, "value" => urldecode($value)); foreach ($parts as $part) { if ($part === "secure") { $values["secure"] = true; } else { list($name, $val) = explode("=", $part); if ($name === "expires") { $values[$name] = strtotime($val); } else { $values[$name] = $val; } } } if (!isset($values["domain"])) { $values["domain"] = $host; } if (!isset($values["path"])) { $values["path"] = "/"; } if (isset($values["expires"]) && $values["expires"] < time()) { $this->deleteCookie($values["name"], $values["path"], $values["domain"]); } else { $this->cookies[] = $values; } } }
/** * Read response from server * * @return string */ public function read() { // First, read headers only $response = ""; $gotStatus = false; while (($line = @fgets($this->socket)) !== false) { $gotStatus = $gotStatus || strpos($line, "HTTP") !== false; if ($gotStatus) { $response .= $line; if (rtrim($line) === "") { break; } } } $this->_checkSocketReadTimeout(); $statusCode = Sabel_Http_Response::extractCode($response); // Handle 100 and 101 responses internally by restarting the read again if ($statusCode == 100 || $statusCode == 101) { return $this->read(); } // Check headers to see what kind of connection / transfer encoding we have $headers = Sabel_Http_Response::extractHeaders($response); /** * Responses to HEAD requests and 204 or 304 responses are not expected * to have a body - stop reading here */ if ($statusCode == 304 || $statusCode == 204 || $this->method === Sabel_Http_Client::HEAD) { // Close the connection if requested to do so by the server if (isset($headers["connection"]) && $headers["connection"] === "close") { $this->close(); } return $response; } // If we got a 'transfer-encoding: chunked' header if (isset($headers["transfer-encoding"])) { if (strtolower($headers["transfer-encoding"]) === "chunked") { do { $line = @fgets($this->socket); $this->_checkSocketReadTimeout(); $chunk = $line; // Figure out the next chunk size $chunksize = trim($line); if (!ctype_xdigit($chunksize)) { $this->close(); $message = __METHOD__ . "() Unable to read chunk body."; throw new Sabel_Exception_Runtime($message); } // Convert the hexadecimal value to plain integer $chunksize = hexdec($chunksize); // Read next chunk $read_to = ftell($this->socket) + $chunksize; do { $current_pos = ftell($this->socket); if ($current_pos >= $read_to) { break; } $line = @fread($this->socket, $read_to - $current_pos); if ($line === false || strlen($line) === 0) { $this->_checkSocketReadTimeout(); break; } else { $chunk .= $line; } } while (!feof($this->socket)); $chunk .= @fgets($this->socket); $this->_checkSocketReadTimeout(); $response .= $chunk; } while ($chunksize > 0); } else { $this->close(); $message = __METHOD__ . "() Cannot handle '{$headers['transfer-encoding']}' transfer encoding. "; throw new Sabel_Exception_Runtime($message); } } elseif (isset($headers["content-length"])) { $current_pos = ftell($this->socket); $chunk = ""; for ($read_to = $current_pos + $headers["content-length"]; $read_to > $current_pos; $current_pos = ftell($this->socket)) { $chunk = @fread($this->socket, $read_to - $current_pos); if ($chunk === false || strlen($chunk) === 0) { $this->_checkSocketReadTimeout(); break; } $response .= $chunk; // Break if the connection ended prematurely if (feof($this->socket)) { break; } } } else { do { $buff = @fread($this->socket, 8192); if ($buff === false || strlen($buff) === 0) { $this->_checkSocketReadTimeout(); break; } else { $response .= $buff; } } while (feof($this->socket) === false); $this->close(); } // Close the connection if requested to do so by the server if (isset($headers["connection"]) && $headers["connection"] === "close") { $this->close(); } return $response; }
/** * Preform handshaking with HTTPS proxy using CONNECT method * * @param string $host * @param integer $port * @param string $http_ver * @param array $headers */ protected function connectHandshake($host, $port = 443, $http_ver = "1.1", array &$headers = array()) { $request = "CONNECT {$host}:{$port} HTTP/{$http_ver}\r\n" . "Host: " . $this->config["proxy_host"] . "\r\n"; // Add the user-agent header if (isset($this->config["useragent"])) { $request .= "User-agent: " . $this->config["useragent"] . "\r\n"; } // If the proxy-authorization header is set, send it to proxy but remove // it from headers sent to target host if (isset($headers["proxy-authorization"])) { $request .= "Proxy-authorization: " . $headers["proxy-authorization"] . "\r\n"; unset($headers["proxy-authorization"]); } $request .= "\r\n"; // Send the request if (!@fwrite($this->socket, $request)) { $message = __METHOD__ . "() Error writing request to proxy server."; throw new Sabel_Exception_Runtime($message); } // Read response headers only $response = ""; $gotStatus = false; while ($line = @fgets($this->socket)) { $gotStatus = $gotStatus || strpos($line, "HTTP") !== false; if ($gotStatus) { $response .= $line; if (!chop($line)) { break; } } } // Check that the response from the proxy is 200 if (Sabel_Http_Response::extractCode($response) != 200) { $message = __METHOD__ . "() Unable to connect to HTTPS proxy. Server response: {$response}."; throw new Sabel_Exception_Runtime($message); } // If all is good, switch socket to secure mode. We have to fall back // through the different modes $modes = array(STREAM_CRYPTO_METHOD_TLS_CLIENT, STREAM_CRYPTO_METHOD_SSLv3_CLIENT, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, STREAM_CRYPTO_METHOD_SSLv2_CLIENT); $success = false; foreach ($modes as $mode) { $success = stream_socket_enable_crypto($this->socket, true, $mode); if ($success) { break; } } if (!$success) { $message = __METHOD__ . "() Unable to connect to HTTPS server through " . "proxy: could not negotiate secure connection."; throw new Sabel_Exception_Runtime($message); } }