<?php
/* Fetch a remote file */
class gpRemoteGet{
/* determine if the functions exist for fetching remote files,
* test is done in order of preference
*
* notes from wordpress http.php
* The order for the GET/HEAD requests are Streams, HTTP Extension, Fopen,
* and finally Fsockopen. fsockopen() is used last, because it has the most
* overhead in its implementation. There isn't any real way around it, since
* redirects have to be supported, much the same way the other transports
* also handle redirects.
*
* @return mixed string indicates the method, False indicates it's not compatible
*/
function Test(){
/*
//http_request
if( function_exists('http_request') ){
return 'http_request';
}
*/
if( function_exists('ini_get') && (ini_get('allow_url_fopen') == true) ){
//streams
if( version_compare(phpversion(), '5.0', '>=') ){
return 'stream';
}
//fopen
return 'fopen';
}
//fsockopen
if( function_exists('fsockopen') ){
return 'fsockopen';
}
return false;
}
//return response only if successful, otherwise return false
function Get_Successful($url,$args=array()){
$result = gpRemoteGet::Get($url,$args);
if( (int)$result['response']['code'] >= 200 && (int)$result['response']['code'] < 300 ){
return $result['body'];
}
return false;
}
function Get($url,$args=array()){
global $langmessage;
//arguments
$defaults = array(
'method' => 'GET',
'timeout' => 5,
'redirection' => 5,
'httpversion' => '1.0',
'user-agent' => 'gpEasy RemoteGet'
//could be added
//'blocking' => true,
//'headers' => array(),
//'body' => null,
//'cookies' => array(),
);
$args += $defaults;
//check url
$url = str_replace(' ','%20',$url); //spaces in the url can make the request fail
if( parse_url($url) === false ){
return false;
}
//decide how to get
$method = gpRemoteGet::Test();
switch($method){
/*
case 'http_request':
return gpRemoteGet::http_request($url,$args);
*/
case 'stream':
return gpRemoteGet::stream_request($url,$args);
case 'fopen':
return gpRemoteGet::fopen_request($url,$args);
case 'fsockopen':
return gpRemoteGet::fsockopen_request($url,$args);
default:
//message($langmessage['OOPS']);
return false;
}
}
function http_request($url,$r){
}
function stream_request($url,$r){
$arrURL = parse_url($url);
if( ($arrURL['scheme'] != 'http') && ($arrURL['scheme'] != 'https') ){
$url = preg_replace('|^' . preg_quote($arrURL['scheme'], '|') . '|', 'http', $url);
}
//create context
$arrContext = array();
$arrContext['http'] = array(
'method' => 'GET',
'user_agent' => $r['user-agent'],
'max_redirects' => $r['redirection'],
'protocol_version' => (float) $r['httpversion'],
'timeout' => $r['timeout'],
);
$context = stream_context_create($arrContext);
if( gpdebug ){
$handle = fopen($url, 'r', false, $context);
}else{
$handle = @fopen($url, 'r', false, $context);
}
if ( !$handle ) return false;
gpRemoteGet::stream_timeout($handle,$r['timeout']);
$strResponse = stream_get_contents($handle);
$theHeaders = gpRemoteGet::StreamHeaders($handle);
fclose($handle);
$processedHeaders = gpRemoteGet::processHeaders($theHeaders);
$strResponse = gpRemoteGet::chunkTransferDecode($strResponse,$processedHeaders);
return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']);
}
function fopen_request($url,$r){
$arrURL = parse_url($url);
if( ($arrURL['scheme'] != 'http') && ($arrURL['scheme'] != 'https') ){
$url = preg_replace('|^' . preg_quote($arrURL['scheme'], '|') . '|', 'http', $url);
}
if( gpdebug ){
$handle = fopen($url, 'r');
}else{
$handle = @fopen($url, 'r');
}
if ( !$handle ) return false;
gpRemoteGet::stream_timeout($handle,$r['timeout']);
$strResponse = '';
while ( ! feof($handle) ){
$strResponse .= fread($handle, 4096);
}
$theHeaders = gpRemoteGet::StreamHeaders($handle);
if( $theHeaders === false ){
//$http_response_header is a PHP reserved variable which is set in the current-scope when using the HTTP Wrapper
//see http://php.oregonstate.edu/manual/en/reserved.variables.httpresponseheader.php
$theHeaders = $http_response_header;
}
fclose($handle);
$processedHeaders = gpRemoteGet::processHeaders($theHeaders);
$strResponse = gpRemoteGet::chunkTransferDecode($strResponse,$processedHeaders);
return array('headers' => $processedHeaders['headers'], 'body' => $strResponse, 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']);
}
function fsockopen_request($url,$r){
$arrURL = parse_url($url);
$fsockopen_host = $arrURL['host'];
if( !isset($arrURL['port']) ){
$arrURL['port'] = 80;
}
//fsockopen has issues with 'localhost' with IPv6 with certain versions of PHP, It attempts to connect to ::1,
// which fails when the server is not setup for it. For compatibility, always connect to the IPv4 address.
if ( 'localhost' == strtolower($fsockopen_host) )
$fsockopen_host = '127.0.0.1';
$iError = null; // Store error number
$strError = null; // Store error string
if( gpdebug ){
$handle = fsockopen( $fsockopen_host, $arrURL['port'], $iError, $strError, $r['timeout'] );
}else{
$handle = @fsockopen( $fsockopen_host, $arrURL['port'], $iError, $strError, $r['timeout'] );
}
if( $handle === false ){
return false;
}
gpRemoteGet::stream_timeout($handle,$r['timeout']);
$requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' );
if ( empty($requestPath) )
$requestPath .= '/';
$strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n";
$strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n";
if ( isset($r['user-agent']) )
$strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n";
$strHeaders .= "\r\n";
fwrite($handle, $strHeaders);
$strResponse = '';
while ( ! feof($handle) )
$strResponse .= fread($handle, 4096);
fclose($handle);
$process = gpRemoteGet::processResponse($strResponse);
$processedHeaders = gpRemoteGet::processHeaders($process['headers']);
// If location is found, then assume redirect and redirect to location.
if ( isset($processedHeaders['headers']['location']) ) {
if( $r['redirection']-- > 0 ){
//check location for releative value
$location = $processedHeaders['headers']['location'];
if( $location{0} = '/' ){
$location = $arrURL['scheme'].'://'.$arrURL['host'].$location;
}
return gpRemoteGet::Get($location, $r);
}else{
trigger_error('Too many redirects');
return false;
}
}
$strResponse = gpRemoteGet::chunkTransferDecode($strResponse,$processedHeaders);
return array('headers' => $processedHeaders['headers'], 'body' => $process['body'], 'response' => $processedHeaders['response'], 'cookies' => $processedHeaders['cookies']);
}
function stream_timeout($handle,$time){
if( !function_exists('stream_set_timeout') ){
return;
}
$timeout = (int) floor( $time );
$utimeout = $timeout == $time ? 0 : 1000000 * $time % 1000000;
stream_set_timeout( $handle, $timeout, $utimeout );
}
/**
* Decodes chunk transfer-encoding, based off the HTTP 1.1 specification.
*
* Based off the HTTP http_encoding_dechunk function. Does not support UTF-8. Does not support
* returning footer headers. Shouldn't be too difficult to support it though.
*
* @todo Add support for footer chunked headers.
* @access public
* @since 1.7
* @static
*
* @param string $body Body content
* @return string Chunked decoded body on success or raw body on failure.
*/
function chunkTransferDecode($body,&$headers){
if( empty($body) ){
return $body;
}
if( !isset( $headers['headers']['transfer-encoding'] ) || 'chunked' != $headers['headers']['transfer-encoding'] ){
return $body;
}
$body = str_replace(array("\r\n", "\r"), "\n", $body);
// The body is not chunked encoding or is malformed.
if ( ! preg_match( '/^[0-9a-f]+(\s|\n)+/mi', trim($body) ) )
return $body;
$parsedBody = '';
//$parsedHeaders = array(); Unsupported
while ( true ) {
$hasChunk = (bool) preg_match( '/^([0-9a-f]+)(\s|\n)+/mi', $body, $match );
if ( $hasChunk ) {
if ( empty( $match[1] ) )
return $body;
$length = hexdec( $match[1] );
$chunkLength = strlen( $match[0] );
$strBody = substr($body, $chunkLength, $length);
$parsedBody .= $strBody;
$body = ltrim(str_replace(array($match[0], $strBody), '', $body), "\n");
if ( "0" == trim($body) )
return $parsedBody; // Ignore footer headers.
} else {
return $body;
}
}
}
/**
* Gets stream headers, return false otherwise
*
* @access public
* @static
* @since 1.7
*
* @param resource $handle stream handle
* @return array|false Array with unprocessed string headers.
*/
function StreamHeaders($handle){
if( !function_exists('stream_get_meta_data') ){
return false;
}
$meta = stream_get_meta_data($handle);
$theHeaders = $meta['wrapper_data'];
if( isset($meta['wrapper_data']['headers']) ){
$theHeaders = $meta['wrapper_data']['headers'];
}
return $theHeaders;
}
/**
* Parses the responses and splits the parts into headers and body.
*
* @access public
* @static
* @since 1.7
*
* @param string $strResponse The full response string
* @return array Array with 'headers' and 'body' keys.
*/
function processResponse($strResponse) {
list($theHeaders, $theBody) = explode("\r\n\r\n", $strResponse, 2);
return array('headers' => $theHeaders, 'body' => $theBody);
}
/**
* Transform header string into an array.
*
* If an array is given then it is assumed to be raw header data with numeric keys with the
* headers as the values. No headers must be passed that were already processed.
*
* @access public
* @static
* @since 1.7
*
* @param string|array $headers
* @return array Processed string headers. If duplicate headers are encountered,
* Then a numbered array is returned as the value of that header-key.
*/
function processHeaders($headers) {
// split headers, one per array element
if ( is_string($headers) ) {
// tolerate line terminator: CRLF = LF (RFC 2616 19.3)
$headers = str_replace("\r\n", "\n", $headers);
// unfold folded header fields. LWS = [CRLF] 1*( SP | HT ) <US-ASCII SP, space (32)>, <US-ASCII HT, horizontal-tab (9)> (RFC 2616 2.2)
$headers = preg_replace('/\n[ \t]/', ' ', $headers);
// create the headers array
$headers = explode("\n", $headers);
}
$response = array('code' => 0, 'message' => '');
$cookies = array();
$newheaders = array();
foreach ( $headers as $tempheader ) {
if ( empty($tempheader) )
continue;
if ( false === strpos($tempheader, ':') ) {
list( , $iResponseCode, $strResponseMsg) = explode(' ', $tempheader, 3);
$response['code'] = $iResponseCode;
$response['message'] = $strResponseMsg;
continue;
}
list($key, $value) = explode(':', $tempheader, 2);
if ( !empty( $value ) ) {
$key = strtolower( $key );
if ( isset( $newheaders[$key] ) ) {
$newheaders[$key] = array( $newheaders[$key], trim( $value ) );
} else {
$newheaders[$key] = trim( $value );
}
}
}
return array('response' => $response, 'headers' => $newheaders, 'cookies' => $cookies);
}
}