XylotrechusZ
<?php
defined("ABSPATH") or die("");
/**
* Dropbox v2 API with wordpress http api
* https://www.dropbox.com/developers/documentation/http/documentation
*
* http://www.upwork.com/fl/albertw6
*
*
* @author Albert Wang <[email protected]>
* @copyright Albert Wang 2017
* @version 1.0
* @license MIT
*
*/
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapUtil;
if (!class_exists('DUP_PRO_DropboxV2Client_UploadInfo')) {
class DUP_PRO_DropboxV2Client_UploadInfo
{
public $upload_id;
public $next_offset;
public $error_details = null;
public $file_meta = null; // Is non null if upload complete
}
}
if (!class_exists('DUP_PRO_DropboxV2Client')) {
class DUP_PRO_DropboxV2Client
{
const API_URL = "https://api.dropboxapi.com/2/";
const API_CONTENT_URL = "https://content.dropboxapi.com/2/";
const OAUTH2_URL = 'https://www.dropbox.com/oauth2/';
const BUFFER_SIZE = 4096;
const MAX_UPLOAD_CHUNK_SIZE = 150000000; // 150MB
const UPLOAD_CHUNK_SIZE = 4000000; // 4MB
const CONTENT_HASH_MISMATCH_TAG = "content_hash_mismatch";
const LOG_PREFIX = "DROPBOX ENDPOINT: ";
private $appParams;
private $consumerToken;
private $requestToken;
private $accessToken;
private $v2AccessToken;
private $locale;
private $rootPath;
private $useCurl;
function __construct($app_params, $locale = "en", $use_curl = true)
{
$this->appParams = $app_params;
if (empty($app_params['app_key'])) throw new DropboxException("App Key is empty!");
$this->consumerToken = array('t' => $this->appParams['app_key'], 's' => $this->appParams['app_secret']);
$this->locale = $locale;
$this->rootPath = empty($app_params['app_full_access']) ? "sandbox" : "dropbox";
$this->requestToken = null;
$this->accessToken = null;
if (isset($this->appParams['v2_access_token'])) {
$this->v2AccessToken = $this->appParams['v2_access_token'];
}
//$this->useCurl = function_exists('curl_init');
$this->useCurl = true; // we don't use fopen any more $use_curl;
if ($this->useCurl) {
self::trace("Using cURL for Dropbox transfers");
} else {
self::trace("Using FOpen URL for Dropbox transfers");
}
}
public function createAuthUrl()
{
return self::OAUTH2_URL.'authorize?client_id='.$this->appParams['app_key'].'&response_type=code';
}
/**
return access_token or false
*/
public function authenticate($auth_code)
{
/*
https://www.dropbox.com/developers/documentation/http/documentation#oa2-token
*/
$url = self::OAUTH2_URL.'token';
$args = array(
'body' => array(
'client_id' => $this->appParams['app_key'],
'client_secret' => $this->appParams['app_secret'],
'code' => $auth_code,
'grant_type' => 'authorization_code',
)
);
$args = $this->injectExtraReqArgs($args);
$args['timeout'] = 30;
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
$error_message = $response->get_error_message();
DUP_PRO_Log::traceObject("Something wrong with when try to get v2_access_token with code", $response);
return false;
} else {
// {"state":"success","msg":{"headers":{},"body":"{\"access_token\": \"Vv4HPoqZMtYAAAAAAAB1jFMBk8fQK7MPYOU4cGsr8jOO4vjHAM8487E_MFkKCniX
// \", \"token_type\": \"bearer\", \"uid\": \"170627281\", \"account_id\": \"dbid:AAA_0dSBhRpPefHEH3w4EzjV-3T5IUkTPnI
// \"}","response":{"code":200,"message":"OK"},"cookies":[],"filename":null,"http_response":{"data":null
// ,"headers":null,"status":null}},"data":""}
DUP_PRO_Log::traceObject("Got v2 access_token", $response);
$ret_obj = json_decode($response['body']);
if (isset($ret_obj->access_token)) {
return $ret_obj->access_token;
} else {
return false;
}
}
}
/**
* Sets a previously retrieved (and stored) access token.
*
* @param array<string,string> $token The Access Token
*
* @return void
*/
public function SetAccessToken($token)
{
if (empty($token['v2_access_token'])) {
throw new DropboxException('Passed invalid access token.');
}
if (isset($token['v2_access_token'])) {
$this->v2AccessToken = $token['v2_access_token'];
}
}
/**
* Checks if an access token has been set.
*
* @access public
* @return boolean Authorized or not
*/
public function IsAuthorized()
{
if (empty($this->v2AccessToken)) return false;
return true;
}
// ##################################################
// API Functions
/**
* Retrieves information about the user's account.
*
* @access public
* @return object Account info object. See https://www.dropbox.com/developers/reference/api#account-info
*/
public function GetAccountInfo()
{
/*
{"account_id": "dbid:AAA_0dSBhRpPefHEH3w4EzjV-3T5IUkTPnI", "name": {"given_name": "nice", "surname": "cool", "familiar_name": "nice", "display_name": "nice cool", "abbreviated_name": "nc"}, "email": "[email protected]", "email_verified": true, "disabled": false, "country": "HK", "locale": "en", "referral_link": "https://db.tt/dQzXutEytF", "is_paired": false, "account_type": {".tag": "basic"}}
*/
return $this->apiCall("users/get_current_account");
}
public function revokeToken()
{
/*
https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
*/
return $this->apiCall("auth/token/revoke");
}
/**
* Get file list of a dropbox folder.
*
* @access public
* @param string|object $dropbox_path Dropbox path of the folder
* @return array An array with metadata of files/folders keyed by paths
*/
public function GetFiles($dropbox_path = '', $recursive = false, $include_deleted = false)
{
/* :
https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
*/
$dropbox_path = $this->getFormatedPath($dropbox_path);
$data = $this->apiCall('files/list_folder', 'POST', array('path' => $dropbox_path));
$tag = '.tag';
$returns = array();
foreach ($data->entries as $key => $entry) {
if ('file' == $entry->$tag) {
$tmp_obj = new stdClass();
$tmp_obj->file_path = $entry->path_display;
$tmp_obj->modified = $entry->client_modified;
$returns[] = $tmp_obj;
}
}
return $returns;
}
/**
* Get file or folder metadata
*
* @access public
* @param $dropbox_path string Dropbox path of the file or folder
*/
public function GetMetadata($path, $include_deleted = false, $rev = null)
{
$path = $this->getFormatedPath($path);
return $this->apiCall("files/get_metadata", "POST", compact('path'));
}
public function DownloadFile($dropbox_file, $dest_path = '', $rev = null, $progress_changed_callback = null)
{
$dropbox_file = $this->getFormatedPath($dropbox_file);
$params['api_arg'] = array('path' => $dropbox_file);
$path = 'files/download';
$url = self::API_CONTENT_URL.$path;
$args = array(
'method' => 'POST',
'timeout' => 180,
'blocking' => true,
'stream' => true,
'filename' => $dest_path,
'headers' => array(
'Authorization' => 'Bearer '.$this->v2AccessToken,
'Content-Type' => '',
'Dropbox-API-Arg' => json_encode($params['api_arg'])
)
);
$args = $this->injectExtraReqArgs($args);
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) {
$error_message = $response->get_error_message();
DUP_PRO_Log::traceObject("Something wrong with apiCall on DownloadFile", $response);
return false;
} else {
/*
dropbox-api-result: {"name": "test4.php", "path_lower": "/sandbox.cms90.com/test4/test4.php", "path_display": "/sandbox.cms90.com/test4/test4.php", "id": "id:Hln_x8l6_aAAAAAAAAAAEA", "client_modified": "2017-03-06T08:01:02Z", "server_modified": "2017-03-06T08:01:02Z", "rev": "1154ca0b05", "size": 1310, "content_hash": "c5e876cefb8b7176af487678cb4b8f64b80320ffc1a39889cd531b7cba656fd2"}
*/
return json_decode($response['headers']['dropbox-api-result']);
}
}
/**
* @param string $src_file path to src file
* @param string $dropbox_path path in remote location
* @param int $upload_chunk_size chunk size
* @param int $max_upload_time_in_sec time in seconds
* @param int $offset current offset
* @param string $upload_id upload if of session
* @param int $server_load_delay server load delay in milliseconds
* @return DUP_PRO_DropboxV2Client_UploadInfo
* @throws Exception
*/
public function upload_file_chunk($src_file, $dropbox_path = '', $upload_chunk_size = self::UPLOAD_CHUNK_SIZE, $max_upload_time_in_sec = 15, $offset = 0, $upload_id = null,
$server_load_delay = 0)
{
self::trace("start");
try {
$dropbox_path = $this->getFormatedPath($dropbox_path);
$dropbox_client_upload_info = new DUP_PRO_DropboxV2Client_UploadInfo();
$dropbox_client_upload_info->next_offset = $offset;
$dropbox_client_upload_info->upload_id = $upload_id;
self::trace("offset coming in=$offset chunk size=$upload_chunk_size dropbox path=$dropbox_path");
$file_size = filesize($src_file);
if (($fh = fopen($src_file, 'rb')) === false) {
throw new Exception("problem opening $src_file");
}
SnapIO::fseek($fh, $offset);
$start_time = time();
$time_passed = 0;
self::trace("upload_id=$upload_id filesize=$file_size max_upload_time=$max_upload_time_in_sec");
while (feof($fh) == false && (time() - $start_time) < $max_upload_time_in_sec) {
if($server_load_delay > 0) {
usleep($server_load_delay);
}
if(($content = fread($fh, $upload_chunk_size)) === false) {
throw new Exception('Error reading archive for Dropbox transmission');
}
if (empty($upload_id)) {
$upload_id = $this->sendFirstChunk($content);
$offset += strlen($content);
continue;
}
self::trace("append v2 with offset {$offset}");
$upload_return = $this->apiCall(
'files/upload_session/append_v2',
'POST',
array(
'api_arg' => array(
'cursor' => array(
'session_id' => $upload_id,
'offset' => $offset
),
'close' => false,
'content_hash' => $this->getContentHash($content)
),
'content' => $content
),
true
);
DUP_PRO_Log::trace("UPLOAD RETURN " . print_r($upload_return, true));
if (
is_object($upload_return) && property_exists($upload_return, "error") &&
property_exists($upload_return->error, "correct_offset")
) {
$wrongOffset = $offset;
$offset = intval($upload_return->error->correct_offset, 10);
DUP_PRO_Log::infoTrace("Detected wrong offset in upload to Dropbox: $wrongOffset. "
. "Replaced it with correct offset: $offset");
} else if (
$upload_return === false ||
(is_object($upload_return) && property_exists($upload_return, "error_summary"))
) {
$error_summary = "";
if (is_object($upload_return) && property_exists($upload_return, "error_summary")) {
$error_summary = $upload_return->error_summary;
}
throw new Exception("problem making call to upload_session/append_v2 for offset {$offset} - " . $error_summary);
} else {
$offset += strlen($content);
}
}
$time_passed = time() - $start_time;
self::trace("Time passed=$time_passed");
if (@feof($fh)) {
self::trace("end of file");
$dropbox_client_upload_info->file_meta = $this->finishChunkedUpload($upload_id, $offset, $dropbox_path);
}
SnapIO::fclose($fh);
} catch (Exception $ex) {
$dropbox_client_upload_info->error_details = $ex->getMessage();
}
$dropbox_client_upload_info->upload_id = $upload_id;
$dropbox_client_upload_info->next_offset = $offset;
return $dropbox_client_upload_info;
}
/**
* @param string $content binary content to be sent
* @throws Exception
*/
private function sendFirstChunk($content)
{
$upload_return = $this->apiCall(
'files/upload_session/start',
'POST',
array(
'api_arg' => array(
'close' => false,
'content_hash' => $this->getContentHash($content)
),
'content' => $content
),
true
);
if ($upload_return === false || (is_object($upload_return) && property_exists($upload_return, "error_summary"))) {
$error_summary = "";
if (is_object($upload_return) && property_exists($upload_return, "error_summary")) {
$error_summary = $upload_return->error_summary;
}
throw new \Exception("problem making call to upload_session/start - " . $error_summary);
}
return $upload_return->session_id;
}
/**
* @param string $upload_id the upload id
* @param int $offset offset
* @param string $dropbox_path dropbox path
* @return mixed
* @throws Exception
*/
private function finishChunkedUpload($upload_id, $offset, $dropbox_path)
{
$return = $this->apiCall(
'files/upload_session/finish',
'POST',
array(
'api_arg' => array(
'cursor' => array(
'session_id' => $upload_id,
'offset' => $offset
),
"commit" => array(
"path" => $dropbox_path,
"mode" => "add",
"autorename" => true,
"mute" => false
)
),
'content' => null
),
true
);
if (null == $return) {
usleep(500);
throw new Exception("**** Upload finish Dropbox API call gave null value! Something going wrong.");
}
return $return;
}
/**
* Upload a file to dropbox
*
* @access public
* @param $src_file string Local file to upload
* @param $dropbox_path string Dropbox path for destination
* @return object Dropbox file metadata
*/
public function UploadFile($src_file, $dropbox_path, $overwrite = true, $parent_rev = null)
{
// Delete any file that may be there ahead of time
try {
self::trace("Deleting Dropbox files $dropbox_path");
$this->Delete($dropbox_path);
} catch (Exception $ex) {
// Bury any exceptions
}
$dropbox_path = $this->getFormatedPath($dropbox_path);
$file_size = filesize($src_file);
if ($file_size > self::MAX_UPLOAD_CHUNK_SIZE) {
//chunk upload
}
/* upload a single file */
$content = file_get_contents($src_file);
if (strlen($content) == 0) throw new DropboxException("Could not read file $src_file or file is empty!");
$params['api_arg'] = array('path' => $dropbox_path);
// $params['content_size']=$file_size;
$params['content'] = $content;
return $this->apiCall('files/upload', 'POST', $params, true);
}
public function checkFileHash($file_metadata, $file)
{
$dropbox_hash = $file_metadata->content_hash;
$local_hash = $this->getFileHash($file);
self::trace("$local_hash <===> $dropbox_hash");
return $local_hash == $dropbox_hash;
}
public function getFileHash($file)
{
$result = '';
$sum_string = '';
$chunksize = 4 * 1024 * 1024;
$handle = fopen($file,"r");
while (!feof($handle)) {
$file_chunk = fread($handle,$chunksize);
$sum_string .= hash("sha256",$file_chunk,true);
}
$result = hash("sha256", $sum_string);
return $result;
}
public function getContentHash($content)
{
$len = strlen($content);
$chunkSize = 4 * 1024 * 1024;
$sumString = '';
for ($i=0; $i < $len/$chunkSize; $i++) {
$chunk = substr($content, $i*$chunkSize, $chunkSize);
$sumString .= hash("sha256", $chunk, true);
}
return hash("sha256", $sumString);
}
/**
* Creates a new folder in the DropBox
*
* @access public
* @param $path string The path to the new folder to create
* @return object Dropbox folder metadata
*/
function CreateFolder($path)
{
$path = $this->getFormatedPath($path);
return $this->apiCall("files/create_folder", "POST", array('path' => $path, 'autorename' => false));
}
/**
* Delete file or folder
*
* @access public
* @param $path mixed The path or metadata of the file/folder to be deleted.
* @return object Dropbox metadata of deleted file or folder
*/
function Delete($path)
{
$path = $this->getFormatedPath($path);
return $this->apiCall("files/delete", "POST", array('path' => $path));
}
function getFormatedPath($path)
{
$path = trim($path, '/');
$path = str_replace('//', '/', $path);
$path = '/'.$path;
return $path;
}
public function getQuota() {
return $this->apiCall('users/get_space_usage');
}
private function apiCall($path, $method = "POST", $params = array(), $content_call = false)
{
if ($content_call) {
$url = self::API_CONTENT_URL.$path;
$args = array(
'timeout' => 180,
'blocking' => true,
'method' => $method,
'headers' => array(
'Authorization' => 'Bearer '.$this->v2AccessToken,
'Content-Type' => 'application/octet-stream',
'Dropbox-API-Arg' => json_encode($params['api_arg'])
// 'Content-Length' => $params['content_size']
),
'body' => $params['content']
);
} else {
$url = self::API_URL.$path;
$body = 'null';
if (!empty($params)) {
$body = json_encode($params);
}
$args = array(
'timeout' => 10,
'blocking' => true,
'method' => $method,
'headers' => array(
'Authorization' => 'Bearer '.$this->v2AccessToken,
'Content-Type' => 'application/json',
),
'body' => $body
);
}
$args = $this->injectExtraReqArgs($args);
$response = wp_remote_request($url, $args);
$params['content'] = '';
if (is_wp_error($response)) {
$error_message = $response->get_error_message();
DUP_PRO_Log::traceObject("Something wrong with apiCall", $response);
DUP_PRO_Log::traceObject("Params", $params);
return false;
} else {
if (isset($response['body'])) {
$ret_obj = json_decode($response['body']);
return $ret_obj;
} else {
return false;
}
}
}
private function injectExtraReqArgs($opts) {
$global = DUP_PRO_Global_Entity::getInstance();
$opts['sslverify'] = $global->ssl_disableverify ? false : true;
if (!$global->ssl_useservercerts) {
$opts['sslcertificates'] = DUPLICATOR_PRO_CERT_PATH;
}
return $opts;
}
/**
* Make trace log with prefix
*
* @param string $str log message
*/
private static function trace($str)
{
DUP_PRO_Log::trace(self::LOG_PREFIX . $str);
}
}
if (!class_exists('DropboxException')) {
class DropboxException extends Exception
{
public function __construct($err = null, $isDebug = FALSE)
{
if (is_null($err)) {
$el = error_get_last();
$this->message = $el['message'];
$this->file = $el['file'];
$this->line = $el['line'];
} else $this->message = $err;
self::log_error($err);
if ($isDebug) {
self::display_error($err, TRUE);
}
}
public static function log_error($err)
{
error_log($err, 0);
}
public static function display_error($err, $kill = FALSE)
{
print_r($err);
if ($kill === FALSE) {
die();
}
}
}
}
}