@@ -54,7 +54,7 @@ class PhpZenfolioException extends Exception {}
5454 * @package phpZenfolio
5555 **/
5656class 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
10721094class 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
11931213class 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