/** * 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)) { throw new Exception("Error writing request to proxy server"); } // 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 (\Zend\HTTP\Response\Response::extractCode($response) != 200) { throw new Exception("Unable to connect to HTTPS proxy. Server response: " . $response); } // 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) { throw new Exception("Unable to connect to" . " HTTPS server through proxy: could not negotiate secure connection."); } }
/** * Populate this instance based on a Zend_Http_Response object * * @param \Zend\HTTP\Response\Response $response * @return \Zend\Service\ReCaptcha\Response */ public function setFromHttpResponse(HTTPResponse\Response $response) { $body = $response->getBody(); $parts = explode("\n", $body, 2); if (count($parts) !== 2) { $status = 'false'; $errorCode = ''; } else { list($status, $errorCode) = $parts; } $this->setStatus($status); $this->setErrorCode($errorCode); return $this; }
/** * Read response from server * * @return string */ public function read() { // First, read headers only $response = ''; $gotStatus = false; $stream = !empty($this->config['stream']); while (($line = @fgets($this->socket)) !== false) { $gotStatus = $gotStatus || strpos($line, 'HTTP') !== false; if ($gotStatus) { $response .= $line; if (rtrim($line) === '') { break; } } } $this->_checkSocketReadTimeout(); $statusCode = Response\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 = Response\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 == \Zend\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(); throw new Exception('Invalid chunk size "' . $chunksize . '" unable to read chunked body'); } // 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; } if ($this->out_stream) { if (stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { $this->_checkSocketReadTimeout(); break; } } else { $line = @fread($this->socket, $read_to - $current_pos); if ($line === false || strlen($line) === 0) { $this->_checkSocketReadTimeout(); break; } $chunk .= $line; } } while (!feof($this->socket)); $chunk .= @fgets($this->socket); $this->_checkSocketReadTimeout(); if (!$this->out_stream) { $response .= $chunk; } } while ($chunksize > 0); } else { $this->close(); throw new Exception('Cannot handle "' . $headers['transfer-encoding'] . '" transfer encoding'); } // We automatically decode chunked-messages when writing to a stream // this means we have to disallow the Zend_Http_Response to do it again if ($this->out_stream) { $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response); } // Else, if we got the content-length header, read this number of bytes } 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)) { if ($this->out_stream) { if (@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) { $this->_checkSocketReadTimeout(); break; } } else { $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; } } // Fallback: just read the response until EOF } else { do { if ($this->out_stream) { if (@stream_copy_to_stream($this->socket, $this->out_stream) == 0) { $this->_checkSocketReadTimeout(); break; } } else { $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; }
/** * Test that responses could be added as objects (ZF-7009) * * @link http://framework.zend.com/issues/browse/ZF-7009 * @dataProvider validHttpResponseProvider */ public function testAddResponseAsObject($testResponse) { $this->adapter->read(); // pop out first response $respObj = \Zend\HTTP\Response\Response::fromString($testResponse); $this->adapter->addResponse($respObj); $this->assertEquals($testResponse, $this->adapter->read()); }
/** * Test we can build a new object from a response object (multiple cookie headers) */ public function testFromResponseMultiHeader() { $res_str = file_get_contents(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'response_with_cookies'); $response = Response\Response::fromString($res_str); $jar = HTTP\CookieJar::fromResponse($response, 'http://www.example.com'); $this->assertTrue($jar instanceof HTTP\CookieJar, '$jar is not an instance of CookieJar as expected'); $this->assertEquals(3, count($jar->getAllCookies()), 'CookieJar expected to contain 3 cookies'); }
/** * Test that parsing a multibyte-encoded chunked response works. * * This can potentially fail on different PHP environments - for example * when mbstring.func_overload is set to overload strlen(). * */ public function testMultibyteChunkedResponse() { $md5 = 'ab952f1617d0e28724932401f2d3c6ae'; $response = Response\Response::fromString($this->readResponse('response_multibyte_body')); $this->assertEquals($md5, md5($response->getBody())); }
/** * Parse a HTTP response body and collect returned parameters * as raw url decoded key-value pairs in an associative array. * * @param \Zend\HTTP\Response\Response $response * @return array */ protected function _parseParameters(HTTPResponse $response) { $params = array(); $body = $response->getBody(); if (empty($body)) { return; } // validate body based on acceptable characters...todo $parts = explode('&', $body); foreach ($parts as $kvpair) { $pair = explode('=', $kvpair); $params[rawurldecode($pair[0])] = rawurldecode($pair[1]); } return $params; }
public function makeHttpResponseFor($nativeVars) { $response = $this->getServerResponseFor($nativeVars); return \Zend\HTTP\Response\Response::fromString($response); }
/** * Send the HTTP request and return an HTTP response object * * @param string $method * @return \Zend\HTTP\Response\Response * @throws \Zend\HTTP\Client\Exception */ public function request($method = null) { if (!$this->uri instanceof URI\URL) { throw new Client\Exception('No valid URI has been passed to the client'); } 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; if (!empty($this->paramsGet)) { $query = $uri->getQuery(); if (!empty($query)) { $query .= '&'; } $query .= http_build_query($this->paramsGet, null, '&'); $uri->setQuery($query); } $body = $this->_prepareBody(); $headers = $this->_prepareHeaders(); // check that adapter supports streaming before using it if (is_resource($body) && !$this->adapter instanceof Adapter\Stream) { throw new Client\Exception('Adapter does not support streaming'); } // Open the connection, send the request and read the response $this->adapter->connect($uri->getHost(), $uri->getPort(), $uri->getScheme() == 'https' ? true : false); if ($this->config['output_stream']) { if ($this->adapter instanceof Adapter\Stream) { $stream = $this->_openTempStream(); $this->adapter->setOutputStream($stream); } else { throw new Client\Exception('Adapter does not support streaming'); } } $this->last_request = $this->adapter->write($this->method, $uri, $this->config['httpversion'], $headers, $body); $response = $this->adapter->read(); if (!$response) { throw new Client\Exception('Unable to read response, or response is empty'); } if ($this->config['output_stream']) { rewind($stream); // cleanup the adapter $this->adapter->setOutputStream(null); $response = HTTP\Response\Stream::fromStream($response, $stream); $response->setStreamName($this->_stream_name); if (!is_string($this->config['output_stream'])) { // we used temp name, will need to clean up $response->setCleanup(true); } } else { $response = HTTP\Response\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 $url = new URI\URL($location); if ($url->isValid()) { $this->setHeaders('host', null); $this->setUri($location); } else { // Split into path and query and set the query if (strpos($location, '?') !== false) { list($location, $query) = explode('?', $location, 2); } else { $query = ''; } $this->uri->setQuery($query); // Else, if we got just an absolute path, set it if (strpos($location, '/') === 0) { $this->uri->setPath($location); // Else, assume we have a relative path } else { // Get the current path directory, removing any trailing slashes $path = $this->uri->getPath(); $path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); $this->uri->setPath($path . '/' . $location); } } ++$this->redirectCounter; } else { // If we didn't get any location, stop redirecting break; } } while ($this->redirectCounter < $this->config['maxredirects']); return $response; }