<?php
namespace Eher\OAth;

require_once "Eher/OAuth/SignatureMethod.php";
require_once "Eher/OAuth/Consumer.php";
require_once "Eher/OAuth/HmacSha1.php";
require_once "Eher/OAuth/OAuthDataStore.php";
require_once "Eher/OAuth/OAuthException.php";
require_once "Eher/OAuth/OAuthServer.php";
require_once "Eher/OAuth/PlainText.php";
require_once "Eher/OAuth/Request.php";
require_once "Eher/OAuth/Token.php";
require_once "Eher/OAuth/Util.php";

namespace Barracuda\Copy;
/**
 * Copy API class
 *
 * @package Copy
 * @license https://raw.github.com/copy-app/php-client-library/master/LICENSE MIT
 */
class API
{
	/**
	 * API URl
	 * @var string $api_url
	 */
	protected $api_url = 'https://api.copy.com';
	/**
	 * Instance of curl
	 * @var resource $curl
	 */
	private $curl;
	/**
	 * @var array
	 * User data
	 */
	private $signature;
	/**
	 * Constructor
	 *
	 * @param string $consumerKey    OAuth consumer key
	 * @param string $consumerSecret OAuth consumer secret
	 * @param string $accessToken    OAuth access token
	 * @param string $tokenSecret    OAuth token secret
	 */
	public function __construct($consumerKey, $consumerSecret, $accessToken, $tokenSecret)
	{
		// oauth setup
		$this->signature = array(
				'consumer_key' => $consumerKey,
				'shared_secret' => $consumerSecret,
				'oauth_token' => $accessToken,
				'oauth_secret' => $tokenSecret
		);
		$this->__wakeup();
	}
	/**
	 * Wakeup function on unserialize
	 *
	 */
	private function __wakeup()
	{
		// curl setup
		$this->curl = curl_init();
		if (!$this->curl) {
			throw new \Exception("Failed to initialize curl");
		}
		// ca bundle
		//$cacrt = __DIR__ . '/ca.crt';
		$cacrt = __DIR__ . '/ca-root.crt';
		if (!is_file($cacrt)) {
			throw new \Exception("Failed to load ca certificate");
		}
		// check for phar execution
		// in case we have to move the .crt file to a temp folder so curl is able to load it
		if (substr(__FILE__, 0, 7) == 'phar://') {
			$cacrt = self::extractPharCacert($cacrt);
		}
		curl_setopt($this->curl, CURLOPT_CAINFO, $cacrt);
	}
	/**
	 * Upload a file from a string
	 *
	 * @param string $path full path containing leading slash and file name
	 * @param string $data binary data
	 *
	 * @return object described in createFile()
	 */
	public function uploadFromString($path, $data)
	{
		// create the temporary stream
		$stream = fopen('php://temp', 'w+');
		// write the data
		fwrite($stream, $data);
		// rewind the pointer
		rewind($stream);
		// upload as a stream
		return $this->uploadFromStream($path, $stream);
	}
	/**
	 * Upload a file from a stream resource
	 *
	 * @param string $path full path containing leading slash and file name
	 * @param resource $stream resource to read data from
	 *
	 * @return object described in createFile()
	 */
	public function uploadFromStream($path, $stream)
	{
		// send data 1MB at a time
		$parts = array();
		$limit = 1048576;
		$buffer = '';
		while ($buffer .= fread($stream, $limit)) {
			// check $buffer size for remote stream
			// ref. http://php.net/manual/function.fread.php
			// see Example #3 Remote fread() examples
			if (!feof($stream) && strlen($buffer) < $limit) {
				continue;
			}
			$next = '';
			if (strlen($buffer) > $limit) {
				$next = substr($buffer, $limit);
				$buffer = substr($buffer, 0, $limit);
			}
			$parts[] = $this->sendData($buffer);
			$buffer = $next;
		}
		// close the stream
		fclose($stream);
		// update the file in the cloud
		return $this->createFile('/' . $path, $parts);
	}
	/**
	 * Read a file to a string
	 *
	 * @param string $path full path containing leading slash and file name
	 *
	 * @return array contains key of contents which contains binary data of the file
	 */
	public function readToString($path)
	{
		$object = $this->readToStream($path);
		$object['contents'] = stream_get_contents($object['stream']);
		fclose($object['stream']);
		unset($object['stream']);
		return $object;
	}
	/**
	 * Read a file to a stream
	 *
	 * @param string $path full path containing leading slash and file name
	 *
	 * @return array contains key of stream which contains a stream resource
	 */
	public function readToStream($path)
	{
		// create the temporary stream
		$stream = fopen('php://temp', 'w+');
		// obtain the list of parts for the file (should be an array of one)
		$files = $this->listPath('/' . $path, array('include_parts' => true));
		if (is_array($files) === false || sizeof($files) !== 1) {
			throw new \Exception("Could not find file at path: '" . $path . "'");
		}
		// found it, verify its a file
		$file = array_pop($files);
		if ($file->{"type"} != "file") {
			throw new \Exception("Could not find file at path: '" . $path . "'");
		}
		// obtain each part and add it to the stream
		foreach ($file->{"revisions"}[0]->{"parts"} as $part) {
			$data = $this->getPart($part->{"fingerprint"}, $part->{"size"}, $file->{"share_id"});
			fwrite($stream, $data);
		}
		// rewind the pointer
		rewind($stream);
		return compact('stream');
	}
	/**
	 * Send a request to remove a given file.
	 *
	 * @param string $path full path containing leading slash and file name
	 *
	 * @return bool true if the file was removed successfully
	 */
	public function removeFile($path)
	{
		return $this->removeItem($path, 'file');
	}
	/**
	 * Send a request to remove a given dir.
	 *
	 * @param string $path full path containing leading slash and dir name
	 *
	 * @return bool true if the dir was removed successfully
	 */
	public function removeDir($path)
	{
		return $this->removeItem($path, 'dir');
	}
	/**
	 * Send a request to remove a given item.
	 *
	 * @param string $path full path containing leading slash and file name
	 * @param string $type file or dir
	 *
	 * @return bool true if the item was removed successfully
	 */
	private function removeItem($path, $type)
	{
		$request = array();
		$request["object_type"] = $type;
		$this->updateObject('remove', $path, $request);
		return true;
	}
	/**
	 * Rename a file
	 *
	 * Object structure:
	 * {
	 *  object_id: "4008"
	 *  path: "/example"
	 *  type: "dir" || "file"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 *  mime_type: string
	 *  revisions: array of revision objects
	 * }
	 *
	 * @param string $source_path full path containing leading slash and file name
	 * @param string $destination_path full path containing leading slash and file name
	 *
	 * @return stdClass using structure as noted above
	 */
	public function rename($source_path, $destination_path)
	{
		return $this->updateObject('rename', $source_path, array('new_path' => $destination_path));
	}
	/**
	 * Copy an item
	 *
	 * Object structure:
	 * {
	 *  object_id: "4008"
	 *  path: "/example"
	 *  type: "dir" || "file"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 *  mime_type: string
	 *  revisions: array of revision objects
	 * }
	 *
	 * @param string $source_path full path containing leading slash and file name
	 * @param string $destination_path full path containing leading slash and file name
	 *
	 * @return stdClass using structure as noted above
	 */
	public function copy($source_path, $destination_path)
	{
		return $this->updateObject('copy', $source_path, array('new_path' => $destination_path));
	}
	/**
	 * List objects within a path
	 *
	 * Object structure:
	 * {
	 *  object_id: "4008"
	 *  path: "/example"
	 *  type: "dir" || "file"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 *  mime_type: string
	 *  revisions: array of revision objects
	 * }
	 *
	 * @param  string $path              full path with leading slash and optionally a filename
	 * @param  array  $additionalOptions used for passing options such as include_parts
	 *
	 * @return array List of file/folder objects described above.
	 */
	public function listPath($path, $additionalOptions = null)
	{
		$list_watermark = false;
		$return = array();
		do {
			$request = array();
			$request["path"] = $path;
			$request["max_items"] = 100;
			$request["list_watermark"] = $list_watermark;
			if ($additionalOptions) {
				$request = array_merge($request, $additionalOptions);
			}
			$result = $this->post("list_objects", $this->encodeRequest("list_objects", $request), true);
			// add the children if we got some, otherwise add the root object itself to the return
			if (isset($result->result->children) && empty($result->result->children) === false) {
				$return = array_merge($return, $result->result->children);
				$list_watermark = $result->result->list_watermark;
			} else {
				$return[] = $result->result->object;
			}
		} while (isset($result->result->more_items) && $result->result->more_items == 1);
		return $return;
	}
	/**
	 * Get directory or file meta data
	 *
	 * Object structure:
	 * {
	 *  id: "/copy/example"
	 *  path: "/example"
	 *  name: "example",
	 *  type: "dir" || "file"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 *  mime_type: string
	 *  revisions: array of revision objects
	 *  children: array of children objects
	 * }
	 *
	 * @param  string $path  full path with leading slash and optionally a filename
	 * @param  string $root  Optional, "copy" is the first level of the real filesystem
	 *
	 * @return array List of file/folder objects described above.
	 */
	public function getMeta($path, $root = "copy")
	{
		$result = $this->get("meta/" . $root . $path);
		// Decode the json reply
		$result = json_decode($result);
		// Check for errors
		if (isset($result->error)) {
			if ($result->error == 1301) {
				// item not found
				return array();
			}
			throw new \Exception("Error listing path " . $path . ": (" . $result->error . ") '" . $result->message . "'");
		}
		return $result;
	}
	/**
	 * Create a dir
	 *
	 * Object structure:
	 * {
	 *  object_id: "4008"
	 *  path: "/example"
	 *  type: "dir"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 * }
	 *
	 * @param string $path      full path containing leading slash and dir name
	 * @param bool   $recursive true to create parent directories
	 *
	 * @return object described above.
	 */
	public function createDir($path, $recursive = true)
	{
		$request = array(
				'object_type' => 'dir',
				'recurse' => $recursive,
		);
		return $this->updateObject('create', $path, $request);
	}
	/**
	 * Create a file with a set of data parts
	 *
	 * Object structure:
	 * {
	 *  object_id: "4008"
	 *  path: "/example"
	 *  type: "file"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 *  mime_type: string
	 *  revisions: array of revision objects
	 * }
	 *
	 * @param string $path  full path containing leading slash and file name
	 * @param array  $parts contains arrays of parts returned by \Barracuda\Copy\API\sendData
	 *
	 * @return object described above.
	 */
	public function createFile($path, $parts)
	{
		$request = array();
		$request["object_type"] = "file";
		$request["parts"] = array();
		$offset = 0;
		foreach ($parts as $part) {
			$partRequest = array(
					'fingerprint' => $part["fingerprint"],
					'offset' => $offset,
					'size' => $part["size"],
			);
			array_push($request["parts"], $partRequest);
			$offset += $part["size"];
		}
		$request["size"] = $offset;
		return $this->updateObject('create', $path, $request);
	}
	/**
	 * Generate the fingerprint for a string of data.
	 *
	 * @param string $data Data part to generate the fingerprint for.
	 *
	 * @return string Fingerprint for $data.
	 **/
	public function fingerprint($data)
	{
		return md5($data) . sha1($data);
	}
	/**
	 * Send a piece of data
	 *
	 * @param  string $data    binary data
	 * @param  int    $shareId setting this to zero is best, unless share id is known
	 *
	 * @return array  contains fingerprint and size, to be used when creating a file
	 */
	public function sendData($data, $shareId = 0)
	{
		// first generate a part hash
		$fingerprint = $this->fingerprint($data);
		$part_size = strlen($data);
		// see if the cloud has this part, and send if needed
		if(!$this->hasPart($fingerprint, $part_size, $shareId)) {
			$this->sendPart($fingerprint, $part_size, $data, $shareId);
		}
		// return information about this part
		return array("fingerprint" => $fingerprint, "size" => $part_size);
	}
	/**
	 * Send a data part
	 *
	 * @param string $fingerprint md5 and sha1 concatenated
	 * @param int    $size        number of bytes
	 * @param string $data        binary data
	 * @param int    $shareId     setting this to zero is best, unless share id is known
	 *
	 */
	public function sendPart($fingerprint, $size, $data, $shareId = 0)
	{
		// They must match
		if (md5($data) . sha1($data) != $fingerprint) {
			throw new \Exception("Failed to validate part hash");
		}
		$request = array(
				'parts' => array(
						array(
								'share_id' => $shareId,
								'fingerprint' => $fingerprint,
								'size' => $size,
								'data' => 'BinaryData-0-' . $size
						)
				)
		);
		$result = $this->post("send_object_parts_v2", $this->encodeRequest("send_object_parts_v2", $request) . chr(0) . $data, true);
		if ($result->result->has_failed_parts) {
			throw new \Exception("Error sending part: " . $result->result->failed_parts[0]->message);
		}
	}
	/**
	 * Check to see if a part already exists
	 *
	 * @param  string $fingerprint md5 and sha1 concatenated
	 * @param  int    $size        number of bytes
	 * @param  int    $shareId     setting this to zero is best, unless share id is known
	 * @return bool   true if part already exists
	 */
	public function hasPart($fingerprint, $size, $shareId = 0)
	{
		$request = array(
				'parts' => array(
						array(
								'share_id' => $shareId,
								'fingerprint' => $fingerprint,
								'size' => $size
						)
				)
		);
		$result = $this->post("has_object_parts_v2", $this->encodeRequest("has_object_parts_v2", $request), true);
		if (empty($result->result->needed_parts)) {
			return true;
		} else {
			$part = $result->result->needed_parts[0];
			if (!empty($part->message)) {
				throw new \Exception("Error checking for part: " . $part->message);
			} else {
				return false;
			}
		}
	}
	/**
	 * Get a part
	 *
	 * @param  string $fingerprint md5 and sha1 concatinated
	 * @param  int    $size        number of bytes
	 * @param  int    $shareId     setting this to zero is best, unless share id is known
	 *
	 * @return string binary data
	 */
	public function getPart($fingerprint, $size, $shareId = 0)
	{
		$request = array(
				'parts' => array(
						array(
								'share_id' => $shareId,
								'fingerprint' => $fingerprint,
								'size' => $size
						)
				)
		);
		$result = $this->post("get_object_parts_v2", $this->encodeRequest("get_object_parts_v2", $request));
		// Find the null byte
		$null_offset = strpos($result, chr(0));
		// Grab the binary payload
		$binary = substr($result, $null_offset + 1, strlen($result) - $null_offset);
		if ($binary === false) {
			throw new \Exception("Error getting part data");
		}
		// Grab the json payload
		$json = isset($binary) ? substr($result, 0, $null_offset) : $result;
		if ($json === false) {
			throw new \Exception("Error getting part data");
		}
		// Decode the json reply
		$result = json_decode($json);
		// Check for errors
		if (isset($result->error)) {
			throw new \Exception("Error getting part data");
		}
		if (isset($result->result->parts[0]->message)) {
			throw new \Exception("Error getting part data: " . $result->result->parts[0]->message);
		}
		// Get the part data (since there is only one part the binary payload should just be the data)
		if (strlen($binary) != $size) {
			throw new \Exception("Error getting part data");
		}
		return $binary;
	}
	/**
	 * Create a New Link
	 *
	 * Object structure:
	 * {
	 *   id: "MBrss3roGDk4",
	 *   name: "My Cool Shared Files",
	 *   public: true,
	 *   url: "https://copy.com/MBrss3roGDk4",
	 *   url_short: "https://copy.com/MBrss3roGDk4",
	 *   creator_id: "1381231",
	 *   company_id: null,
	 *   confirmation_required: false,
	 *   status: "viewed",
	 *   permissions: "read"
	 * }
	 *
	 * @param array|string  $paths   target item(s) path
	 * @param array         $options option attributes, (bool) "public", (string) "name"
	 * @param string        $root
	 *
	 * @throws \Exception
	 *
	 * @return object described above.
	 */
	public function createLink($paths, $options = array(), $root = 'copy')
	{
		if (is_string($paths)) {
			$paths = array($paths);
		}
		$paths = array_map(function($p) use ($root){
			return '/' . $root . $p;
		}, $paths);

		$data = array("paths" => $paths);
		$data = array_merge($data, $options);

		$result = $this->post("links", $data);

		// Decode the json reply
		$result = json_decode($result);

		// Check for errors
		if (isset($result->error)) {
			throw new \Exception("Error creating link for paths " . implode(', ', $paths) . ": (" . $result->error . ") '" . $result->message . "'");
		}

		return $result;
	}
	/**
	 * Update meta object
	 *
	 * Object structure:
	 * {
	 *  object_id: "4008"
	 *  path: "/example"
	 *  type: "dir" || "file"
	 *  share_id: "0"
	 *  share_owner: "21956799"
	 *  company_id: NULL
	 *  size: filesize in bytes, 0 for folders
	 *  created_time: unix timestamp, e.g. "1389731126"
	 *  modified_time: unix timestamp, e.g. "1389731126"
	 *  date_last_synced: unix timestamp, e.g. "1389731126"
	 *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
	 *  mime_type: string
	 *  revisions: array of revision objects
	 * }
	 *
	 * @param string $action
	 * @param string $path
	 * @param array $meta contains action, path, and other attributes of the object to update
	 *
	 * @return stdClass using structure as noted above
	 */
	private function updateObject($action, $path, $meta)
	{
		// Add action and path to meta
		$meta["action"] = $action;
		$meta["path"] = $path;
		$result = $this->post("update_objects", $this->encodeRequest("update_objects", array("meta" => array($meta))), true);
		// Return the object
		return $result->{"result"}[0]->{"object"};
	}
	/**
	 * Create and execute cURL request to send data.
	 *
	 * @param  string  $method         API method
	 * @param  string  $data           raw request
	 * @param  boolean $decodeResponse true to decode response
	 *
	 * @return mixed  result from curl_exec
	 */
	private function post($method, $data, $decodeResponse = false)
	{
		if (is_array($data)) {
			$data = str_replace('\\/', '/', json_encode($data));
		}
		curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "POST");
		curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data);
		curl_setopt($this->curl, CURLOPT_HTTPHEADER, $this->getHeaders($method));
		curl_setopt($this->curl, CURLOPT_URL, $this->api_url . "/" . $this->getEndpoint($method));
		curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($this->curl, CURLOPT_POST, 1);
		$result = curl_exec($this->curl);
		// If curl grossly failed, throw
		if ($result === false) {
			throw new \Exception("Curl failed to exec " . curl_error($this->curl));
		}
		// Decode the response if requested to do so
		if ($decodeResponse) {
			return $this->decodeResponse($result);
		} else {
			return $result;
		}
	}
	/**
	 * Create and execute cURL request by GET method.
	 *
	 * @param  string $method API method
	 *
	 * @return mixed  result from curl_exec
	 */
	protected function get($method)
	{
		$method = str_replace("%2F", "/", rawurlencode($method));
		curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "GET");
		curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
		curl_setopt($this->curl, CURLOPT_HTTPHEADER, $this->getHeaders($method, "GET"));
		curl_setopt($this->curl, CURLOPT_URL, $this->api_url . "/" . $this->GetEndpoint($method));
		curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($this->curl, CURLOPT_HTTPGET, 1);
		$result = curl_exec($this->curl);
		// If curl grossly failed, throw
		if ($result == FALSE) {
			throw new \Exception("Curl failed to exec " . curl_error($this->curl));
		}
		return $result;
	}
	/**
	 * Return which cloud API end point to use for a given method.
	 *
	 * @param  string $method API method
	 *
	 * @return string uri of endpoint without leading slash
	 */
	private function getEndpoint($method)
	{
		if ($method == "has_object_parts_v2" || $method == "send_object_parts_v2" || $method == "get_object_parts_v2") {
			return "jsonrpc_binary";
		} else if ($method == "update_objects" || $method == "list_objects") {
			return "jsonrpc";
		} else {
			return "rest/" . $method;
		}
	}
	/**
	 * Generate the HTTP headers need for a given Cloud API method.
	 *
	 * @param  string $method      API method
	 * @param  string $http_method Optional, HTTP request method
	 *
	 * @return array  contains headers to use for HTTP requests
	 */
	public function getHeaders($method, $http_method = "POST")
	{
		$headers = array();
		$consumer = new \Eher\OAuth\Consumer($this->signature['consumer_key'], $this->signature['shared_secret']);
		$signatureMethod = new \Eher\OAuth\HmacSha1();
		$token = new \Eher\OAuth\Token($this->signature['oauth_token'], $this->signature['oauth_secret']);
		$request = \Eher\OAuth\Request::from_consumer_and_token(
				$consumer,
				$token,
				$http_method,
				$this->api_url . "/" . $this->GetEndpoint($method),
				array()
		);
		$request->sign_request($signatureMethod, $consumer, $token);
		if ($method == "has_object_parts_v2" || $method == "send_object_parts_v2" || $method == "get_object_parts_v2") {
			array_push($headers, "Content-Type: application/octet-stream");
		}
		array_push($headers, "X-Api-Version: 1.0");
		array_push($headers, "X-Client-Type: api");
		array_push($headers, "X-Client-Time: " . time());
		array_push($headers, $request->to_header());
		return $headers;
	}
	/**
	 * JSON encode request data.
	 *
	 * @param  string $method Cloud API method
	 * @param  array  $json   contains data to be encoded
	 *
	 * @return string JSON formatted request body
	 */
	private function encodeRequest($method, $json)
	{
		$request = array(
				'jsonrpc' => '2.0',
				'id' => '0',
				'method' => $method,
				'params' => $json,
		);
		return str_replace('\\/', '/', json_encode($request));
	}
	/**
	 * Decode a JSON response.
	 *
	 * @param string $response JSON response
	 *
	 * @return array JSON decoded string
	 */
	private function decodeResponse($response)
	{
		// Decode the json reply
		$result = json_decode($response);
		// Check for errors
		if (isset($result->error)) {
			throw new \Exception("Error: '" . $result->error->message . "'");
		}
		return $result;
	}
	/**
	 * Copies the phar cacert from a phar into the temp directory.
	 *
	 * @param  string $pharCacertPath Path to the phar cacert.
	 *
	 * @return string Returns the path to the extracted cacert file.
	 */
	public static function extractPharCacert($pharCacertPath)
	{
		$certFile = sys_get_temp_dir() . '/barracuda-copycom-cacert.crt';
		if (!file_exists($pharCacertPath)) {
			throw new \Exception("Could not find " . $pharCacertPath);
		}
		// Copy the cacert file from the phar if it is not in the temp folder.
		if (!file_exists($certFile) || filesize($certFile) != filesize($pharCacertPath)) {
			if (!copy($pharCacertPath, $certFile)) {
				throw new \Exception(
						"Could not copy " . $pharCacertPath . " to " . $certFile . ": "
						. var_export(error_get_last(), true)
				);
			}
		}
		return $certFile;
	}
}
