Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 4842ea3

Browse files
committed
Update remote request libraries with the functionality and enhancements brought into phpSmug. Also make it easier to reference the version so I only need to set it in one place.
1 parent 94796d8 commit 4842ea3

File tree

1 file changed

+115
-112
lines changed

1 file changed

+115
-112
lines changed

phpZenfolio.php

Lines changed: 115 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class PhpZenfolioException extends Exception {}
5454
* @package phpZenfolio
5555
**/
5656
class phpZenfolio {
57-
var $version = '1.1';
57+
static $version = '1.1';
5858
private $cacheType = FALSE;
5959
private $cache_expire = 3600;
6060
private $keyring;
@@ -109,8 +109,8 @@ public function __construct()
109109
// All calls to the API are done via POST using my own constructed httpRequest class
110110
$this->req = new httpRequest();
111111
$this->req->setConfig( array( 'adapter' => $this->adapter, 'follow_redirects' => TRUE, 'max_redirects' => 3, 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE, 'connect_timeout' => 5 ) );
112-
$this->req->setHeader( array( 'User-Agent' => "{$this->AppName} using phpZenfolio/{$this->version}",
113-
'X-Zenfolio-User-Agent' => "{$this->AppName} using phpZenfolio/{$this->version}",
112+
$this->req->setHeader( array( 'User-Agent' => "{$this->AppName} using phpZenfolio/" . phpZenfolio::$version,
113+
'X-Zenfolio-User-Agent' => "{$this->AppName} using phpZenfolio/" . phpZenfolio::$version,
114114
'Content-Type' => 'application/json' ) );
115115
}
116116

@@ -499,8 +499,8 @@ public function upload()
499499
$upload_req = new httpRequest();
500500
$upload_req->setConfig( array( 'adapter' => $this->adapter, 'follow_redirects' => TRUE, 'max_redirects' => 3, 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE, 'connect_timeout' => 60 ) );
501501
$upload_req->setMethod( 'post' );
502-
$upload_req->setHeader( array( 'User-Agent' => "{$this->AppName} using phpZenfolio/{$this->version}",
503-
'X-Zenfolio-User-Agent' => "{$this->AppName} using phpZenfolio/{$this->version}",
502+
$upload_req->setHeader( array( 'User-Agent' => "{$this->AppName} using phpZenfolio/" . phpZenfolio::$version,
503+
'X-Zenfolio-User-Agent' => "{$this->AppName} using phpZenfolio/" . phpZenfolio::$version,
504504
'Content-Type' => $fileinfo['mime'],
505505
'Content-Length' => filesize( $args['File'] ) ) );
506506

@@ -742,8 +742,6 @@ class httpRequest
742742
private $response_body = '';
743743
private $response_headers = '';
744744

745-
private $user_agent = "Unknown application using phpZenfolio/1.0";
746-
747745
/**
748746
* Adapter Configuration parameters
749747
* @var array
@@ -763,7 +761,7 @@ class httpRequest
763761

764762
// TODO: These don't apply to SocketRequestProcessor yet
765763
'ssl_verify_peer' => FALSE,
766-
'ssl_verify_host' => FALSE,
764+
'ssl_verify_host' => 2, // 1 = check CN of ssl cert, 2 = check and verify @see http://php.net/curl_setopt
767765
'ssl_cafile' => NULL,
768766
'ssl_capath' => NULL,
769767
'ssl_local_cert' => NULL,
@@ -783,7 +781,7 @@ public function __construct( $url = NULL, $method = 'POST', $timeout = 30 )
783781
$this->method = strtoupper( $method );
784782
$this->url = $url;
785783
$this->setTimeout( $timeout );
786-
$this->setHeader( array( 'User-Agent' => $this->user_agent ) );
784+
$this->setHeader( array( 'User-Agent' => "Unknown application using phpZenfolio/" . phpZenfolio::$version ) );
787785

788786
// can't use curl's followlocation in safe_mode with open_basedir, so fallback to socket for now
789787
if ( function_exists( 'curl_init' ) && ( $this->config['adapter'] == 'curl' )
@@ -905,6 +903,14 @@ public function setAdapter( $adapter )
905903
$adapter = strtolower( $adapter );
906904
if ( $adapter == 'curl' || $adapter == 'socket' ) {
907905
$this->config['adapter'] = $adapter;
906+
// We need to reset the processor too. This is quite crude and messy, but we need to do it.
907+
if ( function_exists( 'curl_init' ) && ( $adapter == 'curl' )
908+
&& ! ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) ) {
909+
$this->processor = new PhpZenfolioCurlRequestProcessor;
910+
}
911+
else {
912+
$this->processor = new PhpZenfolioSocketRequestProcessor;
913+
}
908914
}
909915
}
910916

@@ -927,6 +933,14 @@ public function getParams()
927933
{
928934
return $this->params;
929935
}
936+
937+
/**
938+
* Get the current configuration. This is more for unit testing purposes
939+
*/
940+
public function getConfig()
941+
{
942+
return $this->config;
943+
}
930944

931945
/**
932946
* Set the destination url
@@ -1027,10 +1041,19 @@ private function prepare()
10271041
$this->setHeader( array( 'Content-Type' => 'application/x-www-form-urlencoded' ) );
10281042
}
10291043
if ( $this->headers['Content-Type'] == 'application/x-www-form-urlencoded' || $this->headers['Content-Type'] == 'application/json' ) {
1030-
if( $this->body != '' && count( $this->postdata ) > 0 ) {
1044+
$count = count( $this->postdata );
1045+
if( $this->body != '' && $count > 0 ) {
10311046
$this->body .= '&';
10321047
}
1033-
$this->body .= http_build_query( $this->postdata, '', '&' );
1048+
//$this->body .= http_build_query( $this->postdata, '', '&' );
1049+
// We don't use http_build_query() as it converts empty array values to 0, which we don't want.
1050+
foreach ( $this->postdata as $key => $value ) {
1051+
$count--;
1052+
$this->body .= $key . '=' . $value;
1053+
if ( $count ) {
1054+
$this->body .= '&';
1055+
}
1056+
}
10341057
}
10351058
$this->setHeader( array( 'Content-Length' => strlen( $this->body ) ) );
10361059
}
@@ -1066,8 +1089,7 @@ private function mergeQueryParams( $url, $params )
10661089

10671090
}
10681091

1069-
?>
1070-
<?php
1092+
10711093

10721094
class PhpZenfolioCurlRequestProcessor implements PhpZenfolioRequestProcessor
10731095
{
@@ -1103,11 +1125,10 @@ public function execute( $method, $url, $headers, $body, $config )
11031125
CURLOPT_SSL_VERIFYHOST => $config['ssl_verify_host'],
11041126
CURLOPT_BUFFERSIZE => $config['buffer_size'],
11051127
CURLOPT_HTTPHEADER => $merged_headers,
1106-
CURLOPT_FOLLOWLOCATION => TRUE,
11071128
CURLOPT_RETURNTRANSFER => TRUE,
11081129
);
11091130

1110-
if ( $this->can_followlocation ) {
1131+
if ( $this->can_followlocation && $config['follow_redirects'] ) {
11111132
$options[CURLOPT_FOLLOWLOCATION] = TRUE; // Follow 302's and the like.
11121133
}
11131134

@@ -1124,13 +1145,13 @@ public function execute( $method, $url, $headers, $body, $config )
11241145
}
11251146

11261147
// set proxy, if needed
1127-
if ( $host = $config['proxy_host'] ) {
1128-
if ( ! ( $port = $config['proxy_port'] ) ) {
1148+
if ( $config['proxy_host'] ) {
1149+
if ( ! $config['proxy_port'] ) {
11291150
throw new HttpRequestException( 'Proxy port not provided' );
11301151
}
1131-
curl_setopt( $ch, CURLOPT_PROXY, $host . ':' . $port );
1132-
if ( $user = $config['proxy_user'] ) {
1133-
curl_setopt( $ch, CURLOPT_PROXYUSERPWD, $user . ':' . $config['proxy_password'] );
1152+
$options[CURLOPT_PROXY] = $config['proxy_host'] . ':' . $config['proxy_port'];
1153+
if ( $config['proxy_user'] ) {
1154+
$options[CURLOPT_PROXYUSERPWD] = $config['proxy_user'] . ':' . $config['proxy_password'];
11341155
switch ( strtolower( $config['proxy_auth_scheme'] ) ) {
11351156
case 'basic':
11361157
curl_setopt( $ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC );
@@ -1187,128 +1208,110 @@ public function getHeaders()
11871208
}
11881209
}
11891210

1190-
?>
1191-
<?php
1211+
11921212

11931213
class PhpZenfolioSocketRequestProcessor implements PhpZenfolioRequestProcessor
11941214
{
11951215
private $response_body = '';
11961216
private $response_headers = '';
11971217
private $executed = FALSE;
11981218
private $redir_count = 0;
1199-
1200-
public function execute( $method, $url, $headers, $body, $config )
1201-
{
1202-
$result = $this->_request( $method, $url, $headers, $body, $config );
1203-
1204-
if ( $result ) {
1205-
list( $response_headers, $response_body ) = $result;
1206-
$this->response_headers = $response_headers;
1207-
$this->response_body = $response_body;
1208-
$this->executed = TRUE;
1209-
1210-
return TRUE;
1211-
}
1212-
else {
1213-
// TODO: Create unit test to test this
1214-
return $result;
1215-
}
1219+
private $can_followlocation = true;
1220+
1221+
public function __construct ( )
1222+
{
1223+
// see if we can follow Location: headers
1224+
if ( ini_get( 'safe_mode' ) || ini_get( 'open_basedir' ) ) {
1225+
$this->can_followlocation = false;
1226+
}
12161227
}
1217-
1218-
private function _request( $method, $url, $headers, $body, $config )
1219-
{
1220-
$_errno = 0;
1221-
$_errstr = '';
1222-
$urlbits = parse_url( $url );
1223-
1224-
if ( !isset( $urlbits['port'] ) || $urlbits['port'] == 0 ) {
1225-
if ( $urlbits['scheme'] == 'https' ) {
1226-
$urlbits['port'] = 443;
1227-
$transport = 'ssl';
1228-
}
1229-
else {
1230-
$urlbits['port'] = 80;
1231-
$transport = 'tcp';
1232-
}
1233-
}
1234-
1235-
if ( $config['proxy_host'] != '' ) {
1236-
// TODO: Finish the implementation of proxy support for socket connections. Until then, only curl has proxy support.
1237-
throw new HttpRequestException( 'The "socket" adapter type does NOT currently support connecting via a proxy. Please use the "curl" adapter type.', -1 );
1238-
$fp = @fsockopen( $transport . '://' . $config['proxy_host'], $config['proxy_port'], $_errno, $_errstr, $config['connect_timeout'] );
1239-
} else {
1240-
$fp = @fsockopen( $transport . '://' . $urlbits['host'], $urlbits['port'], $_errno, $_errstr, $config['connect_timeout'] );
1241-
}
1242-
1243-
if ( $fp === FALSE ) {
1244-
throw new HttpRequestException( sprintf( '%s: Error %d: %s while connecting to %s:%d', __CLASS__, $_errno, $_errstr, $urlbits['host'], $urlbits['port'] ), $_errno );
1245-
}
1246-
1247-
stream_set_timeout( $fp, $config['timeout'] );
1248-
1249-
// fix headers
1250-
$headers['Host'] = $urlbits['host'];
1251-
$headers['Connection'] = 'close';
1252-
1253-
// merge headers into a list
1228+
1229+
public function execute ( $method, $url, $headers, $body, $config )
1230+
{
12541231
$merged_headers = array();
12551232
foreach ( $headers as $k => $v ) {
1256-
$merged_headers[] = $k . ': ' . $v;
1233+
$merged_headers[] = $k . ': '. $v;
12571234
}
12581235

1259-
// build the request
1260-
$request = array();
1261-
$resource = $urlbits['path'];
1262-
if ( isset( $urlbits['query'] ) ) {
1263-
$resource .= '?' . $urlbits['query'];
1264-
}
1236+
// parse out the URL so we can refer to individual pieces
1237+
$urlbits = parse_url( $url );
12651238

1266-
$request[] = "{$method} {$resource} HTTP/1.1";
1267-
$request = array_merge( $request, $merged_headers );
1268-
$request[] = '';
1239+
// set up the options we'll use when creating the request's context
1240+
$options = array(
1241+
'http' => array(
1242+
'method' => $method,
1243+
'header' => implode( "\n", $merged_headers ),
1244+
'timeout' => $config['timeout'],
1245+
'follow_location' => $this->can_followlocation, // 5.3.4+, should be ignored by others
1246+
'max_redirects' => $config['max_redirects'],
1247+
1248+
// and now for our ssl-specific portions, which will be ignored for non-HTTPS requests
1249+
'verify_peer' => $config['ssl_verify_peer'],
1250+
//'verify_host' => $config['ssl_verify_host'], // there doesn't appear to be an equiv of this for sockets - the host is matched by default and you can't just turn that off, only substitute other hostnames
1251+
'cafile' => $config['ssl_cafile'],
1252+
'capath' => $config['ssl_capath'],
1253+
'local_cert' => $config['ssl_local_cert'],
1254+
'passphrase' => $config['ssl_passphrase'],
1255+
),
1256+
);
12691257

1270-
if ( $method === 'POST' || $method === 'PUT' ) {
1271-
$request[] = $body;
1258+
if ( $method == 'POST' || $method == 'PUT' ) {
1259+
$options['http']['content'] = $body;
12721260
}
12731261

1274-
$request[] = '';
1262+
if ( $config['proxy_host'] != '' ) {
1263+
$proxy = $config['proxy_host'] . ':' . $config['proxy_port'];
1264+
if ( $config['proxy_user'] != '' ) {
1265+
$proxy = $config['proxy_user'] . ':' . $config['proxy_password'] . '@' . $proxy;
1266+
}
1267+
$options['http']['proxy'] = 'tcp://' . $proxy;
1268+
}
12751269

1276-
$out = implode( "\r\n", $request );
1270+
// create the context
1271+
$context = stream_context_create( $options );
12771272

1278-
if ( ! fwrite( $fp, $out, strlen( $out ) ) ) {
1279-
throw new HttpRequestException( 'Error writing to socket.' );
1273+
// perform the actual request - we use fopen so stream_get_meta_data works
1274+
$fh = @fopen( $url, 'r', false, $context );
1275+
if ( $fh === false ) {
1276+
throw new Exception( 'Unable to connect to ' . $urlbits['host'] );
12801277
}
12811278

1282-
$in = stream_get_contents( $fp );
1279+
// read in all the contents -- this is the same as file_get_contents, only for a specific stream handle
1280+
$body = stream_get_contents( $fh );
1281+
// get meta data
1282+
$meta = stream_get_meta_data( $fh );
12831283

1284-
fclose( $fp );
1284+
// close the connection before we do anything else
1285+
fclose( $fh );
12851286

1286-
list( $header, $body ) = explode( "\r\n\r\n", $in, 2 );
1287+
// did we timeout?
1288+
if ( $meta['timed_out'] == true ) {
1289+
throw new Exception( 'Request timed out' );
1290+
}
12871291

1288-
// to make the following REs match $ correctly and thus not break parse_url
1289-
$header = str_replace( "\r\n", "\n", $header );
1292+
// $meta['wrapper_data'] should be a list of the headers, the same as is loaded into $http_response_header
1293+
$headers = array();
1294+
foreach ( $meta['wrapper_data'] as $header ) {
12901295

1291-
preg_match( '#^HTTP/1\.[01] ([1-5][0-9][0-9]) ?(.*)#', $header, $status_matches );
1296+
// break the header up into field and value
1297+
$pieces = explode( ': ', $header, 2 );
12921298

1293-
if ( ( $status_matches[1] == '301' || $status_matches[1] == '302' ) && $config['follow_redirects'] ) {
1294-
if ( preg_match( '|^Location: (.+)$|mi', $header, $location_matches ) ) {
1295-
$redirect_url = $location_matches[1];
1296-
$this->redir_count++;
1297-
if ( $this->redir_count > $this->config['max_redirects'] ) {
1298-
throw new HttpRequestException( 'Maximum number of redirections exceeded.' );
1299-
}
1300-
return $this->_request( $method, $redirect_url, $headers, $body, $config );
1299+
if ( count( $pieces ) > 1 ) {
1300+
// if the header was a key: value format, store it keyed in the array
1301+
$headers[ $pieces[0] ] = $pieces[1];
13011302
}
13021303
else {
1303-
throw new HttpRequestException( 'Redirection response without Location: header.' );
1304+
// some headers (like the HTTP version in use) aren't keyed, so just store it keyed as itself
1305+
$headers[ $pieces[0] ] = $pieces[0];
13041306
}
1305-
}
13061307

1307-
if ( preg_match( '|^Transfer-Encoding:.*chunked.*|mi', $header ) ) {
1308-
$body = $this->_unchunk( $body );
13091308
}
13101309

1311-
return array( $header, $body );
1310+
$this->response_headers = $headers;
1311+
$this->response_body = $body;
1312+
$this->executed = true;
1313+
1314+
return true;
13121315
}
13131316

13141317
public function getBody()
@@ -1350,4 +1353,4 @@ private function _unchunk( $body )
13501353
}
13511354
}
13521355

1353-
?>
1356+
?>

0 commit comments

Comments
 (0)