public function sign(Postman_Google_Http_Request $request) { $key = $this->client->getClassConfig($this, 'developer_key'); if ($key) { $this->client->getLogger()->debug('Simple API Access developer key authentication'); $request->setQueryParam('key', $key); } return $request; }
public function sign(Postman_Google_Http_Request $request) { if (!$this->token) { // No token, so nothing to do. return $request; } $this->client->getLogger()->debug('App Identity authentication'); // Add the OAuth2 header to the request $request->setRequestHeaders(array('Authorization' => 'Bearer ' . $this->token['access_token'])); return $request; }
private function getResumeUri() { $result = null; $body = $this->request->getPostBody(); if ($body) { $headers = array('content-type' => 'application/json; charset=UTF-8', 'content-length' => Postman_Google_Utils::getStrLen($body), 'x-upload-content-type' => $this->mimeType, 'x-upload-content-length' => $this->size, 'expect' => ''); $this->request->setRequestHeaders($headers); } $response = $this->client->getIo()->makeRequest($this->request); $location = $response->getResponseHeader('location'); $code = $response->getResponseHttpCode(); if (200 == $code && true == $location) { return $location; } $message = $code; $body = @json_decode($response->getResponseBody()); if (!empty($body->error->errors)) { $message .= ': '; foreach ($body->error->errors as $error) { $message .= "{$error->domain}, {$error->message};"; } $message = rtrim($message, ';'); } $error = "Failed to start the resumable upload (HTTP {$message})"; $this->client->getLogger()->error($error); throw new Postman_Google_Exception($error); }
public function testGzipSupport() { $url = 'http://localhost:8080/foo/bar?foo=a&foo=b&wowee=oh+my'; $request = new Postman_Google_Http_Request($url); $request->enableGzip(); $this->assertStringEndsWith(Postman_Google_Http_Request::GZIP_UA, $request->getUserAgent()); $this->assertArrayHasKey('accept-encoding', $request->getRequestHeaders()); $this->assertTrue($request->canGzip()); $request->disableGzip(); $this->assertStringEndsNotWith(Postman_Google_Http_Request::GZIP_UA, $request->getUserAgent()); $this->assertArrayNotHasKey('accept-encoding', $request->getRequestHeaders()); $this->assertFalse($request->canGzip()); }
public function testProcess() { $client = $this->getClient(); $data = 'foo'; // Test data *only* uploads. $request = new Postman_Google_Http_Request('http://www.example.com', 'POST'); $media = new Postman_Google_Http_MediaFileUpload($client, $request, 'image/png', $data, false); $this->assertEquals($data, $request->getPostBody()); // Test resumable (meta data) - we want to send the metadata, not the app data. $request = new Postman_Google_Http_Request('http://www.example.com', 'POST'); $reqData = json_encode("hello"); $request->setPostBody($reqData); $media = new Postman_Google_Http_MediaFileUpload($client, $request, 'image/png', $data, true); $this->assertEquals(json_decode($reqData), $request->getPostBody()); // Test multipart - we are sending encoded meta data and post data $request = new Postman_Google_Http_Request('http://www.example.com', 'POST'); $reqData = json_encode("hello"); $request->setPostBody($reqData); $media = new Postman_Google_Http_MediaFileUpload($client, $request, 'image/png', $data, false); $this->assertContains($reqData, $request->getPostBody()); $this->assertContains(base64_encode($data), $request->getPostBody()); }
/** * Decode an HTTP Response. * @static * @throws Postman_Google_Service_Exception * @param Postman_Google_Http_Request $response The http response to be decoded. * @param Postman_Google_Client $client * @return mixed|null */ public static function decodeHttpResponse($response, Postman_Google_Client $client = null) { $code = $response->getResponseHttpCode(); $body = $response->getResponseBody(); $decoded = null; if (intVal($code) >= 300) { $decoded = json_decode($body, true); $err = 'Error calling ' . $response->getRequestMethod() . ' ' . $response->getUrl(); if (isset($decoded['error']) && isset($decoded['error']['message']) && isset($decoded['error']['code'])) { // if we're getting a json encoded error definition, use that instead of the raw response // body for improved readability $err .= ": ({$decoded['error']['code']}) {$decoded['error']['message']}"; } else { $err .= ": ({$code}) {$body}"; } $errors = null; // Specific check for APIs which don't return error details, such as Blogger. if (isset($decoded['error']) && isset($decoded['error']['errors'])) { $errors = $decoded['error']['errors']; } if ($client) { $client->getLogger()->error($err, array('code' => $code, 'errors' => $errors)); } throw new Postman_Google_Service_Exception($err, $code, null, $errors); } // Only attempt to decode the response, if the response code wasn't (204) 'no content' if ($code != '204') { $decoded = json_decode($body, true); if ($decoded === null || $decoded === "") { $error = "Invalid json in service response: {$body}"; if ($client) { $client->getLogger()->error($error); } throw new Postman_Google_Service_Exception($error); } if ($response->getExpectedClass()) { $class = $response->getExpectedClass(); $decoded = new $class($decoded); } } return $decoded; }
public function testMustRevalidate() { $now = time(); $client = $this->getClient(); // Expires 1 year in the future, and contains the must-revalidate directive. // Don't revalidate. must-revalidate only applies to expired entries. $future = $now + 365 * 24 * 60 * 60; $resp = new Postman_Google_Http_Request('http://localhost', 'GET'); $resp->setResponseHttpCode('200'); $resp->setResponseHeaders(array('Cache-Control' => 'max-age=3600, must-revalidate', 'Expires' => gmdate('D, d M Y H:i:s', $future) . ' GMT', 'Date' => gmdate('D, d M Y H:i:s', $now) . ' GMT')); $this->assertFalse(Postman_Google_Http_CacheParser::mustRevalidate($resp)); // Contains the max-age=3600 directive, but was created 2 hours ago. // Must revalidate. $past = $now - 2 * 60 * 60; $resp = new Postman_Google_Http_Request('http://localhost', 'GET'); $resp->setResponseHttpCode('200'); $resp->setResponseHeaders(array('Cache-Control' => 'max-age=3600', 'Expires' => gmdate('D, d M Y H:i:s', $future) . ' GMT', 'Date' => gmdate('D, d M Y H:i:s', $past) . ' GMT')); $this->assertTrue(Postman_Google_Http_CacheParser::mustRevalidate($resp)); // Contains the max-age=3600 directive, and was created 600 seconds ago. // No need to revalidate, regardless of the expires header. $past = $now - 600; $resp = new Postman_Google_Http_Request('http://localhost', 'GET'); $resp->setResponseHttpCode('200'); $resp->setResponseHeaders(array('Cache-Control' => 'max-age=3600', 'Expires' => gmdate('D, d M Y H:i:s', $past) . ' GMT', 'Date' => gmdate('D, d M Y H:i:s', $past) . ' GMT')); $this->assertFalse(Postman_Google_Http_CacheParser::mustRevalidate($resp)); }
public function processEntityRequest($io, $client) { $req = new Postman_Google_Http_Request("http://localhost.com"); $req->setRequestMethod("POST"); // Verify that the content-length is calculated. $req->setPostBody("{}"); $io->processEntityRequest($req); $this->assertEquals(2, $req->getRequestHeader("content-length")); // Test an empty post body. $req->setPostBody(""); $io->processEntityRequest($req); $this->assertEquals(0, $req->getRequestHeader("content-length")); // Test a null post body. $req->setPostBody(null); $io->processEntityRequest($req); $this->assertEquals(0, $req->getRequestHeader("content-length")); // Set an array in the postbody, and verify that it is url-encoded. $req->setPostBody(array("a" => "1", "b" => 2)); $io->processEntityRequest($req); $this->assertEquals(7, $req->getRequestHeader("content-length")); $this->assertEquals(Postman_Google_IO_Abstract::FORM_URLENCODED, $req->getRequestHeader("content-type")); $this->assertEquals("a=1&b=2", $req->getPostBody()); // Verify that the content-type isn't reset. $payload = array("a" => "1", "b" => 2); $req->setPostBody($payload); $req->setRequestHeaders(array("content-type" => "multipart/form-data")); $io->processEntityRequest($req); $this->assertEquals("multipart/form-data", $req->getRequestHeader("content-type")); $this->assertEquals($payload, $req->getPostBody()); }
/** * Execute an HTTP Request * * @param Postman_Google_HttpRequest $request the http request to be executed * @return Postman_Google_HttpRequest http request with the response http code, * response headers and response body filled in * @throws Postman_Google_IO_Exception on curl or IO error */ public function executeRequest(Postman_Google_Http_Request $request) { $curl = curl_init(); if ($request->getPostBody()) { curl_setopt($curl, CURLOPT_POSTFIELDS, $request->getPostBody()); } $requestHeaders = $request->getRequestHeaders(); if ($requestHeaders && is_array($requestHeaders)) { $curlHeaders = array(); foreach ($requestHeaders as $k => $v) { $curlHeaders[] = "{$k}: {$v}"; } curl_setopt($curl, CURLOPT_HTTPHEADER, $curlHeaders); } curl_setopt($curl, CURLOPT_URL, $request->getUrl()); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $request->getRequestMethod()); curl_setopt($curl, CURLOPT_USERAGENT, $request->getUserAgent()); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); // 1 is CURL_SSLVERSION_TLSv1, which is not always defined in PHP. curl_setopt($curl, CURLOPT_SSLVERSION, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HEADER, true); if ($request->canGzip()) { curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); } foreach ($this->options as $key => $var) { curl_setopt($curl, $key, $var); } if (!isset($this->options[CURLOPT_CAINFO])) { 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); if ($response === false) { $error = curl_error($curl); $this->client->getLogger()->error('cURL ' . $error); throw new Postman_Google_IO_Exception($error); } $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); list($responseHeaders, $responseBody) = $this->parseHttpResponse($response, $headerSize); $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); }
/** * @static * @param Postman_Google_Http_Request $resp * @return bool True if the HTTP response is considered to be expired. * False if it is considered to be fresh. */ public static function isExpired(Postman_Google_Http_Request $resp) { // HTTP/1.1 clients and caches MUST treat other invalid date formats, // especially including the value “0”, as in the past. $parsedExpires = false; $responseHeaders = $resp->getResponseHeaders(); if (isset($responseHeaders['expires'])) { $rawExpires = $responseHeaders['expires']; // Check for a malformed expires header first. if (empty($rawExpires) || is_numeric($rawExpires) && $rawExpires <= 0) { return true; } // See if we can parse the expires header. $parsedExpires = strtotime($rawExpires); if (false == $parsedExpires || $parsedExpires <= 0) { return true; } } // Calculate the freshness of an http response. $freshnessLifetime = false; $cacheControl = $resp->getParsedCacheControl(); if (isset($cacheControl['max-age'])) { $freshnessLifetime = $cacheControl['max-age']; } $rawDate = $resp->getResponseHeader('date'); $parsedDate = strtotime($rawDate); if (empty($rawDate) || false == $parsedDate) { // We can't default this to now, as that means future cache reads // will always pass with the logic below, so we will require a // date be injected if not supplied. throw new Postman_Google_Exception("All cacheable requests must have creation dates."); } if (false == $freshnessLifetime && isset($responseHeaders['expires'])) { $freshnessLifetime = $parsedExpires - $parsedDate; } if (false == $freshnessLifetime) { return true; } // Calculate the age of an http response. $age = max(0, time() - $parsedDate); if (isset($responseHeaders['age'])) { $age = max($age, strtotime($responseHeaders['age'])); } return $freshnessLifetime <= $age; }
/** * TODO(ianbarber): This function needs simplifying. * @param $name * @param $arguments * @param $expected_class - optional, the expected class name * @return Postman_Google_Http_Request|expected_class * @throws Postman_Google_Exception */ public function call($name, $arguments, $expected_class = null) { if (!isset($this->methods[$name])) { $this->client->getLogger()->error('Service method unknown', array('service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name)); throw new Postman_Google_Exception("Unknown function: " . "{$this->serviceName}->{$this->resourceName}->{$name}()"); } $method = $this->methods[$name]; $parameters = $arguments[0]; // postBody is a special case since it's not defined in the discovery // document as parameter, but we abuse the param entry for storing it. $postBody = null; if (isset($parameters['postBody'])) { if ($parameters['postBody'] instanceof Postman_Google_Model) { // In the cases the post body is an existing object, we want // to use the smart method to create a simple object for // for JSONification. $parameters['postBody'] = $parameters['postBody']->toSimpleObject(); } else { if (is_object($parameters['postBody'])) { // If the post body is another kind of object, we will try and // wrangle it into a sensible format. $parameters['postBody'] = $this->convertToArrayAndStripNulls($parameters['postBody']); } } $postBody = json_encode($parameters['postBody']); unset($parameters['postBody']); } // TODO(ianbarber): optParams here probably should have been // handled already - this may well be redundant code. if (isset($parameters['optParams'])) { $optParams = $parameters['optParams']; unset($parameters['optParams']); $parameters = array_merge($parameters, $optParams); } if (!isset($method['parameters'])) { $method['parameters'] = array(); } $method['parameters'] = array_merge($method['parameters'], $this->stackParameters); foreach ($parameters as $key => $val) { 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 Postman_Google_Exception("({$name}) unknown parameter: '{$key}'"); } } foreach ($method['parameters'] as $paramName => $paramSpec) { if (isset($paramSpec['required']) && $paramSpec['required'] && !isset($parameters[$paramName])) { $this->client->getLogger()->error('Service parameter missing', array('service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name, 'parameter' => $paramName)); throw new Postman_Google_Exception("({$name}) missing required param: '{$paramName}'"); } if (isset($parameters[$paramName])) { $value = $parameters[$paramName]; $parameters[$paramName] = $paramSpec; $parameters[$paramName]['value'] = $value; unset($parameters[$paramName]['required']); } else { // Ensure we don't pass nulls. unset($parameters[$paramName]); } } $servicePath = $this->service->servicePath; $this->client->getLogger()->info('Service Call', array('service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name, 'arguments' => $parameters)); $url = Postman_Google_Http_REST::createRequestUri($servicePath, $method['path'], $parameters); $httpRequest = new Postman_Google_Http_Request($url, $method['httpMethod'], null, $postBody); $httpRequest->setBaseComponent($this->client->getBasePath()); if ($postBody) { $contentTypeHeader = array(); $contentTypeHeader['content-type'] = 'application/json; charset=UTF-8'; $httpRequest->setRequestHeaders($contentTypeHeader); $httpRequest->setPostBody($postBody); } $httpRequest = $this->client->getAuth()->sign($httpRequest); $httpRequest->setExpectedClass($expected_class); if (isset($parameters['data']) && ($parameters['uploadType']['value'] == 'media' || $parameters['uploadType']['value'] == 'multipart')) { // If we are doing a simple media upload, trigger that as a convenience. $mfu = new Postman_Google_Http_MediaFileUpload($this->client, $httpRequest, isset($parameters['mimeType']) ? $parameters['mimeType']['value'] : 'application/octet-stream', $parameters['data']['value']); } if ($this->client->shouldDefer()) { // If we are in batch or upload mode, return the raw request. return $httpRequest; } return $this->client->execute($httpRequest); }
public function parseResponse(Postman_Google_Http_Request $response) { $contentType = $response->getResponseHeader('content-type'); $contentType = explode(';', $contentType); $boundary = false; foreach ($contentType as $part) { $part = explode('=', $part, 2); if (isset($part[0]) && 'boundary' == trim($part[0])) { $boundary = $part[1]; } } $body = $response->getResponseBody(); if ($body) { $body = str_replace("--{$boundary}--", "--{$boundary}", $body); $parts = explode("--{$boundary}", $body); $responses = array(); foreach ($parts as $part) { $part = trim($part); if (!empty($part)) { list($metaHeaders, $part) = explode("\r\n\r\n", $part, 2); $metaHeaders = $this->client->getIo()->getHttpResponseHeaders($metaHeaders); $status = substr($part, 0, strpos($part, "\n")); $status = explode(" ", $status); $status = $status[1]; list($partHeaders, $partBody) = $this->client->getIo()->ParseHttpResponse($part, false); $response = new Postman_Google_Http_Request(""); $response->setResponseHttpCode($status); $response->setResponseHeaders($partHeaders); $response->setResponseBody($partBody); // Need content id. $key = $metaHeaders['content-id']; if (isset($this->expected_classes[$key]) && strlen($this->expected_classes[$key]) > 0) { $class = $this->expected_classes[$key]; $response->setExpectedClass($class); } try { $response = Postman_Google_Http_REST::decodeHttpResponse($response, $this->client); $responses[$key] = $response; } catch (Postman_Google_Service_Exception $e) { // Store the exception as the response, so successful responses // can be processed. $responses[$key] = $e; } } } return $responses; } return null; }
/** * Execute an HTTP Request * * @param Postman_Google_HttpRequest $request the http request to be executed * @return Postman_Google_HttpRequest http request with the response http code, * response headers and response body filled in * @throws Postman_Google_IO_Exception on curl or IO error */ public function executeRequest(Postman_Google_Http_Request $request) { $default_options = stream_context_get_options(stream_context_get_default()); $requestHttpContext = array_key_exists('http', $default_options) ? $default_options['http'] : array(); if ($request->getPostBody()) { $requestHttpContext["content"] = $request->getPostBody(); } $requestHeaders = $request->getRequestHeaders(); if ($requestHeaders && is_array($requestHeaders)) { $headers = ""; foreach ($requestHeaders as $k => $v) { $headers .= "{$k}: {$v}\r\n"; } $requestHttpContext["header"] = $headers; } $requestHttpContext["method"] = $request->getRequestMethod(); $requestHttpContext["user_agent"] = $request->getUserAgent(); $requestSslContext = array_key_exists('ssl', $default_options) ? $default_options['ssl'] : array(); if (!array_key_exists("cafile", $requestSslContext)) { $requestSslContext["cafile"] = dirname(__FILE__) . '/cacerts.pem'; } $options = array("http" => array_merge(self::$DEFAULT_HTTP_CONTEXT, $requestHttpContext), "ssl" => array_merge(self::$DEFAULT_SSL_CONTEXT, $requestSslContext)); $context = stream_context_create($options); $url = $request->getUrl(); if ($request->canGzip()) { $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 // throwing an exception. $this->trappedErrorNumber = null; $this->trappedErrorString = null; // START - error trap. set_error_handler(array($this, 'trapError')); $fh = fopen($url, 'r', false, $context); restore_error_handler(); // END - error trap. if ($this->trappedErrorNumber) { $error = sprintf("HTTP Error: Unable to connect: '%s'", $this->trappedErrorString); $this->client->getLogger()->error('Stream ' . $error); throw new Postman_Google_IO_Exception($error, $this->trappedErrorNumber); } $response_data = false; $respHttpCode = self::UNKNOWN_CODE; if ($fh) { if (isset($this->options[self::TIMEOUT])) { // @jason: added @ to hide PHP warnings if the host has disabled stream_set_timeout @stream_set_timeout($fh, $this->options[self::TIMEOUT]); } $response_data = stream_get_contents($fh); fclose($fh); $respHttpCode = $this->getHttpResponseCode($http_response_header); } if (false === $response_data) { $error = sprintf("HTTP Error: Unable to connect: '%s'", $respHttpCode); $this->client->getLogger()->error('Stream ' . $error); throw new Postman_Google_IO_Exception($error, $respHttpCode); } $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); }
/** * Revoke an OAuth2 access token or refresh token. This method will revoke the current access * token, if a token isn't provided. * @throws Postman_Google_Auth_Exception * @param string|null $token The token (access token or a refresh token) that should be revoked. * @return boolean Returns True if the revocation was successful, otherwise False. */ public function revokeToken($token = null) { if (!$token) { if (!$this->token) { // Not initialized, no token to actually revoke return false; } elseif (array_key_exists('refresh_token', $this->token)) { $token = $this->token['refresh_token']; } else { $token = $this->token['access_token']; } } $request = new Postman_Google_Http_Request(self::OAUTH2_REVOKE_URI, 'POST', array(), "token={$token}"); $request->disableGzip(); $response = $this->client->getIo()->makeRequest($request); $code = $response->getResponseHttpCode(); if ($code == 200) { $this->token = null; return true; } return false; }
/** * Check if an already cached request must be revalidated, and if so update * the request with the correct ETag headers. * @param Postman_Google_Http_Request $cached A previously cached response. * @param Postman_Google_Http_Request $request The outbound request. * return bool If the cached object needs to be revalidated, false if it is * still current and can be re-used. */ protected function checkMustRevalidateCachedRequest($cached, $request) { if (Postman_Google_Http_CacheParser::mustRevalidate($cached)) { $addHeaders = array(); if ($cached->getResponseHeader('etag')) { // [13.3.4] If an entity tag has been provided by the origin server, // we must use that entity tag in any cache-conditional request. $addHeaders['If-None-Match'] = $cached->getResponseHeader('etag'); } elseif ($cached->getResponseHeader('date')) { $addHeaders['If-Modified-Since'] = $cached->getResponseHeader('date'); } $request->setRequestHeaders($addHeaders); return true; } else { return false; } }
/** * @expectedException Postman_Google_Service_Exception */ public function tesProperErrorFormatting() { $request = new Postman_Google_Http_Request("/a/b"); $request->setResponseHttpCode(401); $request->setResponseBody('{ error: { errors: [ { "domain": "global", "reason": "authError", "message": "Invalid Credentials", "locationType": "header", "location": "Authorization", } ], "code": 401, "message": "Invalid Credentials" }'); Postman_Google_Http_Rest::decodeHttpResponse($request); }