710 lines
18 KiB
PHP
710 lines
18 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the sfWebBrowserPlugin package.
|
|
* (c) 2004-2006 Francois Zaninotto <francois.zaninotto@symfony-project.com>
|
|
* (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com> for the click-related functions
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
/**
|
|
* sfWebBrowser provides a basic HTTP client.
|
|
*
|
|
* @package sfWebBrowserPlugin
|
|
* @author Francois Zaninotto <francois.zaninotto@symfony-project.com>
|
|
* @author Tristan Rivoallan <tristan@rivoallan.net>
|
|
* @version 0.9
|
|
*/
|
|
class sfWebBrowser
|
|
{
|
|
protected
|
|
$defaultHeaders = array(),
|
|
$stack = array(),
|
|
$stackPosition = -1,
|
|
$responseHeaders = array(),
|
|
$responseCode = '',
|
|
$responseMessage = '',
|
|
$responseText = '',
|
|
$responseDom = null,
|
|
$responseDomCssSelector = null,
|
|
$responseXml = null,
|
|
$fields = array(),
|
|
$urlInfo = array(),
|
|
$followRedirect = true;
|
|
|
|
public function __construct($defaultHeaders = array(), $adapterClass = null, $adapterOptions = array())
|
|
{
|
|
if (!$adapterClass)
|
|
{
|
|
if (function_exists('curl_init'))
|
|
{
|
|
$adapterClass = 'sfCurlAdapter';
|
|
}
|
|
else if (ini_get('allow_url_fopen') == 1)
|
|
{
|
|
$adapterClass = 'sfFopenAdapter';
|
|
}
|
|
else
|
|
{
|
|
$adapterClass = 'sfSocketsAdapter';
|
|
}
|
|
}
|
|
$this->defaultHeaders = $this->fixHeaders($defaultHeaders);
|
|
$this->adapter = new $adapterClass($adapterOptions);
|
|
}
|
|
|
|
/**
|
|
* Set followRedirect
|
|
*
|
|
* @param boolean $value
|
|
*/
|
|
public function setFollowRedirect($value)
|
|
{
|
|
$this->followRedirect = $value;
|
|
|
|
return $this;
|
|
}
|
|
|
|
// Browser methods
|
|
|
|
/**
|
|
* Restarts the browser
|
|
*
|
|
* @param array default browser options
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function restart($defaultHeaders = array())
|
|
{
|
|
$this->defaultHeaders = $this->fixHeaders($defaultHeaders);
|
|
$this->stack = array();
|
|
$this->stackPosition = -1;
|
|
$this->urlInfo = array();
|
|
$this->initializeResponse();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Sets the browser user agent name
|
|
*
|
|
* @param string agent name
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function setUserAgent($agent)
|
|
{
|
|
$this->defaultHeaders['User-Agent'] = $agent;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Gets the browser user agent name
|
|
*
|
|
* @return string agent name
|
|
*/
|
|
public function getUserAgent()
|
|
{
|
|
return isset($this->defaultHeaders['User-Agent']) ? $this->defaultHeaders['User-Agent'] : '';
|
|
}
|
|
|
|
/**
|
|
* Submits a GET request
|
|
*
|
|
* @param string The request uri
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
* @param boolean To specify is the request changes the browser history
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function get($uri, $parameters = array(), $headers = array(), $changeStack = false)
|
|
{
|
|
if ($parameters)
|
|
{
|
|
$uri .= ((false !== strpos($uri, '?')) ? '&' : '?') . http_build_query($parameters, '', '&');
|
|
}
|
|
return $this->call($uri, 'GET', array(), $headers, $changeStack);
|
|
}
|
|
|
|
/**
|
|
* Submits a HEAD request
|
|
*
|
|
* @param string The request uri
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
* @param boolean To specify is the request changes the browser history
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function head($uri, $parameters = array(), $headers = array(), $changeStack = false)
|
|
{
|
|
if ($parameters)
|
|
{
|
|
$uri .= ((false !== strpos($uri, '?')) ? '&' : '?') . http_build_query($parameters, '', '&');
|
|
}
|
|
return $this->call($uri, 'HEAD', array(), $headers, $changeStack);
|
|
}
|
|
|
|
/**
|
|
* Submits a POST request
|
|
*
|
|
* @param string The request uri
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
* @param boolean To specify is the request changes the browser history
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function post($uri, $parameters = array(), $headers = array(), $changeStack = false)
|
|
{
|
|
return $this->call($uri, 'POST', $parameters, $headers, $changeStack);
|
|
}
|
|
|
|
/**
|
|
* Submits a PUT request.
|
|
*
|
|
* @param string The request uri
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
* @param boolean To specify is the request changes the browser history
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function put($uri, $parameters = array(), $headers = array(), $changeStack = false)
|
|
{
|
|
return $this->call($uri, 'PUT', $parameters, $headers, $changeStack);
|
|
}
|
|
|
|
/**
|
|
* Submits a DELETE request.
|
|
*
|
|
* @param string The request uri
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
* @param boolean To specify is the request changes the browser history
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function delete($uri, $parameters = array(), $headers = array(), $changeStack = false)
|
|
{
|
|
return $this->call($uri, 'DELETE', $parameters, $headers, $changeStack);
|
|
}
|
|
|
|
/**
|
|
* Submits a request
|
|
*
|
|
* @param string The request uri
|
|
* @param string The request method
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
* @param boolean To specify is the request changes the browser history
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function call($uri, $method = 'GET', $parameters = array(), $headers = array(), $changeStack = true)
|
|
{
|
|
$urlInfo = parse_url($uri);
|
|
|
|
// Check headers
|
|
$headers = $this->fixHeaders($headers);
|
|
|
|
// check port
|
|
if (isset($urlInfo['port']))
|
|
{
|
|
$this->urlInfo['port'] = $urlInfo['port'];
|
|
}
|
|
else if (!isset($this->urlInfo['port']))
|
|
{
|
|
$this->urlInfo['port'] = 80;
|
|
}
|
|
|
|
if(!isset($urlInfo['host']))
|
|
{
|
|
// relative link
|
|
$uri = $this->urlInfo['scheme'].'://'.$this->urlInfo['host'].':'.$this->urlInfo['port'].'/'.$uri;
|
|
}
|
|
else if($urlInfo['scheme'] != 'http' && $urlInfo['scheme'] != 'https')
|
|
{
|
|
throw new Exception('sfWebBrowser handles only http and https requests');
|
|
}
|
|
|
|
$this->urlInfo = parse_url($uri);
|
|
|
|
$this->initializeResponse();
|
|
|
|
if ($changeStack)
|
|
{
|
|
$this->addToStack($uri, $method, $parameters, $headers);
|
|
}
|
|
|
|
$browser = $this->adapter->call($this, $uri, $method, $parameters, $headers);
|
|
|
|
// redirect support
|
|
if ($this->followRedirect)
|
|
{
|
|
if ((in_array($browser->getResponseCode(), array(301, 307)) && in_array($method, array('GET', 'HEAD'))) || in_array($browser->getResponseCode(), array(302,303)))
|
|
{
|
|
$this->call($browser->getResponseHeader('Location'), 'GET', array(), $headers);
|
|
}
|
|
}
|
|
|
|
return $browser;
|
|
}
|
|
|
|
/**
|
|
* Adds the current request to the history stack
|
|
*
|
|
* @param string The request uri
|
|
* @param string The request method
|
|
* @param array The request parameters (associative array)
|
|
* @param array The request headers (associative array)
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function addToStack($uri, $method, $parameters, $headers)
|
|
{
|
|
$this->stack = array_slice($this->stack, 0, $this->stackPosition + 1);
|
|
$this->stack[] = array(
|
|
'uri' => $uri,
|
|
'method' => $method,
|
|
'parameters' => $parameters,
|
|
'headers' => $headers
|
|
);
|
|
$this->stackPosition = count($this->stack) - 1;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get stack
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getStack()
|
|
{
|
|
return $this->stack;
|
|
}
|
|
|
|
/**
|
|
* Submits the previous request in history again
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function back()
|
|
{
|
|
if ($this->stackPosition < 1)
|
|
{
|
|
throw new Exception('You are already on the first page.');
|
|
}
|
|
|
|
--$this->stackPosition;
|
|
return $this->call($this->stack[$this->stackPosition]['uri'],
|
|
$this->stack[$this->stackPosition]['method'],
|
|
$this->stack[$this->stackPosition]['parameters'],
|
|
$this->stack[$this->stackPosition]['headers'],
|
|
false);
|
|
}
|
|
|
|
/**
|
|
* Submits the next request in history again
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function forward()
|
|
{
|
|
if ($this->stackPosition > count($this->stack) - 2)
|
|
{
|
|
throw new Exception('You are already on the last page.');
|
|
}
|
|
|
|
++$this->stackPosition;
|
|
return $this->call($this->stack[$this->stackPosition]['uri'],
|
|
$this->stack[$this->stackPosition]['method'],
|
|
$this->stack[$this->stackPosition]['parameters'],
|
|
$this->stack[$this->stackPosition]['headers'],
|
|
false);
|
|
}
|
|
|
|
/**
|
|
* Submits the current request again
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function reload()
|
|
{
|
|
if (-1 == $this->stackPosition)
|
|
{
|
|
throw new Exception('No page to reload.');
|
|
}
|
|
|
|
return $this->call($this->stack[$this->stackPosition]['uri'],
|
|
$this->stack[$this->stackPosition]['method'],
|
|
$this->stack[$this->stackPosition]['parameters'],
|
|
$this->stack[$this->stackPosition]['headers'],
|
|
false);
|
|
}
|
|
|
|
/**
|
|
* Transforms an associative array of header names => header values to its HTTP equivalent.
|
|
*
|
|
* @param array $headers
|
|
* @return string
|
|
*/
|
|
public function prepareHeaders($headers = array())
|
|
{
|
|
$prepared_headers = array();
|
|
foreach ($headers as $name => $value)
|
|
{
|
|
$prepared_headers[] = sprintf("%s: %s\r\n", ucfirst($name), $value);
|
|
}
|
|
|
|
return implode('', $prepared_headers);
|
|
}
|
|
|
|
// Response methods
|
|
|
|
/**
|
|
* Initializes the response and erases all content from prior requests
|
|
*/
|
|
public function initializeResponse()
|
|
{
|
|
$this->responseHeaders = array();
|
|
$this->responseCode = '';
|
|
$this->responseText = '';
|
|
$this->responseDom = null;
|
|
$this->responseDomCssSelector = null;
|
|
$this->responseXml = null;
|
|
$this->fields = array();
|
|
}
|
|
|
|
/**
|
|
* Set the response headers
|
|
*
|
|
* @param array The response headers as an array of strings shaped like "key: value"
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function setResponseHeaders($headers = array())
|
|
{
|
|
$header_array = array();
|
|
foreach($headers as $header)
|
|
{
|
|
$arr = explode(': ', $header);
|
|
if(isset($arr[1]))
|
|
{
|
|
$header_array[$this->normalizeHeaderName($arr[0])] = trim($arr[1]);
|
|
}
|
|
}
|
|
|
|
$this->responseHeaders = $header_array;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the response code
|
|
*
|
|
* @param string The first line of the response
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function setResponseCode($firstLine)
|
|
{
|
|
preg_match('/\d{3}/', $firstLine, $matches);
|
|
if(isset($matches[0]))
|
|
{
|
|
$this->responseCode = $matches[0];
|
|
}
|
|
else
|
|
{
|
|
$this->responseCode = '';
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Set the response contents
|
|
*
|
|
* @param string The response contents
|
|
*
|
|
* @return sfWebBrowser The current browser object
|
|
*/
|
|
public function setResponseText($res)
|
|
{
|
|
$this->responseText = $res;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get a text version of the response
|
|
*
|
|
* @return string The response contents
|
|
*/
|
|
public function getResponseText()
|
|
{
|
|
$text = $this->responseText;
|
|
|
|
// Decode any content-encoding (gzip or deflate) if needed
|
|
switch (strtolower($this->getResponseHeader('content-encoding'))) {
|
|
|
|
// Handle gzip encoding
|
|
case 'gzip':
|
|
$text = $this->decodeGzip($text);
|
|
break;
|
|
|
|
// Handle deflate encoding
|
|
case 'deflate':
|
|
$text = $this->decodeDeflate($text);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Get a text version of the body part of the response (without <body> and </body>)
|
|
*
|
|
* @return string The body part of the response contents
|
|
*/
|
|
public function getResponseBody()
|
|
{
|
|
preg_match('/<body.*?>(.*)<\/body>/si', $this->getResponseText(), $matches);
|
|
|
|
return isset($matches[1]) ? $matches[1] : '';
|
|
}
|
|
|
|
/**
|
|
* Get a DOMDocument version of the response
|
|
*
|
|
* @return DOMDocument The reponse contents
|
|
*/
|
|
public function getResponseDom()
|
|
{
|
|
if(!$this->responseDom)
|
|
{
|
|
// for HTML/XML content, create a DOM object for the response content
|
|
if (preg_match('/(x|ht)ml/i', $this->getResponseHeader('Content-Type')))
|
|
{
|
|
$this->responseDom = new DomDocument('1.0', 'utf8');
|
|
$this->responseDom->validateOnParse = true;
|
|
@$this->responseDom->loadHTML($this->getResponseText());
|
|
}
|
|
}
|
|
|
|
return $this->responseDom;
|
|
}
|
|
|
|
/**
|
|
* Get a sfDomCssSelector version of the response
|
|
*
|
|
* @return sfDomCssSelector The response contents
|
|
*/
|
|
public function getResponseDomCssSelector()
|
|
{
|
|
if(!$this->responseDomCssSelector)
|
|
{
|
|
// for HTML/XML content, create a DOM object for the response content
|
|
if (preg_match('/(x|ht)ml/i', $this->getResponseHeader('Content-Type')))
|
|
{
|
|
$this->responseDomCssSelector = new sfDomCssSelector($this->getResponseDom());
|
|
}
|
|
}
|
|
|
|
return $this->responseDomCssSelector;
|
|
}
|
|
|
|
/**
|
|
* Get a SimpleXML version of the response
|
|
*
|
|
* @return SimpleXMLElement The reponse contents
|
|
* @throws sfWebBrowserInvalidResponseException when response is not in a valid format
|
|
*/
|
|
public function getResponseXML()
|
|
{
|
|
if(!$this->responseXml)
|
|
{
|
|
// for HTML/XML content, create a DOM object for the response content
|
|
if (preg_match('/(x|ht)ml/i', $this->getResponseHeader('Content-Type')))
|
|
{
|
|
$this->responseXml = @simplexml_load_string($this->getResponseText());
|
|
}
|
|
}
|
|
|
|
// Throw an exception if response is not valid XML
|
|
if (get_class($this->responseXml) != 'SimpleXMLElement')
|
|
{
|
|
$msg = sprintf("Response is not a valid XML string : \n%s", $this->getResponseText());
|
|
throw new sfWebBrowserInvalidResponseException($msg);
|
|
}
|
|
|
|
return $this->responseXml;
|
|
}
|
|
|
|
/**
|
|
* Returns true if server response is an error.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function responseIsError()
|
|
{
|
|
return in_array((int)($this->getResponseCode() / 100), array(4, 5));
|
|
}
|
|
|
|
/**
|
|
* Get the response headers
|
|
*
|
|
* @return array The response headers
|
|
*/
|
|
public function getResponseHeaders()
|
|
{
|
|
return $this->responseHeaders;
|
|
}
|
|
|
|
/**
|
|
* Get a response header
|
|
*
|
|
* @param string The response header name
|
|
*
|
|
* @return string The response header value
|
|
*/
|
|
public function getResponseHeader($key)
|
|
{
|
|
$normalized_key = $this->normalizeHeaderName($key);
|
|
return (isset($this->responseHeaders[$normalized_key])) ? $this->responseHeaders[$normalized_key] : '';
|
|
}
|
|
|
|
/**
|
|
* Decodes gzip-encoded content ("content-encoding: gzip" response header).
|
|
*
|
|
* @param stream $gzip_text
|
|
* @return string
|
|
*/
|
|
protected function decodeGzip($gzip_text)
|
|
{
|
|
return gzinflate(substr($gzip_text, 10));
|
|
}
|
|
|
|
/**
|
|
* Decodes deflate-encoded content ("content-encoding: deflate" response header).
|
|
*
|
|
* @param stream $deflate_text
|
|
* @return string
|
|
*/
|
|
protected function decodeDeflate($deflate_text)
|
|
{
|
|
return gzuncompress($deflate_text);
|
|
}
|
|
|
|
/**
|
|
* Get the response code
|
|
*
|
|
* @return string The response code
|
|
*/
|
|
public function getResponseCode()
|
|
{
|
|
return $this->responseCode;
|
|
}
|
|
|
|
/**
|
|
* Returns the response message (the 'Not Found' part in 'HTTP/1.1 404 Not Found')
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getResponseMessage()
|
|
{
|
|
return $this->responseMessage;
|
|
}
|
|
|
|
/**
|
|
* Sets response message.
|
|
*
|
|
* @param string $message
|
|
*/
|
|
public function setResponseMessage($msg)
|
|
{
|
|
$this->responseMessage = $msg;
|
|
}
|
|
|
|
public function getUrlInfo()
|
|
{
|
|
return $this->urlInfo;
|
|
}
|
|
|
|
public function getDefaultRequestHeaders()
|
|
{
|
|
return $this->defaultHeaders;
|
|
}
|
|
|
|
/**
|
|
* Adds default headers to the supplied headers array.
|
|
*
|
|
* @param array $headers
|
|
* @return array
|
|
*/
|
|
public function initializeRequestHeaders($headers = array())
|
|
{
|
|
// Supported encodings
|
|
$encodings = array();
|
|
if (isset($headers['Accept-Encoding']))
|
|
{
|
|
$encodings = explode(',', $headers['Accept-Encoding']);
|
|
}
|
|
if (function_exists('gzinflate') && !in_array('gzip', $encodings))
|
|
{
|
|
$encodings[] = 'gzip';
|
|
}
|
|
if (function_exists('gzuncompress') && !in_array('deflate', $encodings))
|
|
{
|
|
$encodings[] = 'deflate';
|
|
}
|
|
|
|
$headers['Accept-Encoding'] = implode(',', array_unique($encodings));
|
|
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* Validates supplied headers and turns all names to lowercase.
|
|
*
|
|
* @param array $headers
|
|
* @return array
|
|
*/
|
|
protected function fixHeaders($headers)
|
|
{
|
|
$fixed_headers = array();
|
|
foreach ($headers as $name => $value)
|
|
{
|
|
if (!preg_match('/([a-z]*)(-[a-z]*)*/i', $name))
|
|
{
|
|
$msg = sprintf('Invalid header "%s"', $name);
|
|
throw new Exception($msg);
|
|
}
|
|
$fixed_headers[$this->normalizeHeaderName($name)] = trim($value);
|
|
}
|
|
|
|
return $fixed_headers;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a normalized Header.
|
|
*
|
|
* @param string Header name
|
|
*
|
|
* @return string Normalized header
|
|
*/
|
|
protected function normalizeHeaderName($name)
|
|
{
|
|
return preg_replace_callback('/\-(.)/', function($m) { return '-'.strtoupper($m[1]); }, strtr(ucfirst($name), '_', '-'));
|
|
}
|
|
|
|
}
|