Update google-api-php-client from 1.0.6-beta to 1.1.7

This commit is contained in:
Francesco Rovelli 2016-04-26 15:58:15 +02:00 committed by Vincent Petry
parent 4ffc936624
commit c1583f03ab
No known key found for this signature in database
GPG Key ID: AF8F9EFC56562186
33 changed files with 3660 additions and 1823 deletions

View File

@ -25,10 +25,13 @@ For the latest installation and setup instructions, see [the documentation](http
See the examples/ directory for examples of the key client features. See the examples/ directory for examples of the key client features.
```PHP ```PHP
<?php <?php
require_once 'google-api-php-client/autoload.php'; // or wherever autoload.php is located
require_once 'google-api-php-client/src/Google/autoload.php'; // or wherever autoload.php is located
$client = new Google_Client(); $client = new Google_Client();
$client->setApplicationName("Client_Library_Examples"); $client->setApplicationName("Client_Library_Examples");
$client->setDeveloperKey("YOUR_APP_KEY"); $client->setDeveloperKey("YOUR_APP_KEY");
$service = new Google_Service_Books($client); $service = new Google_Service_Books($client);
$optParams = array('filter' => 'free-ebooks'); $optParams = array('filter' => 'free-ebooks');
$results = $service->volumes->listVolumes('Henry David Thoreau', $optParams); $results = $service->volumes->listVolumes('Henry David Thoreau', $optParams);
@ -36,8 +39,13 @@ See the examples/ directory for examples of the key client features.
foreach ($results as $item) { foreach ($results as $item) {
echo $item['volumeInfo']['title'], "<br /> \n"; echo $item['volumeInfo']['title'], "<br /> \n";
} }
``` ```
### Service Specific Examples ###
YouTube: https://github.com/youtube/api-samples/tree/master/php
## Frequently Asked Questions ## ## Frequently Asked Questions ##
### What do I do if something isn't working? ### ### What do I do if something isn't working? ###
@ -50,6 +58,10 @@ If there is a specific bug with the library, please file a issue in the Github i
We accept contributions via Github Pull Requests, but all contributors need to be covered by the standard Google Contributor License Agreement. You can find links, and more instructions, in the documentation: https://developers.google.com/api-client-library/php/contribute We accept contributions via Github Pull Requests, but all contributors need to be covered by the standard Google Contributor License Agreement. You can find links, and more instructions, in the documentation: https://developers.google.com/api-client-library/php/contribute
### I want an example of X! ###
If X is a feature of the library, file away! If X is an example of using a specific service, the best place to go is to the teams for those specific APIs - our preference is to link to their examples rather than add them to the library, as they can then pin to specific versions of the library. If you have any examples for other APIs, let us know and we will happily add a link to the README above!
### Why do you still support 5.2? ### ### Why do you still support 5.2? ###
When we started working on the 1.0.0 branch we knew there were several fundamental issues to fix with the 0.6 releases of the library. At that time we looked at the usage of the library, and other related projects, and determined that there was still a large and active base of PHP 5.2 installs. You can see this in statistics such as the PHP versions chart in the WordPress stats: http://wordpress.org/about/stats/. We will keep looking at the types of usage we see, and try to take advantage of newer PHP features where possible. When we started working on the 1.0.0 branch we knew there were several fundamental issues to fix with the 0.6 releases of the library. At that time we looked at the usage of the library, and other related projects, and determined that there was still a large and active base of PHP 5.2 installs. You can see this in statistics such as the PHP versions chart in the WordPress stats: http://wordpress.org/about/stats/. We will keep looking at the types of usage we see, and try to take advantage of newer PHP features where possible.
@ -68,10 +80,26 @@ $opt_params = array(
); );
``` ```
### How do I set a field to null? ###
The library strips out nulls from the objects sent to the Google APIs as its the default value of all of the uninitialised properties. To work around this, set the field you want to null to Google_Model::NULL_VALUE. This is a placeholder that will be replaced with a true null when sent over the wire.
## Code Quality ## ## Code Quality ##
Copy the ruleset.xml in style/ into a new directory named GAPI/ in your Run the PHPUnit tests with PHPUnit. You can configure an API key and token in BaseTest.php to run all calls, but this will require some setup on the Google Developer Console.
/usr/share/php/PHP/CodeSniffer/Standards (or appropriate equivalent directory),
and run code sniffs with:
phpcs --standard=GAPI src/ phpunit tests/
### Coding Style
To check for coding style violations, run
```
vendor/bin/phpcs src --standard=style/ruleset.xml -np
```
To automatically fix (fixable) coding style violations, run
```
vendor/bin/phpcbf src --standard=style/ruleset.xml
```

View File

@ -14,7 +14,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Http/Request.php";
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* Abstract class for the Authentication in the API client * Abstract class for the Authentication in the API client

View File

@ -15,14 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Auth/OAuth2.php"; if (!class_exists('Google_Client')) {
require_once "Google/Signer/P12.php"; require_once dirname(__FILE__) . '/../autoload.php';
require_once "Google/Utils.php"; }
/** /**
* Credentials object used for OAuth 2.0 Signed JWT assertion grants. * Credentials object used for OAuth 2.0 Signed JWT assertion grants.
*
* @author Chirag Shah <chirags@google.com>
*/ */
class Google_Auth_AssertionCredentials class Google_Auth_AssertionCredentials
{ {

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Exception.php"; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Auth_Exception extends Google_Exception class Google_Auth_Exception extends Google_Exception
{ {

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Auth/Exception.php"; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* Class to hold information about an authenticated login. * Class to hold information about an authenticated login.

View File

@ -15,21 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Auth/Abstract.php"; if (!class_exists('Google_Client')) {
require_once "Google/Auth/AssertionCredentials.php"; require_once dirname(__FILE__) . '/../autoload.php';
require_once "Google/Auth/Exception.php"; }
require_once "Google/Auth/LoginTicket.php";
require_once "Google/Client.php";
require_once "Google/Http/Request.php";
require_once "Google/Utils.php";
require_once "Google/Verifier/Pem.php";
/** /**
* Authentication class that deals with the OAuth 2 web-server authentication flow * Authentication class that deals with the OAuth 2 web-server authentication flow
* *
* @author Chris Chabot <chabotc@google.com>
* @author Chirag Shah <chirags@google.com>
*
*/ */
class Google_Auth_OAuth2 extends Google_Auth_Abstract class Google_Auth_OAuth2 extends Google_Auth_Abstract
{ {
@ -40,6 +32,7 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
const OAUTH2_ISSUER = 'accounts.google.com'; const OAUTH2_ISSUER = 'accounts.google.com';
const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
/** @var Google_Auth_AssertionCredentials $assertionCredentials */ /** @var Google_Auth_AssertionCredentials $assertionCredentials */
private $assertionCredentials; private $assertionCredentials;
@ -86,28 +79,34 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
/** /**
* @param string $code * @param string $code
* @param boolean $crossClient
* @throws Google_Auth_Exception * @throws Google_Auth_Exception
* @return string * @return string
*/ */
public function authenticate($code) public function authenticate($code, $crossClient = false)
{ {
if (strlen($code) == 0) { if (strlen($code) == 0) {
throw new Google_Auth_Exception("Invalid code"); throw new Google_Auth_Exception("Invalid code");
} }
$arguments = array(
'code' => $code,
'grant_type' => 'authorization_code',
'client_id' => $this->client->getClassConfig($this, 'client_id'),
'client_secret' => $this->client->getClassConfig($this, 'client_secret')
);
if ($crossClient !== true) {
$arguments['redirect_uri'] = $this->client->getClassConfig($this, 'redirect_uri');
}
// We got here from the redirect from a successful authorization grant, // We got here from the redirect from a successful authorization grant,
// fetch the access token // fetch the access token
$request = new Google_Http_Request( $request = new Google_Http_Request(
self::OAUTH2_TOKEN_URI, self::OAUTH2_TOKEN_URI,
'POST', 'POST',
array(), array(),
array( $arguments
'code' => $code,
'grant_type' => 'authorization_code',
'redirect_uri' => $this->client->getClassConfig($this, 'redirect_uri'),
'client_id' => $this->client->getClassConfig($this, 'client_id'),
'client_secret' => $this->client->getClassConfig($this, 'client_secret')
)
); );
$request->disableGzip(); $request->disableGzip();
$response = $this->client->getIo()->makeRequest($request); $response = $this->client->getIo()->makeRequest($request);
@ -119,15 +118,15 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
} else { } else {
$decodedResponse = json_decode($response->getResponseBody(), true); $decodedResponse = json_decode($response->getResponseBody(), true);
if ($decodedResponse != null && $decodedResponse['error']) { if ($decodedResponse != null && $decodedResponse['error']) {
$decodedResponse = $decodedResponse['error']; $errorText = $decodedResponse['error'];
if (isset($decodedResponse['error_description'])) { if (isset($decodedResponse['error_description'])) {
$decodedResponse .= ": " . $decodedResponse['error_description']; $errorText .= ": " . $decodedResponse['error_description'];
} }
} }
throw new Google_Auth_Exception( throw new Google_Auth_Exception(
sprintf( sprintf(
"Error fetching OAuth2 access token, message: '%s'", "Error fetching OAuth2 access token, message: '%s'",
$decodedResponse $errorText
), ),
$response->getResponseHttpCode() $response->getResponseHttpCode()
); );
@ -151,11 +150,15 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
'access_type' => $this->client->getClassConfig($this, 'access_type'), 'access_type' => $this->client->getClassConfig($this, 'access_type'),
); );
// Prefer prompt to approval prompt.
if ($this->client->getClassConfig($this, 'prompt')) {
$params = $this->maybeAddParam($params, 'prompt');
} else {
$params = $this->maybeAddParam($params, 'approval_prompt'); $params = $this->maybeAddParam($params, 'approval_prompt');
}
$params = $this->maybeAddParam($params, 'login_hint'); $params = $this->maybeAddParam($params, 'login_hint');
$params = $this->maybeAddParam($params, 'hd'); $params = $this->maybeAddParam($params, 'hd');
$params = $this->maybeAddParam($params, 'openid.realm'); $params = $this->maybeAddParam($params, 'openid.realm');
$params = $this->maybeAddParam($params, 'prompt');
$params = $this->maybeAddParam($params, 'include_granted_scopes'); $params = $this->maybeAddParam($params, 'include_granted_scopes');
// If the list of scopes contains plus.login, add request_visible_actions // If the list of scopes contains plus.login, add request_visible_actions
@ -236,17 +239,21 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
if ($this->assertionCredentials) { if ($this->assertionCredentials) {
$this->refreshTokenWithAssertion(); $this->refreshTokenWithAssertion();
} else { } else {
$this->client->getLogger()->debug('OAuth2 access token expired');
if (! array_key_exists('refresh_token', $this->token)) { if (! array_key_exists('refresh_token', $this->token)) {
throw new Google_Auth_Exception( $error = "The OAuth 2.0 access token has expired,"
"The OAuth 2.0 access token has expired,"
." and a refresh token is not available. Refresh tokens" ." and a refresh token is not available. Refresh tokens"
." are not returned for responses that were auto-approved." ." are not returned for responses that were auto-approved.";
);
$this->client->getLogger()->error($error);
throw new Google_Auth_Exception($error);
} }
$this->refreshToken($this->token['refresh_token']); $this->refreshToken($this->token['refresh_token']);
} }
} }
$this->client->getLogger()->debug('OAuth2 authentication');
// Add the OAuth2 header to the request // Add the OAuth2 header to the request
$request->setRequestHeaders( $request->setRequestHeaders(
array('Authorization' => 'Bearer ' . $this->token['access_token']) array('Authorization' => 'Bearer ' . $this->token['access_token'])
@ -298,6 +305,7 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
} }
} }
$this->client->getLogger()->debug('OAuth2 access token expired');
$this->refreshTokenRequest( $this->refreshTokenRequest(
array( array(
'grant_type' => 'assertion', 'grant_type' => 'assertion',
@ -317,6 +325,14 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
private function refreshTokenRequest($params) private function refreshTokenRequest($params)
{ {
if (isset($params['assertion'])) {
$this->client->getLogger()->info(
'OAuth2 access token refresh with Signed JWT assertion grants.'
);
} else {
$this->client->getLogger()->info('OAuth2 access token refresh');
}
$http = new Google_Http_Request( $http = new Google_Http_Request(
self::OAUTH2_TOKEN_URI, self::OAUTH2_TOKEN_URI,
'POST', 'POST',
@ -414,7 +430,9 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
/** /**
* Retrieve and cache a certificates file. * Retrieve and cache a certificates file.
* @param $url location *
* @param $url string location
* @throws Google_Auth_Exception
* @return array certificates * @return array certificates
*/ */
public function retrieveCertsFromLocation($url) public function retrieveCertsFromLocation($url)
@ -471,18 +489,24 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
$audience = $this->client->getClassConfig($this, 'client_id'); $audience = $this->client->getClassConfig($this, 'client_id');
} }
return $this->verifySignedJwtWithCerts($id_token, $certs, $audience, self::OAUTH2_ISSUER); return $this->verifySignedJwtWithCerts(
$id_token,
$certs,
$audience,
array(self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS)
);
} }
/** /**
* Verifies the id token, returns the verified token contents. * Verifies the id token, returns the verified token contents.
* *
* @param $jwt the token * @param $jwt string the token
* @param $certs array of certificates * @param $certs array of certificates
* @param $required_audience the expected consumer of the token * @param $required_audience string the expected consumer of the token
* @param [$issuer] the expected issues, defaults to Google * @param [$issuer] the expected issues, defaults to Google
* @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
* @return token information if valid, false if not * @throws Google_Auth_Exception
* @return mixed token information if valid, false if not
*/ */
public function verifySignedJwtWithCerts( public function verifySignedJwtWithCerts(
$jwt, $jwt,
@ -577,13 +601,15 @@ class Google_Auth_OAuth2 extends Google_Auth_Abstract
); );
} }
// support HTTP and HTTPS issuers
// @see https://developers.google.com/identity/sign-in/web/backend-auth
$iss = $payload['iss']; $iss = $payload['iss'];
if ($issuer && $iss != $issuer) { if ($issuer && !in_array($iss, (array) $issuer)) {
throw new Google_Auth_Exception( throw new Google_Auth_Exception(
sprintf( sprintf(
"Invalid issuer, %s != %s: %s", "Invalid issuer, %s not in %s: %s",
$iss, $iss,
$issuer, "[".implode(",", (array) $issuer)."]",
$json_body $json_body
) )
); );

View File

@ -15,19 +15,17 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Auth/Abstract.php"; if (!class_exists('Google_Client')) {
require_once "Google/Http/Request.php"; require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* Simple API access implementation. Can either be used to make requests * Simple API access implementation. Can either be used to make requests
* completely unauthenticated, or by using a Simple API Access developer * completely unauthenticated, or by using a Simple API Access developer
* key. * key.
* @author Chris Chabot <chabotc@google.com>
* @author Chirag Shah <chirags@google.com>
*/ */
class Google_Auth_Simple extends Google_Auth_Abstract class Google_Auth_Simple extends Google_Auth_Abstract
{ {
private $key = null;
private $client; private $client;
public function __construct(Google_Client $client, $config = null) public function __construct(Google_Client $client, $config = null)
@ -55,6 +53,9 @@ class Google_Auth_Simple extends Google_Auth_Abstract
{ {
$key = $this->client->getClassConfig($this, 'developer_key'); $key = $this->client->getClassConfig($this, 'developer_key');
if ($key) { if ($key) {
$this->client->getLogger()->debug(
'Simple API Access developer key authentication'
);
$request->setQueryParam('key', $key); $request->setQueryParam('key', $key);
} }
return $request; return $request;

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Cache/Abstract.php"; if (!class_exists('Google_Client')) {
require_once "Google/Cache/Exception.php"; require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* A persistent storage class based on the APC cache, which is not * A persistent storage class based on the APC cache, which is not
@ -28,11 +29,21 @@ require_once "Google/Cache/Exception.php";
*/ */
class Google_Cache_Apc extends Google_Cache_Abstract class Google_Cache_Apc extends Google_Cache_Abstract
{ {
/**
* @var Google_Client the current client
*/
private $client;
public function __construct(Google_Client $client) public function __construct(Google_Client $client)
{ {
if (! function_exists('apc_add') ) { if (! function_exists('apc_add') ) {
throw new Google_Cache_Exception("Apc functions not available"); $error = "Apc functions not available";
$client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
} }
$this->client = $client;
} }
/** /**
@ -42,12 +53,26 @@ class Google_Cache_Apc extends Google_Cache_Abstract
{ {
$ret = apc_fetch($key); $ret = apc_fetch($key);
if ($ret === false) { if ($ret === false) {
$this->client->getLogger()->debug(
'APC cache miss',
array('key' => $key)
);
return false; return false;
} }
if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) { if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
$this->client->getLogger()->debug(
'APC cache miss (expired)',
array('key' => $key, 'var' => $ret)
);
$this->delete($key); $this->delete($key);
return false; return false;
} }
$this->client->getLogger()->debug(
'APC cache hit',
array('key' => $key, 'var' => $ret)
);
return $ret['data']; return $ret['data'];
} }
@ -56,10 +81,21 @@ class Google_Cache_Apc extends Google_Cache_Abstract
*/ */
public function set($key, $value) public function set($key, $value)
{ {
$rc = apc_store($key, array('time' => time(), 'data' => $value)); $var = array('time' => time(), 'data' => $value);
$rc = apc_store($key, $var);
if ($rc == false) { if ($rc == false) {
$this->client->getLogger()->error(
'APC cache set failed',
array('key' => $key, 'var' => $var)
);
throw new Google_Cache_Exception("Couldn't store data"); throw new Google_Cache_Exception("Couldn't store data");
} }
$this->client->getLogger()->debug(
'APC cache set',
array('key' => $key, 'var' => $var)
);
} }
/** /**
@ -68,6 +104,10 @@ class Google_Cache_Apc extends Google_Cache_Abstract
*/ */
public function delete($key) public function delete($key)
{ {
$this->client->getLogger()->debug(
'APC cache delete',
array('key' => $key)
);
apc_delete($key); apc_delete($key);
} }
} }

View File

@ -14,7 +14,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Exception.php";
if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Cache_Exception extends Google_Exception class Google_Cache_Exception extends Google_Exception
{ {

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Cache/Abstract.php"; if (!class_exists('Google_Client')) {
require_once "Google/Cache/Exception.php"; require_once dirname(__FILE__) . '/../autoload.php';
}
/* /*
* This class implements a basic on disk storage. While that does * This class implements a basic on disk storage. While that does
@ -32,9 +33,15 @@ class Google_Cache_File extends Google_Cache_Abstract
private $path; private $path;
private $fh; private $fh;
/**
* @var Google_Client the current client
*/
private $client;
public function __construct(Google_Client $client) public function __construct(Google_Client $client)
{ {
$this->path = $client->getClassConfig($this, 'directory'); $this->client = $client;
$this->path = $this->client->getClassConfig($this, 'directory');
} }
public function get($key, $expiration = false) public function get($key, $expiration = false)
@ -43,23 +50,43 @@ class Google_Cache_File extends Google_Cache_Abstract
$data = false; $data = false;
if (!file_exists($storageFile)) { if (!file_exists($storageFile)) {
$this->client->getLogger()->debug(
'File cache miss',
array('key' => $key, 'file' => $storageFile)
);
return false; return false;
} }
if ($expiration) { if ($expiration) {
$mtime = filemtime($storageFile); $mtime = filemtime($storageFile);
if ((time() - $mtime) >= $expiration) { if ((time() - $mtime) >= $expiration) {
$this->client->getLogger()->debug(
'File cache miss (expired)',
array('key' => $key, 'file' => $storageFile)
);
$this->delete($key); $this->delete($key);
return false; return false;
} }
} }
if ($this->acquireReadLock($storageFile)) { if ($this->acquireReadLock($storageFile)) {
if (filesize($storageFile) > 0) {
$data = fread($this->fh, filesize($storageFile)); $data = fread($this->fh, filesize($storageFile));
$data = unserialize($data); $data = unserialize($data);
} else {
$this->client->getLogger()->debug(
'Cache file was empty',
array('file' => $storageFile)
);
}
$this->unlock($storageFile); $this->unlock($storageFile);
} }
$this->client->getLogger()->debug(
'File cache hit',
array('key' => $key, 'file' => $storageFile, 'var' => $data)
);
return $data; return $data;
} }
@ -72,6 +99,16 @@ class Google_Cache_File extends Google_Cache_Abstract
$data = serialize($value); $data = serialize($value);
$result = fwrite($this->fh, $data); $result = fwrite($this->fh, $data);
$this->unlock($storageFile); $this->unlock($storageFile);
$this->client->getLogger()->debug(
'File cache set',
array('key' => $key, 'file' => $storageFile, 'var' => $value)
);
} else {
$this->client->getLogger()->notice(
'File cache set failed',
array('key' => $key, 'file' => $storageFile)
);
} }
} }
@ -79,8 +116,17 @@ class Google_Cache_File extends Google_Cache_Abstract
{ {
$file = $this->getCacheFile($key); $file = $this->getCacheFile($key);
if (file_exists($file) && !unlink($file)) { if (file_exists($file) && !unlink($file)) {
$this->client->getLogger()->error(
'File cache delete failed',
array('key' => $key, 'file' => $file)
);
throw new Google_Cache_Exception("Cache file could not be deleted"); throw new Google_Cache_Exception("Cache file could not be deleted");
} }
$this->client->getLogger()->debug(
'File cache delete',
array('key' => $key, 'file' => $file)
);
} }
private function getWriteableCacheFile($file) private function getWriteableCacheFile($file)
@ -100,7 +146,11 @@ class Google_Cache_File extends Google_Cache_Abstract
// and thus give some basic amount of scalability // and thus give some basic amount of scalability
$storageDir = $this->path . '/' . substr(md5($file), 0, 2); $storageDir = $this->path . '/' . substr(md5($file), 0, 2);
if ($forWrite && ! is_dir($storageDir)) { if ($forWrite && ! is_dir($storageDir)) {
if (! mkdir($storageDir, 0755, true)) { if (! mkdir($storageDir, 0700, true)) {
$this->client->getLogger()->error(
'File cache creation failed',
array('dir' => $storageDir)
);
throw new Google_Cache_Exception("Could not create storage directory: $storageDir"); throw new Google_Cache_Exception("Could not create storage directory: $storageDir");
} }
} }
@ -116,6 +166,10 @@ class Google_Cache_File extends Google_Cache_Abstract
{ {
$rc = $this->acquireLock(LOCK_EX, $storageFile); $rc = $this->acquireLock(LOCK_EX, $storageFile);
if (!$rc) { if (!$rc) {
$this->client->getLogger()->notice(
'File cache write lock failed',
array('file' => $storageFile)
);
$this->delete($storageFile); $this->delete($storageFile);
} }
return $rc; return $rc;
@ -125,6 +179,16 @@ class Google_Cache_File extends Google_Cache_Abstract
{ {
$mode = $type == LOCK_EX ? "w" : "r"; $mode = $type == LOCK_EX ? "w" : "r";
$this->fh = fopen($storageFile, $mode); $this->fh = fopen($storageFile, $mode);
if (!$this->fh) {
$this->client->getLogger()->error(
'Failed to open file during lock acquisition',
array('file' => $storageFile)
);
return false;
}
if ($type == LOCK_EX) {
chmod($storageFile, 0600);
}
$count = 0; $count = 0;
while (!flock($this->fh, $type | LOCK_NB)) { while (!flock($this->fh, $type | LOCK_NB)) {
// Sleep for 10ms. // Sleep for 10ms.

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once "Google/Cache/Abstract.php"; if (!class_exists('Google_Client')) {
require_once "Google/Cache/Exception.php"; require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* A persistent storage class based on the memcache, which is not * A persistent storage class based on the memcache, which is not
@ -35,11 +36,22 @@ class Google_Cache_Memcache extends Google_Cache_Abstract
private $host; private $host;
private $port; private $port;
/**
* @var Google_Client the current client
*/
private $client;
public function __construct(Google_Client $client) public function __construct(Google_Client $client)
{ {
if (!function_exists('memcache_connect') && !class_exists("Memcached")) { if (!function_exists('memcache_connect') && !class_exists("Memcached")) {
throw new Google_Cache_Exception("Memcache functions not available"); $error = "Memcache functions not available";
$client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
} }
$this->client = $client;
if ($client->isAppEngine()) { if ($client->isAppEngine()) {
// No credentials needed for GAE. // No credentials needed for GAE.
$this->mc = new Memcached(); $this->mc = new Memcached();
@ -48,7 +60,10 @@ class Google_Cache_Memcache extends Google_Cache_Abstract
$this->host = $client->getClassConfig($this, 'host'); $this->host = $client->getClassConfig($this, 'host');
$this->port = $client->getClassConfig($this, 'port'); $this->port = $client->getClassConfig($this, 'port');
if (empty($this->host) || (empty($this->port) && (string) $this->port != "0")) { if (empty($this->host) || (empty($this->port) && (string) $this->port != "0")) {
throw new Google_Cache_Exception("You need to supply a valid memcache host and port"); $error = "You need to supply a valid memcache host and port";
$client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
} }
} }
} }
@ -66,12 +81,26 @@ class Google_Cache_Memcache extends Google_Cache_Abstract
$ret = memcache_get($this->connection, $key); $ret = memcache_get($this->connection, $key);
} }
if ($ret === false) { if ($ret === false) {
$this->client->getLogger()->debug(
'Memcache cache miss',
array('key' => $key)
);
return false; return false;
} }
if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) { if (is_numeric($expiration) && (time() - $ret['time'] > $expiration)) {
$this->client->getLogger()->debug(
'Memcache cache miss (expired)',
array('key' => $key, 'var' => $ret)
);
$this->delete($key); $this->delete($key);
return false; return false;
} }
$this->client->getLogger()->debug(
'Memcache cache hit',
array('key' => $key, 'var' => $ret)
);
return $ret['data']; return $ret['data'];
} }
@ -94,8 +123,18 @@ class Google_Cache_Memcache extends Google_Cache_Abstract
$rc = memcache_set($this->connection, $key, $data, false); $rc = memcache_set($this->connection, $key, $data, false);
} }
if ($rc == false) { if ($rc == false) {
$this->client->getLogger()->error(
'Memcache cache set failed',
array('key' => $key, 'var' => $data)
);
throw new Google_Cache_Exception("Couldn't store data in cache"); throw new Google_Cache_Exception("Couldn't store data in cache");
} }
$this->client->getLogger()->debug(
'Memcache cache set',
array('key' => $key, 'var' => $data)
);
} }
/** /**
@ -110,6 +149,11 @@ class Google_Cache_Memcache extends Google_Cache_Abstract
} else { } else {
memcache_delete($this->connection, $key, 0); memcache_delete($this->connection, $key, 0);
} }
$this->client->getLogger()->debug(
'Memcache cache delete',
array('key' => $key)
);
} }
/** /**
@ -131,7 +175,10 @@ class Google_Cache_Memcache extends Google_Cache_Abstract
} }
if (! $this->connection) { if (! $this->connection) {
throw new Google_Cache_Exception("Couldn't connect to memcache server"); $error = "Couldn't connect to memcache server";
$this->client->getLogger()->error($error);
throw new Google_Cache_Exception($error);
} }
} }
} }

View File

@ -15,28 +15,17 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Auth/AssertionCredentials.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Cache/File.php'; require_once dirname(__FILE__) . '/autoload.php';
require_once 'Google/Cache/Memcache.php'; }
require_once 'Google/Config.php';
require_once 'Google/Collection.php';
require_once 'Google/Exception.php';
require_once 'Google/IO/Curl.php';
require_once 'Google/IO/Stream.php';
require_once 'Google/Model.php';
require_once 'Google/Service.php';
require_once 'Google/Service/Resource.php';
/** /**
* The Google API Client * The Google API Client
* http://code.google.com/p/google-api-php-client/ * https://github.com/google/google-api-php-client
*
* @author Chris Chabot <chabotc@google.com>
* @author Chirag Shah <chirags@google.com>
*/ */
class Google_Client class Google_Client
{ {
const LIBVER = "1.0.6-beta"; const LIBVER = "1.1.5";
const USER_AGENT_SUFFIX = "google-api-php-client/"; const USER_AGENT_SUFFIX = "google-api-php-client/";
/** /**
* @var Google_Auth_Abstract $auth * @var Google_Auth_Abstract $auth
@ -58,6 +47,11 @@ class Google_Client
*/ */
private $config; private $config;
/**
* @var Google_Logger_Abstract $logger
*/
private $logger;
/** /**
* @var boolean $deferExecution * @var boolean $deferExecution
*/ */
@ -97,7 +91,8 @@ class Google_Client
} }
if ($config->getIoClass() == Google_Config::USE_AUTO_IO_SELECTION) { if ($config->getIoClass() == Google_Config::USE_AUTO_IO_SELECTION) {
if (function_exists('curl_version') && function_exists('curl_exec')) { if (function_exists('curl_version') && function_exists('curl_exec')
&& !$this->isAppEngine()) {
$config->setIoClass("Google_IO_Curl"); $config->setIoClass("Google_IO_Curl");
} else { } else {
$config->setIoClass("Google_IO_Stream"); $config->setIoClass("Google_IO_Stream");
@ -119,15 +114,45 @@ class Google_Client
/** /**
* Attempt to exchange a code for an valid authentication token. * Attempt to exchange a code for an valid authentication token.
* If $crossClient is set to true, the request body will not include
* the request_uri argument
* Helper wrapped around the OAuth 2.0 implementation. * Helper wrapped around the OAuth 2.0 implementation.
* *
* @param $code string code from accounts.google.com * @param $code string code from accounts.google.com
* @param $crossClient boolean, whether this is a cross-client authentication
* @return string token * @return string token
*/ */
public function authenticate($code) public function authenticate($code, $crossClient = false)
{ {
$this->authenticated = true; $this->authenticated = true;
return $this->getAuth()->authenticate($code); return $this->getAuth()->authenticate($code, $crossClient);
}
/**
* Loads a service account key and parameters from a JSON
* file from the Google Developer Console. Uses that and the
* given array of scopes to return an assertion credential for
* use with refreshTokenWithAssertionCredential.
*
* @param string $jsonLocation File location of the project-key.json.
* @param array $scopes The scopes to assert.
* @return Google_Auth_AssertionCredentials.
* @
*/
public function loadServiceAccountJson($jsonLocation, $scopes)
{
$data = json_decode(file_get_contents($jsonLocation));
if (isset($data->type) && $data->type == 'service_account') {
// Service Account format.
$cred = new Google_Auth_AssertionCredentials(
$data->client_email,
$scopes,
$data->private_key
);
return $cred;
} else {
throw new Google_Exception("Invalid service account JSON file.");
}
} }
/** /**
@ -136,6 +161,7 @@ class Google_Client
* the "Download JSON" button on in the Google Developer * the "Download JSON" button on in the Google Developer
* Console. * Console.
* @param string $json the configuration json * @param string $json the configuration json
* @throws Google_Exception
*/ */
public function setAuthConfig($json) public function setAuthConfig($json)
{ {
@ -164,6 +190,7 @@ class Google_Client
} }
/** /**
* @throws Google_Auth_Exception
* @return array * @return array
* @visible For Testing * @visible For Testing
*/ */
@ -205,9 +232,9 @@ class Google_Client
/** /**
* Set the IO object * Set the IO object
* @param Google_Io_Abstract $auth * @param Google_IO_Abstract $io
*/ */
public function setIo(Google_Io_Abstract $io) public function setIo(Google_IO_Abstract $io)
{ {
$this->config->setIoClass(get_class($io)); $this->config->setIoClass(get_class($io));
$this->io = $io; $this->io = $io;
@ -215,7 +242,7 @@ class Google_Client
/** /**
* Set the Cache object * Set the Cache object
* @param Google_Cache_Abstract $auth * @param Google_Cache_Abstract $cache
*/ */
public function setCache(Google_Cache_Abstract $cache) public function setCache(Google_Cache_Abstract $cache)
{ {
@ -223,6 +250,16 @@ class Google_Client
$this->cache = $cache; $this->cache = $cache;
} }
/**
* Set the Logger object
* @param Google_Logger_Abstract $logger
*/
public function setLogger(Google_Logger_Abstract $logger)
{
$this->config->setLoggerClass(get_class($logger));
$this->logger = $logger;
}
/** /**
* Construct the OAuth 2.0 authorization request URI. * Construct the OAuth 2.0 authorization request URI.
* @return string * @return string
@ -414,11 +451,10 @@ class Google_Client
/** /**
* Fetches a fresh OAuth 2.0 access token with the given refresh token. * Fetches a fresh OAuth 2.0 access token with the given refresh token.
* @param string $refreshToken * @param string $refreshToken
* @return void
*/ */
public function refreshToken($refreshToken) public function refreshToken($refreshToken)
{ {
return $this->getAuth()->refreshToken($refreshToken); $this->getAuth()->refreshToken($refreshToken);
} }
/** /**
@ -449,12 +485,12 @@ class Google_Client
/** /**
* Verify a JWT that was signed with your own certificates. * Verify a JWT that was signed with your own certificates.
* *
* @param $jwt the token * @param $id_token string The JWT token
* @param $certs array of certificates * @param $cert_location array of certificates
* @param $required_audience the expected consumer of the token * @param $audience string the expected consumer of the token
* @param [$issuer] the expected issues, defaults to Google * @param $issuer string the expected issuer, defaults to Google
* @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
* @return token information if valid, false if not * @return mixed token information if valid, false if not
*/ */
public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null) public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null)
{ {
@ -464,8 +500,7 @@ class Google_Client
} }
/** /**
* @param Google_Auth_AssertionCredentials $creds * @param $creds Google_Auth_AssertionCredentials
* @return void
*/ */
public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds) public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
{ {
@ -539,6 +574,8 @@ class Google_Client
/** /**
* Helper method to execute deferred HTTP requests. * Helper method to execute deferred HTTP requests.
* *
* @param $request Google_Http_Request|Google_Http_Batch
* @throws Google_Exception
* @return object of the type of the expected class or array. * @return object of the type of the expected class or array.
*/ */
public function execute($request) public function execute($request)
@ -606,10 +643,23 @@ class Google_Client
return $this->cache; return $this->cache;
} }
/**
* @return Google_Logger_Abstract Logger implementation
*/
public function getLogger()
{
if (!isset($this->logger)) {
$class = $this->config->getLoggerClass();
$this->logger = new $class($this);
}
return $this->logger;
}
/** /**
* Retrieve custom configuration for a specific class. * Retrieve custom configuration for a specific class.
* @param $class string|object - class or instance of class to retrieve * @param $class string|object - class or instance of class to retrieve
* @param $key string optional - key to retrieve * @param $key string optional - key to retrieve
* @return array
*/ */
public function getClassConfig($class, $key = null) public function getClassConfig($class, $key = null)
{ {
@ -623,9 +673,9 @@ class Google_Client
* Set configuration specific to a given class. * Set configuration specific to a given class.
* $config->setClassConfig('Google_Cache_File', * $config->setClassConfig('Google_Cache_File',
* array('directory' => '/tmp/cache')); * array('directory' => '/tmp/cache'));
* @param $class The class name for the configuration * @param $class string|object - The class name for the configuration
* @param $config string key or an array of configuration values * @param $config string key or an array of configuration values
* @param $value optional - if $config is a key, the value * @param $value string optional - if $config is a key, the value
* *
*/ */
public function setClassConfig($class, $config, $value = null) public function setClassConfig($class, $config, $value = null)
@ -633,7 +683,7 @@ class Google_Client
if (!is_string($class)) { if (!is_string($class)) {
$class = get_class($class); $class = get_class($class);
} }
return $this->config->setClassConfig($class, $config, $value); $this->config->setClassConfig($class, $config, $value);
} }

View File

@ -1,6 +1,8 @@
<?php <?php
require_once "Google/Model.php"; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/autoload.php';
}
/** /**
* Extension to the regular Google_Model that automatically * Extension to the regular Google_Model that automatically
@ -48,6 +50,9 @@ class Google_Collection extends Google_Model implements Iterator, Countable
public function count() public function count()
{ {
if (!isset($this->modelData[$this->collection_key])) {
return 0;
}
return count($this->modelData[$this->collection_key]); return count($this->modelData[$this->collection_key]);
} }

View File

@ -25,6 +25,9 @@ class Google_Config
const GZIP_UPLOADS_ENABLED = true; const GZIP_UPLOADS_ENABLED = true;
const GZIP_UPLOADS_DISABLED = false; const GZIP_UPLOADS_DISABLED = false;
const USE_AUTO_IO_SELECTION = "auto"; const USE_AUTO_IO_SELECTION = "auto";
const TASK_RETRY_NEVER = 0;
const TASK_RETRY_ONCE = 1;
const TASK_RETRY_ALWAYS = -1;
protected $configuration; protected $configuration;
/** /**
@ -44,6 +47,7 @@ class Google_Config
'auth_class' => 'Google_Auth_OAuth2', 'auth_class' => 'Google_Auth_OAuth2',
'io_class' => self::USE_AUTO_IO_SELECTION, 'io_class' => self::USE_AUTO_IO_SELECTION,
'cache_class' => 'Google_Cache_File', 'cache_class' => 'Google_Cache_File',
'logger_class' => 'Google_Logger_Null',
// Don't change these unless you're working against a special development // Don't change these unless you're working against a special development
// or testing environment. // or testing environment.
@ -54,6 +58,21 @@ class Google_Config
'Google_IO_Abstract' => array( 'Google_IO_Abstract' => array(
'request_timeout_seconds' => 100, 'request_timeout_seconds' => 100,
), ),
'Google_IO_Curl' => array(
'disable_proxy_workaround' => false,
'options' => null,
),
'Google_Logger_Abstract' => array(
'level' => 'debug',
'log_format' => "[%datetime%] %level%: %message% %context%\n",
'date_format' => 'd/M/Y:H:i:s O',
'allow_newlines' => true
),
'Google_Logger_File' => array(
'file' => 'php://stdout',
'mode' => 0640,
'lock' => false,
),
'Google_Http_Request' => array( 'Google_Http_Request' => array(
// Disable the use of gzip on calls if set to true. Defaults to false. // Disable the use of gzip on calls if set to true. Defaults to false.
'disable_gzip' => self::GZIP_ENABLED, 'disable_gzip' => self::GZIP_ENABLED,
@ -89,6 +108,36 @@ class Google_Config
'federated_signon_certs_url' => 'federated_signon_certs_url' =>
'https://www.googleapis.com/oauth2/v1/certs', 'https://www.googleapis.com/oauth2/v1/certs',
), ),
'Google_Task_Runner' => array(
// Delays are specified in seconds
'initial_delay' => 1,
'max_delay' => 60,
// Base number for exponential backoff
'factor' => 2,
// A random number between -jitter and jitter will be added to the
// factor on each iteration to allow for better distribution of
// retries.
'jitter' => .5,
// Maximum number of retries allowed
'retries' => 0
),
'Google_Service_Exception' => array(
'retry_map' => array(
'500' => self::TASK_RETRY_ALWAYS,
'503' => self::TASK_RETRY_ALWAYS,
'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS
)
),
'Google_IO_Exception' => array(
'retry_map' => !extension_loaded('curl') ? array() : array(
CURLE_COULDNT_RESOLVE_HOST => self::TASK_RETRY_ALWAYS,
CURLE_COULDNT_CONNECT => self::TASK_RETRY_ALWAYS,
CURLE_OPERATION_TIMEOUTED => self::TASK_RETRY_ALWAYS,
CURLE_SSL_CONNECT_ERROR => self::TASK_RETRY_ALWAYS,
CURLE_GOT_NOTHING => self::TASK_RETRY_ALWAYS
)
),
// Set a default directory for the file cache. // Set a default directory for the file cache.
'Google_Cache_File' => array( 'Google_Cache_File' => array(
'directory' => sys_get_temp_dir() . '/Google_Client' 'directory' => sys_get_temp_dir() . '/Google_Client'
@ -98,7 +147,11 @@ class Google_Config
if ($ini_file_location) { if ($ini_file_location) {
$ini = parse_ini_file($ini_file_location, true); $ini = parse_ini_file($ini_file_location, true);
if (is_array($ini) && count($ini)) { if (is_array($ini) && count($ini)) {
$this->configuration = array_merge($this->configuration, $ini); $merged_configuration = $ini + $this->configuration;
if (isset($ini['classes']) && isset($this->configuration['classes'])) {
$merged_configuration['classes'] = $ini['classes'] + $this->configuration['classes'];
}
$this->configuration = $merged_configuration;
} }
} }
} }
@ -107,9 +160,9 @@ class Google_Config
* Set configuration specific to a given class. * Set configuration specific to a given class.
* $config->setClassConfig('Google_Cache_File', * $config->setClassConfig('Google_Cache_File',
* array('directory' => '/tmp/cache')); * array('directory' => '/tmp/cache'));
* @param $class The class name for the configuration * @param $class string The class name for the configuration
* @param $config string key or an array of configuration values * @param $config string key or an array of configuration values
* @param $value optional - if $config is a key, the value * @param $value string optional - if $config is a key, the value
*/ */
public function setClassConfig($class, $config, $value = null) public function setClassConfig($class, $config, $value = null)
{ {
@ -144,6 +197,15 @@ class Google_Config
return $this->configuration['cache_class']; return $this->configuration['cache_class'];
} }
/**
* Return the configured logger class.
* @return string
*/
public function getLoggerClass()
{
return $this->configuration['logger_class'];
}
/** /**
* Return the configured Auth class. * Return the configured Auth class.
* @return string * @return string
@ -156,7 +218,7 @@ class Google_Config
/** /**
* Set the auth class. * Set the auth class.
* *
* @param $class the class name to set * @param $class string the class name to set
*/ */
public function setAuthClass($class) public function setAuthClass($class)
{ {
@ -172,7 +234,7 @@ class Google_Config
/** /**
* Set the IO class. * Set the IO class.
* *
* @param $class the class name to set * @param $class string the class name to set
*/ */
public function setIoClass($class) public function setIoClass($class)
{ {
@ -188,7 +250,7 @@ class Google_Config
/** /**
* Set the cache class. * Set the cache class.
* *
* @param $class the class name to set * @param $class string the class name to set
*/ */
public function setCacheClass($class) public function setCacheClass($class)
{ {
@ -201,8 +263,25 @@ class Google_Config
$this->configuration['cache_class'] = $class; $this->configuration['cache_class'] = $class;
} }
/**
* Set the logger class.
*
* @param $class string the class name to set
*/
public function setLoggerClass($class)
{
$prev = $this->configuration['logger_class'];
if (!isset($this->configuration['classes'][$class]) &&
isset($this->configuration['classes'][$prev])) {
$this->configuration['classes'][$class] =
$this->configuration['classes'][$prev];
}
$this->configuration['logger_class'] = $class;
}
/** /**
* Return the configured IO class. * Return the configured IO class.
*
* @return string * @return string
*/ */
public function getIoClass() public function getIoClass()
@ -229,7 +308,7 @@ class Google_Config
/** /**
* Set the client ID for the auth class. * Set the client ID for the auth class.
* @param $key string - the API console client ID * @param $clientId string - the API console client ID
*/ */
public function setClientId($clientId) public function setClientId($clientId)
{ {
@ -238,7 +317,7 @@ class Google_Config
/** /**
* Set the client secret for the auth class. * Set the client secret for the auth class.
* @param $key string - the API console client secret * @param $secret string - the API console client secret
*/ */
public function setClientSecret($secret) public function setClientSecret($secret)
{ {
@ -248,7 +327,8 @@ class Google_Config
/** /**
* Set the redirect uri for the auth class. Note that if using the * Set the redirect uri for the auth class. Note that if using the
* Javascript based sign in flow, this should be the string 'postmessage'. * Javascript based sign in flow, this should be the string 'postmessage'.
* @param $key string - the URI that users should be redirected to *
* @param $uri string - the URI that users should be redirected to
*/ */
public function setRedirectUri($uri) public function setRedirectUri($uri)
{ {
@ -305,6 +385,11 @@ class Google_Config
* Set the hd (hosted domain) parameter streamlines the login process for * Set the hd (hosted domain) parameter streamlines the login process for
* Google Apps hosted accounts. By including the domain of the user, you * Google Apps hosted accounts. By including the domain of the user, you
* restrict sign-in to accounts at that domain. * restrict sign-in to accounts at that domain.
*
* This should not be used to ensure security on your application - check
* the hd values within an id token (@see Google_Auth_LoginTicket) after sign
* in to ensure that the user is from the domain you were expecting.
*
* @param $hd string - the domain to use. * @param $hd string - the domain to use.
*/ */
public function setHostedDomain($hd) public function setHostedDomain($hd)

View File

@ -15,12 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Client.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Http/Request.php'; require_once dirname(__FILE__) . '/../autoload.php';
require_once 'Google/Http/REST.php'; }
/** /**
* @author Chirag Shah <chirags@google.com> * Class to handle batched requests to the Google API service.
*/ */
class Google_Http_Batch class Google_Http_Batch
{ {
@ -35,12 +35,15 @@ class Google_Http_Batch
private $expected_classes = array(); private $expected_classes = array();
private $base_path; private $root_url;
public function __construct(Google_Client $client, $boundary = false) private $batch_path;
public function __construct(Google_Client $client, $boundary = false, $rootUrl = '', $batchPath = '')
{ {
$this->client = $client; $this->client = $client;
$this->base_path = $this->client->getBasePath(); $this->root_url = rtrim($rootUrl ? $rootUrl : $this->client->getBasePath(), '/');
$this->batch_path = $batchPath ? $batchPath : 'batch';
$this->expected_classes = array(); $this->expected_classes = array();
$boundary = (false == $boundary) ? mt_rand() : $boundary; $boundary = (false == $boundary) ? mt_rand() : $boundary;
$this->boundary = str_replace('"', '', $boundary); $this->boundary = str_replace('"', '', $boundary);
@ -62,14 +65,13 @@ class Google_Http_Batch
/** @var Google_Http_Request $req */ /** @var Google_Http_Request $req */
foreach ($this->requests as $key => $req) { foreach ($this->requests as $key => $req) {
$body .= "--{$this->boundary}\n"; $body .= "--{$this->boundary}\n";
$body .= $req->toBatchString($key) . "\n"; $body .= $req->toBatchString($key) . "\n\n";
$this->expected_classes["response-" . $key] = $req->getExpectedClass(); $this->expected_classes["response-" . $key] = $req->getExpectedClass();
} }
$body = rtrim($body); $body .= "--{$this->boundary}--";
$body .= "\n--{$this->boundary}--";
$url = $this->base_path . '/batch'; $url = $this->root_url . '/' . $this->batch_path;
$httpRequest = new Google_Http_Request($url, 'POST'); $httpRequest = new Google_Http_Request($url, 'POST');
$httpRequest->setRequestHeaders( $httpRequest->setRequestHeaders(
array('Content-Type' => 'multipart/mixed; boundary=' . $this->boundary) array('Content-Type' => 'multipart/mixed; boundary=' . $this->boundary)
@ -125,10 +127,10 @@ class Google_Http_Batch
} }
try { try {
$response = Google_Http_REST::decodeHttpResponse($response); $response = Google_Http_REST::decodeHttpResponse($response, $this->client);
$responses[$key] = $response; $responses[$key] = $response;
} catch (Google_Service_Exception $e) { } catch (Google_Service_Exception $e) {
// Store the exception as the response, so succesful responses // Store the exception as the response, so successful responses
// can be processed. // can be processed.
$responses[$key] = $e; $responses[$key] = $e;
} }

View File

@ -15,12 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Http/Request.php'; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* Implement the caching directives specified in rfc2616. This * Implement the caching directives specified in rfc2616. This
* implementation is guided by the guidance offered in rfc2616-sec13. * implementation is guided by the guidance offered in rfc2616-sec13.
* @author Chirag Shah <chirags@google.com>
*/ */
class Google_Http_CacheParser class Google_Http_CacheParser
{ {

View File

@ -15,15 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Client.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Exception.php'; require_once dirname(__FILE__) . '/../autoload.php';
require_once 'Google/Http/Request.php'; }
require_once 'Google/Http/REST.php';
require_once 'Google/Utils.php';
/** /**
* @author Chirag Shah <chirags@google.com> * Manage large file uploads, which may be media but can be any type
* * of sizable data.
*/ */
class Google_Http_MediaFileUpload class Google_Http_MediaFileUpload
{ {
@ -128,35 +126,16 @@ class Google_Http_MediaFileUpload
} }
/** /**
* Send the next part of the file to upload. * Sends a PUT-Request to google drive and parses the response,
* @param [$chunk] the next set of bytes to send. If false will used $data passed * setting the appropiate variables from the response()
* at construct time. *
* @param Google_Http_Request $httpRequest the Reuqest which will be send
*
* @return false|mixed false when the upload is unfinished or the decoded http response
*
*/ */
public function nextChunk($chunk = false) private function makePutRequest(Google_Http_Request $httpRequest)
{ {
if (false == $this->resumeUri) {
$this->resumeUri = $this->getResumeUri();
}
if (false == $chunk) {
$chunk = substr($this->data, $this->progress, $this->chunkSize);
}
$lastBytePos = $this->progress + strlen($chunk) - 1;
$headers = array(
'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
'content-type' => $this->request->getRequestHeader('content-type'),
'content-length' => $this->chunkSize,
'expect' => '',
);
$httpRequest = new Google_Http_Request(
$this->resumeUri,
'PUT',
$headers,
$chunk
);
if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) { if ($this->client->getClassConfig("Google_Http_Request", "enable_gzip_for_uploads")) {
$httpRequest->enableGzip(); $httpRequest->enableGzip();
} else { } else {
@ -182,13 +161,61 @@ class Google_Http_MediaFileUpload
// No problems, but upload not complete. // No problems, but upload not complete.
return false; return false;
} else { } else {
return Google_Http_REST::decodeHttpResponse($response); return Google_Http_REST::decodeHttpResponse($response, $this->client);
} }
} }
/** /**
* @param $meta * Send the next part of the file to upload.
* @param $params * @param [$chunk] the next set of bytes to send. If false will used $data passed
* at construct time.
*/
public function nextChunk($chunk = false)
{
if (false == $this->resumeUri) {
$this->resumeUri = $this->fetchResumeUri();
}
if (false == $chunk) {
$chunk = substr($this->data, $this->progress, $this->chunkSize);
}
$lastBytePos = $this->progress + strlen($chunk) - 1;
$headers = array(
'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
'content-type' => $this->request->getRequestHeader('content-type'),
'content-length' => $this->chunkSize,
'expect' => '',
);
$httpRequest = new Google_Http_Request(
$this->resumeUri,
'PUT',
$headers,
$chunk
);
return $this->makePutRequest($httpRequest);
}
/**
* Resume a previously unfinished upload
* @param $resumeUri the resume-URI of the unfinished, resumable upload.
*/
public function resume($resumeUri)
{
$this->resumeUri = $resumeUri;
$headers = array(
'content-range' => "bytes */$this->size",
'content-length' => 0,
);
$httpRequest = new Google_Http_Request(
$this->resumeUri,
'PUT',
$headers
);
return $this->makePutRequest($httpRequest);
}
/**
* @return array|bool * @return array|bool
* @visible for testing * @visible for testing
*/ */
@ -265,7 +292,12 @@ class Google_Http_MediaFileUpload
return self::UPLOAD_MULTIPART_TYPE; return self::UPLOAD_MULTIPART_TYPE;
} }
private function getResumeUri() public function getResumeUri()
{
return ( $this->resumeUri !== null ? $this->resumeUri : $this->fetchResumeUri() );
}
private function fetchResumeUri()
{ {
$result = null; $result = null;
$body = $this->request->getPostBody(); $body = $this->request->getPostBody();
@ -296,6 +328,14 @@ class Google_Http_MediaFileUpload
} }
$message = rtrim($message, ';'); $message = rtrim($message, ';');
} }
throw new Google_Exception("Failed to start the resumable upload (HTTP {$message})");
$error = "Failed to start the resumable upload (HTTP {$message})";
$this->client->getLogger()->error($error);
throw new Google_Exception($error);
}
public function setChunkSize($chunkSize)
{
$this->chunkSize = $chunkSize;
} }
} }

View File

@ -15,21 +15,18 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Client.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Http/Request.php'; require_once dirname(__FILE__) . '/../autoload.php';
require_once 'Google/Service/Exception.php'; }
require_once 'Google/Utils/URITemplate.php';
/** /**
* This class implements the RESTful transport of apiServiceRequest()'s * This class implements the RESTful transport of apiServiceRequest()'s
*
* @author Chris Chabot <chabotc@google.com>
* @author Chirag Shah <chirags@google.com>
*/ */
class Google_Http_REST class Google_Http_REST
{ {
/** /**
* Executes a Google_Http_Request * Executes a Google_Http_Request and (if applicable) automatically retries
* when errors occur.
* *
* @param Google_Client $client * @param Google_Client $client
* @param Google_Http_Request $req * @param Google_Http_Request $req
@ -38,10 +35,31 @@ class Google_Http_REST
* invalid or malformed post body, invalid url) * invalid or malformed post body, invalid url)
*/ */
public static function execute(Google_Client $client, Google_Http_Request $req) public static function execute(Google_Client $client, Google_Http_Request $req)
{
$runner = new Google_Task_Runner(
$client,
sprintf('%s %s', $req->getRequestMethod(), $req->getUrl()),
array(get_class(), 'doExecute'),
array($client, $req)
);
return $runner->run();
}
/**
* Executes a Google_Http_Request
*
* @param Google_Client $client
* @param Google_Http_Request $req
* @return array decoded result
* @throws Google_Service_Exception on server side error (ie: not authenticated,
* invalid or malformed post body, invalid url)
*/
public static function doExecute(Google_Client $client, Google_Http_Request $req)
{ {
$httpRequest = $client->getIo()->makeRequest($req); $httpRequest = $client->getIo()->makeRequest($req);
$httpRequest->setExpectedClass($req->getExpectedClass()); $httpRequest->setExpectedClass($req->getExpectedClass());
return self::decodeHttpResponse($httpRequest); return self::decodeHttpResponse($httpRequest, $client);
} }
/** /**
@ -49,9 +67,10 @@ class Google_Http_REST
* @static * @static
* @throws Google_Service_Exception * @throws Google_Service_Exception
* @param Google_Http_Request $response The http response to be decoded. * @param Google_Http_Request $response The http response to be decoded.
* @param Google_Client $client
* @return mixed|null * @return mixed|null
*/ */
public static function decodeHttpResponse($response) public static function decodeHttpResponse($response, Google_Client $client = null)
{ {
$code = $response->getResponseHttpCode(); $code = $response->getResponseHttpCode();
$body = $response->getResponseBody(); $body = $response->getResponseBody();
@ -76,14 +95,34 @@ class Google_Http_REST
$errors = $decoded['error']['errors']; $errors = $decoded['error']['errors'];
} }
throw new Google_Service_Exception($err, $code, null, $errors); $map = null;
if ($client) {
$client->getLogger()->error(
$err,
array('code' => $code, 'errors' => $errors)
);
$map = $client->getClassConfig(
'Google_Service_Exception',
'retry_map'
);
}
throw new Google_Service_Exception($err, $code, null, $errors, $map);
} }
// Only attempt to decode the response, if the response code wasn't (204) 'no content' // Only attempt to decode the response, if the response code wasn't (204) 'no content'
if ($code != '204') { if ($code != '204') {
if ($response->getExpectedRaw()) {
return $body;
}
$decoded = json_decode($body, true); $decoded = json_decode($body, true);
if ($decoded === null || $decoded === "") { if ($decoded === null || $decoded === "") {
throw new Google_Service_Exception("Invalid json in service response: $body"); $error = "Invalid json in service response: $body";
if ($client) {
$client->getLogger()->error($error);
}
throw new Google_Service_Exception($error);
} }
if ($response->getExpectedClass()) { if ($response->getExpectedClass()) {
@ -117,10 +156,10 @@ class Google_Http_REST
} else if ($paramSpec['location'] == 'query') { } else if ($paramSpec['location'] == 'query') {
if (isset($paramSpec['repeated']) && is_array($paramSpec['value'])) { if (isset($paramSpec['repeated']) && is_array($paramSpec['value'])) {
foreach ($paramSpec['value'] as $value) { foreach ($paramSpec['value'] as $value) {
$queryVars[] = $paramName . '=' . rawurlencode($value); $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($value));
} }
} else { } else {
$queryVars[] = $paramName . '=' . rawurlencode($paramSpec['value']); $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($paramSpec['value']));
} }
} }
} }

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Utils.php'; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* HTTP Request to be executed by IO classes. Upon execution, the * HTTP Request to be executed by IO classes. Upon execution, the
@ -49,6 +51,7 @@ class Google_Http_Request
protected $responseBody; protected $responseBody;
protected $expectedClass; protected $expectedClass;
protected $expectedRaw = false;
public $accessKey; public $accessKey;
@ -80,7 +83,7 @@ class Google_Http_Request
*/ */
public function setBaseComponent($baseComponent) public function setBaseComponent($baseComponent)
{ {
$this->baseComponent = $baseComponent; $this->baseComponent = rtrim($baseComponent, '/');
} }
/** /**
@ -188,6 +191,31 @@ class Google_Http_Request
return $this->expectedClass; return $this->expectedClass;
} }
/**
* Enable expected raw response
*/
public function enableExpectedRaw()
{
$this->expectedRaw = true;
}
/**
* Disable expected raw response
*/
public function disableExpectedRaw()
{
$this->expectedRaw = false;
}
/**
* Expected raw response or not.
* @return boolean expected raw response
*/
public function getExpectedRaw()
{
return $this->expectedRaw;
}
/** /**
* @param array $headers The HTTP response headers * @param array $headers The HTTP response headers
* to be normalized. * to be normalized.

View File

@ -19,10 +19,9 @@
* Abstract IO base class * Abstract IO base class
*/ */
require_once 'Google/Client.php'; if (!class_exists('Google_Client')) {
require_once 'Google/IO/Exception.php'; require_once dirname(__FILE__) . '/../autoload.php';
require_once 'Google/Http/CacheParser.php'; }
require_once 'Google/Http/Request.php';
abstract class Google_IO_Abstract abstract class Google_IO_Abstract
{ {
@ -33,6 +32,17 @@ abstract class Google_IO_Abstract
"HTTP/1.1 200 Connection established\r\n\r\n", "HTTP/1.1 200 Connection established\r\n\r\n",
); );
private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null); private static $ENTITY_HTTP_METHODS = array("POST" => null, "PUT" => null);
private static $HOP_BY_HOP = array(
'connection' => true,
'keep-alive' => true,
'proxy-authenticate' => true,
'proxy-authorization' => true,
'te' => true,
'trailers' => true,
'transfer-encoding' => true,
'upgrade' => true
);
/** @var Google_Client */ /** @var Google_Client */
protected $client; protected $client;
@ -47,9 +57,10 @@ abstract class Google_IO_Abstract
} }
/** /**
* Executes a Google_Http_Request and returns the resulting populated Google_Http_Request * Executes a Google_Http_Request
* @param Google_Http_Request $request * @param Google_Http_Request $request the http request to be executed
* @return Google_Http_Request $request * @return array containing response headers, body, and http code
* @throws Google_IO_Exception on curl or IO error
*/ */
abstract public function executeRequest(Google_Http_Request $request); abstract public function executeRequest(Google_Http_Request $request);
@ -103,8 +114,8 @@ abstract class Google_IO_Abstract
/** /**
* Execute an HTTP Request * Execute an HTTP Request
* *
* @param Google_HttpRequest $request the http request to be executed * @param Google_Http_Request $request the http request to be executed
* @return Google_HttpRequest http request with the response http code, * @return Google_Http_Request http request with the response http code,
* response headers and response body filled in * response headers and response body filled in
* @throws Google_IO_Exception on curl or IO error * @throws Google_IO_Exception on curl or IO error
*/ */
@ -131,7 +142,7 @@ abstract class Google_IO_Abstract
} }
if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) { if (!isset($responseHeaders['Date']) && !isset($responseHeaders['date'])) {
$responseHeaders['Date'] = date("r"); $responseHeaders['date'] = date("r");
} }
$request->setResponseHttpCode($respHttpCode); $request->setResponseHttpCode($respHttpCode);
@ -219,29 +230,25 @@ abstract class Google_IO_Abstract
/** /**
* Update a cached request, using the headers from the last response. * Update a cached request, using the headers from the last response.
* @param Google_HttpRequest $cached A previously cached response. * @param Google_Http_Request $cached A previously cached response.
* @param mixed Associative array of response headers from the last request. * @param mixed Associative array of response headers from the last request.
*/ */
protected function updateCachedRequest($cached, $responseHeaders) protected function updateCachedRequest($cached, $responseHeaders)
{ {
if (isset($responseHeaders['connection'])) { $hopByHop = self::$HOP_BY_HOP;
$hopByHop = array_merge( if (!empty($responseHeaders['connection'])) {
self::$HOP_BY_HOP, $connectionHeaders = array_map(
explode( 'strtolower',
',', array_filter(
$responseHeaders['connection'] array_map('trim', explode(',', $responseHeaders['connection']))
) )
); );
$hopByHop += array_fill_keys($connectionHeaders, true);
}
$endToEnd = array(); $endToEnd = array_diff_key($responseHeaders, $hopByHop);
foreach ($hopByHop as $key) {
if (isset($responseHeaders[$key])) {
$endToEnd[$key] = $responseHeaders[$key];
}
}
$cached->setResponseHeaders($endToEnd); $cached->setResponseHeaders($endToEnd);
} }
}
/** /**
* Used by the IO lib and also the batch processing. * Used by the IO lib and also the batch processing.
@ -323,7 +330,7 @@ abstract class Google_IO_Abstract
// Times will have colons in - so we just want the first match. // Times will have colons in - so we just want the first match.
$header_parts = explode(': ', $header, 2); $header_parts = explode(': ', $header, 2);
if (count($header_parts) == 2) { if (count($header_parts) == 2) {
$headers[$header_parts[0]] = $header_parts[1]; $headers[strtolower($header_parts[0])] = $header_parts[1];
} }
} }

View File

@ -21,7 +21,9 @@
* @author Stuart Langley <slangley@google.com> * @author Stuart Langley <slangley@google.com>
*/ */
require_once 'Google/IO/Abstract.php'; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_IO_Curl extends Google_IO_Abstract class Google_IO_Curl extends Google_IO_Abstract
{ {
@ -29,12 +31,31 @@ class Google_IO_Curl extends Google_IO_Abstract
const NO_QUIRK_VERSION = 0x071E00; const NO_QUIRK_VERSION = 0x071E00;
private $options = array(); private $options = array();
/** @var bool $disableProxyWorkaround */
private $disableProxyWorkaround;
public function __construct(Google_Client $client)
{
if (!extension_loaded('curl')) {
$error = 'The cURL IO handler requires the cURL extension to be enabled';
$client->getLogger()->critical($error);
throw new Google_IO_Exception($error);
}
parent::__construct($client);
$this->disableProxyWorkaround = $this->client->getClassConfig(
'Google_IO_Curl',
'disable_proxy_workaround'
);
}
/** /**
* Execute an HTTP Request * Execute an HTTP Request
* *
* @param Google_HttpRequest $request the http request to be executed * @param Google_Http_Request $request the http request to be executed
* @return Google_HttpRequest http request with the response http code, * @return array containing response headers, body, and http code
* response headers and response body filled in
* @throws Google_IO_Exception on curl or IO error * @throws Google_IO_Exception on curl or IO error
*/ */
public function executeRequest(Google_Http_Request $request) public function executeRequest(Google_Http_Request $request)
@ -53,7 +74,6 @@ class Google_IO_Curl extends Google_IO_Abstract
} }
curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders); curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders);
} }
curl_setopt($curl, CURLOPT_URL, $request->getUrl()); curl_setopt($curl, CURLOPT_URL, $request->getUrl());
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod()); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod());
@ -61,6 +81,11 @@ class Google_IO_Curl extends Google_IO_Abstract
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
// The SSL version will be determined by the underlying library
// @see https://github.com/google/google-api-php-client/pull/644
//curl_setopt($curl, CURLOPT_SSLVERSION, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_HEADER, true);
@ -68,6 +93,11 @@ class Google_IO_Curl extends Google_IO_Abstract
curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate');
} }
$options = $this->client->getClassConfig('Google_IO_Curl', 'options');
if (is_array($options)) {
$this->setOptions($options);
}
foreach ($this->options as $key => $var) { foreach ($this->options as $key => $var) {
curl_setopt($curl, $key, $var); curl_setopt($curl, $key, $var);
} }
@ -76,16 +106,39 @@ class Google_IO_Curl extends Google_IO_Abstract
curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacerts.pem'); curl_setopt($curl, CURLOPT_CAINFO, dirname(__FILE__) . '/cacerts.pem');
} }
$this->client->getLogger()->debug(
'cURL request',
array(
'url' => $request->getUrl(),
'method' => $request->getRequestMethod(),
'headers' => $requestHeaders,
'body' => $request->getPostBody()
)
);
$response = curl_exec($curl); $response = curl_exec($curl);
if ($response === false) { if ($response === false) {
throw new Google_IO_Exception(curl_error($curl)); $error = curl_error($curl);
$code = curl_errno($curl);
$map = $this->client->getClassConfig('Google_IO_Exception', 'retry_map');
$this->client->getLogger()->error('cURL ' . $error);
throw new Google_IO_Exception($error, $code, null, $map);
} }
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
list($responseHeaders, $responseBody) = $this->parseHttpResponse($response, $headerSize); list($responseHeaders, $responseBody) = $this->parseHttpResponse($response, $headerSize);
$responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->client->getLogger()->debug(
'cURL response',
array(
'code' => $responseCode,
'headers' => $responseHeaders,
'body' => $responseBody,
)
);
return array($responseBody, $responseHeaders, $responseCode); return array($responseBody, $responseHeaders, $responseCode);
} }
@ -106,7 +159,7 @@ class Google_IO_Curl extends Google_IO_Abstract
{ {
// Since this timeout is really for putting a bound on the time // Since this timeout is really for putting a bound on the time
// we'll set them both to the same. If you need to specify a longer // we'll set them both to the same. If you need to specify a longer
// CURLOPT_TIMEOUT, or a tigher CONNECTTIMEOUT, the best thing to // CURLOPT_TIMEOUT, or a higher CONNECTTIMEOUT, the best thing to
// do is use the setOptions method for the values individually. // do is use the setOptions method for the values individually.
$this->options[CURLOPT_CONNECTTIMEOUT] = $timeout; $this->options[CURLOPT_CONNECTTIMEOUT] = $timeout;
$this->options[CURLOPT_TIMEOUT] = $timeout; $this->options[CURLOPT_TIMEOUT] = $timeout;
@ -130,6 +183,10 @@ class Google_IO_Curl extends Google_IO_Abstract
*/ */
protected function needsQuirk() protected function needsQuirk()
{ {
if ($this->disableProxyWorkaround) {
return false;
}
$ver = curl_version(); $ver = curl_version();
$versionNum = $ver['version_number']; $versionNum = $ver['version_number'];
return $versionNum < Google_IO_Curl::NO_QUIRK_VERSION; return $versionNum < Google_IO_Curl::NO_QUIRK_VERSION;

View File

@ -15,8 +15,55 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Exception.php'; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
class Google_IO_Exception extends Google_Exception }
{
class Google_IO_Exception extends Google_Exception implements Google_Task_Retryable
{
/**
* @var array $retryMap Map of errors with retry counts.
*/
private $retryMap = array();
/**
* Creates a new IO exception with an optional retry map.
*
* @param string $message
* @param int $code
* @param Exception|null $previous
* @param array|null $retryMap Map of errors with retry counts.
*/
public function __construct(
$message,
$code = 0,
Exception $previous = null,
array $retryMap = null
) {
if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
parent::__construct($message, $code, $previous);
} else {
parent::__construct($message, $code);
}
if (is_array($retryMap)) {
$this->retryMap = $retryMap;
}
}
/**
* Gets the number of times the associated task can be retried.
*
* NOTE: -1 is returned if the task can be retried indefinitely
*
* @return integer
*/
public function allowedRetries()
{
if (isset($this->retryMap[$this->code])) {
return $this->retryMap[$this->code];
}
return 0;
}
} }

View File

@ -21,7 +21,9 @@
* @author Stuart Langley <slangley@google.com> * @author Stuart Langley <slangley@google.com>
*/ */
require_once 'Google/IO/Abstract.php'; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_IO_Stream extends Google_IO_Abstract class Google_IO_Stream extends Google_IO_Abstract
{ {
@ -40,12 +42,23 @@ class Google_IO_Stream extends Google_IO_Abstract
"verify_peer" => true, "verify_peer" => true,
); );
public function __construct(Google_Client $client)
{
if (!ini_get('allow_url_fopen')) {
$error = 'The stream IO handler requires the allow_url_fopen runtime ' .
'configuration to be enabled';
$client->getLogger()->critical($error);
throw new Google_IO_Exception($error);
}
parent::__construct($client);
}
/** /**
* Execute an HTTP Request * Execute an HTTP Request
* *
* @param Google_HttpRequest $request the http request to be executed * @param Google_Http_Request $request the http request to be executed
* @return Google_HttpRequest http request with the response http code, * @return array containing response headers, body, and http code
* response headers and response body filled in
* @throws Google_IO_Exception on curl or IO error * @throws Google_IO_Exception on curl or IO error
*/ */
public function executeRequest(Google_Http_Request $request) public function executeRequest(Google_Http_Request $request)
@ -74,7 +87,7 @@ class Google_IO_Stream extends Google_IO_Abstract
$requestSslContext = array_key_exists('ssl', $default_options) ? $requestSslContext = array_key_exists('ssl', $default_options) ?
$default_options['ssl'] : array(); $default_options['ssl'] : array();
if (!array_key_exists("cafile", $requestSslContext)) { if (!$this->client->isAppEngine() && !array_key_exists("cafile", $requestSslContext)) {
$requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem'; $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem';
} }
@ -97,6 +110,16 @@ class Google_IO_Stream extends Google_IO_Abstract
$url = self::ZLIB . $url; $url = self::ZLIB . $url;
} }
$this->client->getLogger()->debug(
'Stream request',
array(
'url' => $url,
'method' => $request->getRequestMethod(),
'headers' => $requestHeaders,
'body' => $request->getPostBody()
)
);
// We are trapping any thrown errors in this method only and // We are trapping any thrown errors in this method only and
// throwing an exception. // throwing an exception.
$this->trappedErrorNumber = null; $this->trappedErrorNumber = null;
@ -109,13 +132,13 @@ class Google_IO_Stream extends Google_IO_Abstract
// END - error trap. // END - error trap.
if ($this->trappedErrorNumber) { if ($this->trappedErrorNumber) {
throw new Google_IO_Exception( $error = sprintf(
sprintf(
"HTTP Error: Unable to connect: '%s'", "HTTP Error: Unable to connect: '%s'",
$this->trappedErrorString $this->trappedErrorString
),
$this->trappedErrorNumber
); );
$this->client->getLogger()->error('Stream ' . $error);
throw new Google_IO_Exception($error, $this->trappedErrorNumber);
} }
$response_data = false; $response_data = false;
@ -132,17 +155,26 @@ class Google_IO_Stream extends Google_IO_Abstract
} }
if (false === $response_data) { if (false === $response_data) {
throw new Google_IO_Exception( $error = sprintf(
sprintf(
"HTTP Error: Unable to connect: '%s'", "HTTP Error: Unable to connect: '%s'",
$respHttpCode $respHttpCode
),
$respHttpCode
); );
$this->client->getLogger()->error('Stream ' . $error);
throw new Google_IO_Exception($error, $respHttpCode);
} }
$responseHeaders = $this->getHttpResponseHeaders($http_response_header); $responseHeaders = $this->getHttpResponseHeaders($http_response_header);
$this->client->getLogger()->debug(
'Stream response',
array(
'code' => $respHttpCode,
'headers' => $responseHeaders,
'body' => $response_data,
)
);
return array($response_data, $responseHeaders, $respHttpCode); return array($response_data, $responseHeaders, $respHttpCode);
} }

File diff suppressed because it is too large Load Diff

View File

@ -20,11 +20,14 @@
* from a given json schema. * from a given json schema.
* http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5 * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5
* *
* @author Chirag Shah <chirags@google.com>
*
*/ */
class Google_Model implements ArrayAccess class Google_Model implements ArrayAccess
{ {
/**
* If you need to specify a NULL JSON value, use Google_Model::NULL_VALUE
* instead - it will be replaced when converting to JSON with a real null.
*/
const NULL_VALUE = "{}gapi-php-null";
protected $internal_gapi_mappings = array(); protected $internal_gapi_mappings = array();
protected $modelData = array(); protected $modelData = array();
protected $processed = array(); protected $processed = array();
@ -33,15 +36,21 @@ class Google_Model implements ArrayAccess
* Polymorphic - accepts a variable number of arguments dependent * Polymorphic - accepts a variable number of arguments dependent
* on the type of the model subclass. * on the type of the model subclass.
*/ */
public function __construct() final public function __construct()
{ {
if (func_num_args() == 1 && is_array(func_get_arg(0))) { if (func_num_args() == 1 && is_array(func_get_arg(0))) {
// Initialize the model with the array's contents. // Initialize the model with the array's contents.
$array = func_get_arg(0); $array = func_get_arg(0);
$this->mapTypes($array); $this->mapTypes($array);
} }
$this->gapiInit();
} }
/**
* Getter that handles passthrough access to the data array, and lazy object creation.
* @param string $key Property name.
* @return mixed The value if any, or null.
*/
public function __get($key) public function __get($key)
{ {
$keyTypeName = $this->keyType($key); $keyTypeName = $this->keyType($key);
@ -87,7 +96,7 @@ class Google_Model implements ArrayAccess
*/ */
protected function mapTypes($array) protected function mapTypes($array)
{ {
// Hard initilise simple types, lazy load more complex ones. // Hard initialise simple types, lazy load more complex ones.
foreach ($array as $key => $val) { foreach ($array as $key => $val) {
if ( !property_exists($this, $this->keyType($key)) && if ( !property_exists($this, $this->keyType($key)) &&
property_exists($this, $key)) { property_exists($this, $key)) {
@ -102,6 +111,16 @@ class Google_Model implements ArrayAccess
$this->modelData = $array; $this->modelData = $array;
} }
/**
* Blank initialiser to be used in subclasses to do post-construction initialisation - this
* avoids the need for subclasses to have to implement the variadics handling in their
* constructors.
*/
protected function gapiInit()
{
return;
}
/** /**
* Create a simplified object suitable for straightforward * Create a simplified object suitable for straightforward
* conversion to JSON. This is relatively expensive * conversion to JSON. This is relatively expensive
@ -116,7 +135,7 @@ class Google_Model implements ArrayAccess
foreach ($this->modelData as $key => $val) { foreach ($this->modelData as $key => $val) {
$result = $this->getSimpleValue($val); $result = $this->getSimpleValue($val);
if ($result !== null) { if ($result !== null) {
$object->$key = $result; $object->$key = $this->nullPlaceholderCheck($result);
} }
} }
@ -128,7 +147,7 @@ class Google_Model implements ArrayAccess
$result = $this->getSimpleValue($this->$name); $result = $this->getSimpleValue($this->$name);
if ($result !== null) { if ($result !== null) {
$name = $this->getMappedName($name); $name = $this->getMappedName($name);
$object->$name = $result; $object->$name = $this->nullPlaceholderCheck($result);
} }
} }
@ -149,7 +168,7 @@ class Google_Model implements ArrayAccess
$a_value = $this->getSimpleValue($a_value); $a_value = $this->getSimpleValue($a_value);
if ($a_value !== null) { if ($a_value !== null) {
$key = $this->getMappedName($key); $key = $this->getMappedName($key);
$return[$key] = $a_value; $return[$key] = $this->nullPlaceholderCheck($a_value);
} }
} }
return $return; return $return;
@ -157,6 +176,17 @@ class Google_Model implements ArrayAccess
return $value; return $value;
} }
/**
* Check whether the value is the null placeholder and return true null.
*/
private function nullPlaceholderCheck($value)
{
if ($value === self::NULL_VALUE) {
return null;
}
return $value;
}
/** /**
* If there is an internal name mapping, use that. * If there is an internal name mapping, use that.
*/ */

View File

@ -17,6 +17,8 @@
class Google_Service class Google_Service
{ {
public $batchPath;
public $rootUrl;
public $version; public $version;
public $servicePath; public $servicePath;
public $availableScopes; public $availableScopes;
@ -36,4 +38,19 @@ class Google_Service
{ {
return $this->client; return $this->client;
} }
/**
* Create a new HTTP Batch handler for this service
*
* @return Google_Http_Batch
*/
public function createBatch()
{
return new Google_Http_Batch(
$this->client,
false,
$this->rootUrl,
$this->batchPath
);
}
} }

View File

@ -1,8 +1,25 @@
<?php <?php
/*
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require_once 'Google/Exception.php'; if (!class_exists('Google_Client')) {
require_once dirname(__FILE__) . '/../autoload.php';
}
class Google_Service_Exception extends Google_Exception class Google_Service_Exception extends Google_Exception implements Google_Task_Retryable
{ {
/** /**
* Optional list of errors returned in a JSON body of an HTTP error response. * Optional list of errors returned in a JSON body of an HTTP error response.
@ -10,19 +27,27 @@ class Google_Service_Exception extends Google_Exception
protected $errors = array(); protected $errors = array();
/** /**
* Override default constructor to add ability to set $errors. * @var array $retryMap Map of errors with retry counts.
*/
private $retryMap = array();
/**
* Override default constructor to add the ability to set $errors and a retry
* map.
* *
* @param string $message * @param string $message
* @param int $code * @param int $code
* @param Exception|null $previous * @param Exception|null $previous
* @param [{string, string}] errors List of errors returned in an HTTP * @param [{string, string}] errors List of errors returned in an HTTP
* response. Defaults to []. * response. Defaults to [].
* @param array|null $retryMap Map of errors with retry counts.
*/ */
public function __construct( public function __construct(
$message, $message,
$code = 0, $code = 0,
Exception $previous = null, Exception $previous = null,
$errors = array() $errors = array(),
array $retryMap = null
) { ) {
if (version_compare(PHP_VERSION, '5.3.0') >= 0) { if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
@ -31,6 +56,10 @@ class Google_Service_Exception extends Google_Exception
} }
$this->errors = $errors; $this->errors = $errors;
if (is_array($retryMap)) {
$this->retryMap = $retryMap;
}
} }
/** /**
@ -50,4 +79,27 @@ class Google_Service_Exception extends Google_Exception
{ {
return $this->errors; return $this->errors;
} }
/**
* Gets the number of times the associated task can be retried.
*
* NOTE: -1 is returned if the task can be retried indefinitely
*
* @return integer
*/
public function allowedRetries()
{
if (isset($this->retryMap[$this->code])) {
return $this->retryMap[$this->code];
}
$errors = $this->getErrors();
if (!empty($errors) && isset($errors[0]['reason']) &&
isset($this->retryMap[$errors[0]['reason']])) {
return $this->retryMap[$errors[0]['reason']];
}
return 0;
}
} }

View File

@ -15,21 +15,15 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Client.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Exception.php'; require_once dirname(__FILE__) . '/../autoload.php';
require_once 'Google/Utils.php'; }
require_once 'Google/Http/Request.php';
require_once 'Google/Http/MediaFileUpload.php';
require_once 'Google/Http/REST.php';
/** /**
* Implements the actual methods/resources of the discovered Google API using magic function * Implements the actual methods/resources of the discovered Google API using magic function
* calling overloading (__call()), which on call will see if the method name (plus.activities.list) * calling overloading (__call()), which on call will see if the method name (plus.activities.list)
* is available in this service, and if so construct an apiHttpRequest representing it. * is available in this service, and if so construct an apiHttpRequest representing it.
* *
* @author Chris Chabot <chabotc@google.com>
* @author Chirag Shah <chirags@google.com>
*
*/ */
class Google_Service_Resource class Google_Service_Resource
{ {
@ -39,16 +33,16 @@ class Google_Service_Resource
'fields' => array('type' => 'string', 'location' => 'query'), 'fields' => array('type' => 'string', 'location' => 'query'),
'trace' => array('type' => 'string', 'location' => 'query'), 'trace' => array('type' => 'string', 'location' => 'query'),
'userIp' => array('type' => 'string', 'location' => 'query'), 'userIp' => array('type' => 'string', 'location' => 'query'),
'userip' => array('type' => 'string', 'location' => 'query'),
'quotaUser' => array('type' => 'string', 'location' => 'query'), 'quotaUser' => array('type' => 'string', 'location' => 'query'),
'data' => array('type' => 'string', 'location' => 'body'), 'data' => array('type' => 'string', 'location' => 'body'),
'mimeType' => array('type' => 'string', 'location' => 'header'), 'mimeType' => array('type' => 'string', 'location' => 'header'),
'uploadType' => array('type' => 'string', 'location' => 'query'), 'uploadType' => array('type' => 'string', 'location' => 'query'),
'mediaUpload' => array('type' => 'complex', 'location' => 'query'), 'mediaUpload' => array('type' => 'complex', 'location' => 'query'),
'prettyPrint' => array('type' => 'string', 'location' => 'query'),
); );
/** @var Google_Service $service */ /** @var string $rootUrl */
private $service; private $rootUrl;
/** @var Google_Client $client */ /** @var Google_Client $client */
private $client; private $client;
@ -56,6 +50,9 @@ class Google_Service_Resource
/** @var string $serviceName */ /** @var string $serviceName */
private $serviceName; private $serviceName;
/** @var string $servicePath */
private $servicePath;
/** @var string $resourceName */ /** @var string $resourceName */
private $resourceName; private $resourceName;
@ -64,17 +61,18 @@ class Google_Service_Resource
public function __construct($service, $serviceName, $resourceName, $resource) public function __construct($service, $serviceName, $resourceName, $resource)
{ {
$this->service = $service; $this->rootUrl = $service->rootUrl;
$this->client = $service->getClient(); $this->client = $service->getClient();
$this->servicePath = $service->servicePath;
$this->serviceName = $serviceName; $this->serviceName = $serviceName;
$this->resourceName = $resourceName; $this->resourceName = $resourceName;
$this->methods = isset($resource['methods']) ? $this->methods = is_array($resource) && isset($resource['methods']) ?
$resource['methods'] : $resource['methods'] :
array($resourceName => $resource); array($resourceName => $resource);
} }
/** /**
* TODO(ianbarber): This function needs simplifying. * TODO: This function needs simplifying.
* @param $name * @param $name
* @param $arguments * @param $arguments
* @param $expected_class - optional, the expected class name * @param $expected_class - optional, the expected class name
@ -84,6 +82,15 @@ class Google_Service_Resource
public function call($name, $arguments, $expected_class = null) public function call($name, $arguments, $expected_class = null)
{ {
if (! isset($this->methods[$name])) { if (! isset($this->methods[$name])) {
$this->client->getLogger()->error(
'Service method unknown',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name
)
);
throw new Google_Exception( throw new Google_Exception(
"Unknown function: " . "Unknown function: " .
"{$this->serviceName}->{$this->resourceName}->{$name}()" "{$this->serviceName}->{$this->resourceName}->{$name}()"
@ -108,10 +115,13 @@ class Google_Service_Resource
$this->convertToArrayAndStripNulls($parameters['postBody']); $this->convertToArrayAndStripNulls($parameters['postBody']);
} }
$postBody = json_encode($parameters['postBody']); $postBody = json_encode($parameters['postBody']);
if ($postBody === false && $parameters['postBody'] !== false) {
throw new Google_Exception("JSON encoding failed. Ensure all strings in the request are UTF-8 encoded.");
}
unset($parameters['postBody']); unset($parameters['postBody']);
} }
// TODO(ianbarber): optParams here probably should have been // TODO: optParams here probably should have been
// handled already - this may well be redundant code. // handled already - this may well be redundant code.
if (isset($parameters['optParams'])) { if (isset($parameters['optParams'])) {
$optParams = $parameters['optParams']; $optParams = $parameters['optParams'];
@ -124,11 +134,20 @@ class Google_Service_Resource
} }
$method['parameters'] = array_merge( $method['parameters'] = array_merge(
$method['parameters'], $this->stackParameters,
$this->stackParameters $method['parameters']
); );
foreach ($parameters as $key => $val) { foreach ($parameters as $key => $val) {
if ($key != 'postBody' && ! isset($method['parameters'][$key])) { if ($key != 'postBody' && ! isset($method['parameters'][$key])) {
$this->client->getLogger()->error(
'Service parameter unknown',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name,
'parameter' => $key
)
);
throw new Google_Exception("($name) unknown parameter: '$key'"); throw new Google_Exception("($name) unknown parameter: '$key'");
} }
} }
@ -138,6 +157,15 @@ class Google_Service_Resource
$paramSpec['required'] && $paramSpec['required'] &&
! isset($parameters[$paramName]) ! isset($parameters[$paramName])
) { ) {
$this->client->getLogger()->error(
'Service parameter missing',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name,
'parameter' => $paramName
)
);
throw new Google_Exception("($name) missing required param: '$paramName'"); throw new Google_Exception("($name) missing required param: '$paramName'");
} }
if (isset($parameters[$paramName])) { if (isset($parameters[$paramName])) {
@ -151,10 +179,18 @@ class Google_Service_Resource
} }
} }
$servicePath = $this->service->servicePath; $this->client->getLogger()->info(
'Service Call',
array(
'service' => $this->serviceName,
'resource' => $this->resourceName,
'method' => $name,
'arguments' => $parameters,
)
);
$url = Google_Http_REST::createRequestUri( $url = Google_Http_REST::createRequestUri(
$servicePath, $this->servicePath,
$method['path'], $method['path'],
$parameters $parameters
); );
@ -164,7 +200,12 @@ class Google_Service_Resource
null, null,
$postBody $postBody
); );
if ($this->rootUrl) {
$httpRequest->setBaseComponent($this->rootUrl);
} else {
$httpRequest->setBaseComponent($this->client->getBasePath()); $httpRequest->setBaseComponent($this->client->getBasePath());
}
if ($postBody) { if ($postBody) {
$contentTypeHeader = array(); $contentTypeHeader = array();
@ -187,6 +228,10 @@ class Google_Service_Resource
); );
} }
if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') {
$httpRequest->enableExpectedRaw();
}
if ($this->client->shouldDefer()) { if ($this->client->shouldDefer()) {
// If we are in batch or upload mode, return the raw request. // If we are in batch or upload mode, return the raw request.
return $httpRequest; return $httpRequest;

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Auth/Exception.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Signer/Abstract.php'; require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* Signs data. * Signs data.
@ -45,6 +46,8 @@ class Google_Signer_P12 extends Google_Signer_Abstract
// at the time. // at the time.
if (!$password && strpos($p12, "-----BEGIN RSA PRIVATE KEY-----") !== false) { if (!$password && strpos($p12, "-----BEGIN RSA PRIVATE KEY-----") !== false) {
$this->privateKey = openssl_pkey_get_private($p12); $this->privateKey = openssl_pkey_get_private($p12);
} elseif ($password === 'notasecret' && strpos($p12, "-----BEGIN PRIVATE KEY-----") !== false) {
$this->privateKey = openssl_pkey_get_private($p12);
} else { } else {
// This throws on error // This throws on error
$certs = array(); $certs = array();

View File

@ -18,8 +18,6 @@
/** /**
* Collection of static utility methods used for convenience across * Collection of static utility methods used for convenience across
* the client library. * the client library.
*
* @author Chirag Shah <chirags@google.com>
*/ */
class Google_Utils class Google_Utils
{ {

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
require_once 'Google/Auth/Exception.php'; if (!class_exists('Google_Client')) {
require_once 'Google/Verifier/Abstract.php'; require_once dirname(__FILE__) . '/../autoload.php';
}
/** /**
* Verifies signatures using PEM encoded certificates. * Verifies signatures using PEM encoded certificates.