Add upstream
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Abstract OAuth consumer
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Consumer
|
||||
*/
|
||||
|
||||
abstract class Dropbox_ConsumerAbstract
|
||||
{
|
||||
// Dropbox web endpoint. v2 API has just dropped the 1/ suffix to the below.
|
||||
const WEB_URL = 'https://www.dropbox.com/';
|
||||
|
||||
// OAuth flow methods
|
||||
const AUTHORISE_METHOD = 'oauth2/authorize';
|
||||
// Beware - the documentation in one place says oauth2/token/revoke, but that appears to be wrong
|
||||
const DEAUTHORISE_METHOD = '2/auth/token/revoke';
|
||||
const ACCESS_TOKEN_METHOD = 'oauth2/token';
|
||||
// The next endpoint only exists with APIv1
|
||||
const OAUTH_UPGRADE = 'oauth2/token_from_oauth1';
|
||||
|
||||
/**
|
||||
* Signature method, either PLAINTEXT or HMAC-SHA1
|
||||
* @var string
|
||||
*/
|
||||
private $sigMethod = 'PLAINTEXT';
|
||||
|
||||
/**
|
||||
* Output file handle
|
||||
* @var null|resource
|
||||
*/
|
||||
protected $outFile = null;
|
||||
|
||||
/**
|
||||
* Input file handle
|
||||
* @var null|resource
|
||||
*/
|
||||
protected $inFile = null;
|
||||
|
||||
/**
|
||||
* Authenticate using 3-legged OAuth flow, firstly
|
||||
* checking we don't already have tokens to use
|
||||
* @return void
|
||||
*/
|
||||
protected function authenticate()
|
||||
{
|
||||
global $updraftplus;
|
||||
|
||||
$access_token = $this->storage->get('access_token');
|
||||
//Check if the new token type is set if not they need to be upgraded to OAuth2
|
||||
if (!empty($access_token) && isset($access_token->oauth_token) && !isset($access_token->token_type)) {
|
||||
$updraftplus->log('OAuth v1 token found: upgrading to v2');
|
||||
$this->upgradeOAuth();
|
||||
$updraftplus->log('OAuth token upgrade successful');
|
||||
}
|
||||
|
||||
if (empty($access_token) || !isset($access_token->oauth_token)) {
|
||||
try {
|
||||
$this->getAccessToken();
|
||||
} catch(Exception $e) {
|
||||
$excep_class = get_class($e);
|
||||
// 04-Sep-2015 - Dropbox started throwing a 400, which caused a Dropbox_BadRequestException which previously wasn't being caught
|
||||
if ('Dropbox_BadRequestException' == $excep_class || 'Dropbox_Exception' == $excep_class) {
|
||||
global $updraftplus;
|
||||
$updraftplus->log($e->getMessage().' - need to reauthenticate this site with Dropbox (if this fails, then you can also try wiping your settings from the Expert Settings section)');
|
||||
//$this->getRequestToken();
|
||||
$this->authorise();
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade the user's OAuth1 token to a OAuth2 token
|
||||
* @return void
|
||||
*/
|
||||
private function upgradeOAuth()
|
||||
{
|
||||
// N.B. This call only exists under API v1 - i.e. there is no APIv2 equivalent. Hence the APIv1 endpoint (API_URL) is used, and not the v2 (API_URL_V2)
|
||||
$url = 'https://api.dropbox.com/1/' . self::OAUTH_UPGRADE;
|
||||
$response = $this->fetch('POST', $url, '');
|
||||
$token = new stdClass();
|
||||
/*
|
||||
oauth token secret and oauth token were needed by oauth1
|
||||
these are replaced in oauth2 with an access token
|
||||
currently they are still there just in case a method somewhere is expecting them to both be set
|
||||
as far as I can tell only the oauth token is used
|
||||
after more testing token secret can be removed.
|
||||
*/
|
||||
|
||||
$token->oauth_token_secret = $response['body']->access_token;
|
||||
$token->oauth_token = $response['body']->access_token;
|
||||
$token->token_type = $response['body']->token_type;
|
||||
$this->storage->set($token, 'access_token');
|
||||
$this->storage->set('true','upgraded');
|
||||
$this->storage->do_unset('request_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain user authorisation
|
||||
* The user will be redirected to Dropbox' web endpoint
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-2.2
|
||||
* @return void
|
||||
*/
|
||||
private function authorise()
|
||||
{
|
||||
// Only redirect if not using CLI
|
||||
if (PHP_SAPI !== 'cli' && (!defined('DOING_CRON') || !DOING_CRON) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
|
||||
$url = $this->getAuthoriseUrl();
|
||||
if (!headers_sent()) {
|
||||
header('Location: ' . $url);
|
||||
exit;
|
||||
} else {
|
||||
throw new Dropbox_Exception(sprintf(__('The %s authentication could not go ahead, because something else on your site is breaking it. Try disabling your other plugins and switching to a default theme. (Specifically, you are looking for the component that sends output (most likely PHP warnings/errors) before the page begins. Turning off any debugging settings may also help).', 'updraftplus'), 'Dropbox'));
|
||||
}
|
||||
?><?php
|
||||
return false;
|
||||
}
|
||||
global $updraftplus;
|
||||
$updraftplus->log('Dropbox reauthorisation needed; but we are running from cron, AJAX or the CLI, so this is not possible');
|
||||
$this->storage->do_unset('access_token');
|
||||
throw new Dropbox_Exception(sprintf(__('You need to re-authenticate with %s, as your existing credentials are not working.', 'updraftplus'), 'Dropbox'));
|
||||
#$updraftplus->log(sprintf(__('You need to re-authenticate with %s, as your existing credentials are not working.', 'updraftplus'), 'Dropbox'), 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the user authorisation URL
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthoriseUrl()
|
||||
{
|
||||
/*
|
||||
Generate a random key to be passed to Dropbox and stored in session to be checked to prevent CSRF
|
||||
Uses OpenSSL or Mcrypt or defaults to pure PHP implementaion if neither are available.
|
||||
*/
|
||||
|
||||
global $updraftplus;
|
||||
if (!function_exists('crypt_random_string')) $updraftplus->ensure_phpseclib('Crypt_Random');
|
||||
|
||||
$CSRF = base64_encode(crypt_random_string(16));
|
||||
$this->storage->set($CSRF,'CSRF');
|
||||
// Prepare request parameters
|
||||
/*
|
||||
For OAuth v2 Dropbox needs to use a authorisation url that matches one that is set inside the
|
||||
Dropbox developer console. In order to check this it needs the client ID for the OAuth v2 app
|
||||
This will use the default one unless the user is using their own Dropbox App
|
||||
|
||||
For users that use their own Dropbox App there is also no need to provide the callbackhome as
|
||||
part of the CSRF as there is no need to go to auth.updraftplus.com also the redirect uri can
|
||||
then be set to the home as default
|
||||
|
||||
Check if the key has dropbox: if so then remove it to stop the request from being invalid
|
||||
*/
|
||||
$appkey = $this->storage->get('appkey');
|
||||
|
||||
if (!empty($appkey) && 'dropbox:' == substr($appkey, 0, 8)) {
|
||||
$key = substr($appkey, 8);
|
||||
} else if (!empty($appkey)) {
|
||||
$key = $appkey;
|
||||
}
|
||||
|
||||
if ('' != $this->instance_id) $this->instance_id = ':'.$this->instance_id;
|
||||
|
||||
$params = array(
|
||||
'client_id' => empty($key) ? $this->oauth2_id : $key,
|
||||
'response_type' => 'code',
|
||||
'redirect_uri' => empty($key) ? $this->callback : $this->callbackhome,
|
||||
'state' => empty($key) ? "POST:".$CSRF.$this->instance_id.$this->callbackhome : $CSRF.$this->instance_id,
|
||||
);
|
||||
|
||||
// Build the URL and redirect the user
|
||||
$query = '?' . http_build_query($params, '', '&');
|
||||
$url = self::WEB_URL . self::AUTHORISE_METHOD . $query;
|
||||
return $url;
|
||||
}
|
||||
|
||||
protected function deauthenticate()
|
||||
{
|
||||
$url = UpdraftPlus_Dropbox_API::API_URL_V2 . self::DEAUTHORISE_METHOD;
|
||||
$response = $this->fetch('POST', $url, '', array('api_v2' => true));
|
||||
$this->storage->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire an access token
|
||||
* Tokens acquired at this point should be stored to
|
||||
* prevent having to request new tokens for each API call
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-2.3
|
||||
*/
|
||||
public function getAccessToken()
|
||||
{
|
||||
|
||||
// If this is non-empty, then we just received a code. It is stored in 'code' - our next job is to put it into the proper place.
|
||||
$code = $this->storage->get('code');
|
||||
/*
|
||||
Checks to see if the user is using their own Dropbox App if so then they need to get
|
||||
a request token. If they are using our App then we just need to save these details
|
||||
*/
|
||||
if (!empty($code)){
|
||||
$appkey = $this->storage->get('appkey');
|
||||
if (!empty($appkey)){
|
||||
// Get the signed request URL
|
||||
$url = UpdraftPlus_Dropbox_API::API_URL_V2 . self::ACCESS_TOKEN_METHOD;
|
||||
$params = array(
|
||||
'code' => $code,
|
||||
'grant_type' => 'authorization_code',
|
||||
'redirect_uri' => $this->callbackhome,
|
||||
'client_id' => $this->consumerKey,
|
||||
'client_secret' => $this->consumerSecret,
|
||||
);
|
||||
$response = $this->fetch('POST', $url, '' , $params);
|
||||
|
||||
$code = json_decode(json_encode($response['body']),true);
|
||||
|
||||
} else {
|
||||
$code = base64_decode($code);
|
||||
$code = json_decode($code, true);
|
||||
}
|
||||
|
||||
/*
|
||||
Again oauth token secret and oauth token were needed by oauth1
|
||||
these are replaced in oauth2 with an access token
|
||||
currently they are still there just in case a method somewhere is expecting them to both be set
|
||||
as far as I can tell only the oauth token is used
|
||||
after more testing token secret can be removed.
|
||||
*/
|
||||
|
||||
$token = new stdClass();
|
||||
$token->oauth_token_secret = $code['access_token'];
|
||||
$token->oauth_token = $code['access_token'];
|
||||
$token->account_id = $code['account_id'];
|
||||
$token->token_type = $code['token_type'];
|
||||
$token->uid = $code['uid'];
|
||||
$this->storage->set($token, 'access_token');
|
||||
$this->storage->do_unset('upgraded');
|
||||
|
||||
//reset code
|
||||
$this->storage->do_unset('code');
|
||||
} else {
|
||||
throw new Dropbox_BadRequestException("No Dropbox Code found, will try to get one now", 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request/access token
|
||||
* This will return the access/request token depending on
|
||||
* which stage we are at in the OAuth flow, or a dummy object
|
||||
* if we have not yet started the authentication process
|
||||
* @return object stdClass
|
||||
*/
|
||||
private function getToken()
|
||||
{
|
||||
if (!$token = $this->storage->get('access_token')) {
|
||||
if (!$token = $this->storage->get('request_token')) {
|
||||
$token = new stdClass();
|
||||
$token->oauth_token = null;
|
||||
$token->oauth_token_secret = null;
|
||||
}
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate signed request URL
|
||||
* See inline comments for description
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-3.4
|
||||
* @param string $method HTTP request method
|
||||
* @param string $url API endpoint to send the request to
|
||||
* @param string $call API call to send
|
||||
* @param array $additional Additional parameters as an associative array
|
||||
* @return array
|
||||
*/
|
||||
protected function getSignedRequest($method, $url, $call, array $additional = array())
|
||||
{
|
||||
// Get the request/access token
|
||||
$token = $this->getToken();
|
||||
|
||||
// Prepare the standard request parameters differently for OAuth1 and OAuth2; we still need OAuth1 to make the request to the upgrade token endpoint
|
||||
if (isset($token->token_type)) {
|
||||
$params = array(
|
||||
'access_token' => $token->oauth_token,
|
||||
);
|
||||
|
||||
/*
|
||||
To keep this API backwards compatible with the API v1 endpoints all v2 endpoints will also send to this method a api_v2 parameter this will then return just the access token as the signed request is not needed for any calls.
|
||||
*/
|
||||
|
||||
if (isset($additional['api_v2']) && $additional['api_v2'] == true) {
|
||||
unset($additional['api_v2']);
|
||||
if (isset($additional['timeout'])) unset($additional['timeout']);
|
||||
if (isset($additional['content_download']) && $additional['content_download'] == true) {
|
||||
unset($additional['content_download']);
|
||||
$extra_headers = array();
|
||||
if (isset($additional['headers'])) {
|
||||
foreach ($additional['headers'] as $key => $header) {
|
||||
$extra_headers[] = $header;
|
||||
}
|
||||
unset($additional['headers']);
|
||||
}
|
||||
$headers = array(
|
||||
'Authorization: Bearer '.$params['access_token'],
|
||||
'Content-Type:',
|
||||
'Dropbox-API-Arg: '.json_encode($additional),
|
||||
);
|
||||
|
||||
$headers = array_merge($headers, $extra_headers);
|
||||
$additional = '';
|
||||
} else if (isset($additional['content_upload']) && $additional['content_upload'] == true) {
|
||||
unset($additional['content_upload']);
|
||||
$headers = array(
|
||||
'Authorization: Bearer '.$params['access_token'],
|
||||
'Content-Type: application/octet-stream',
|
||||
'Dropbox-API-Arg: '.json_encode($additional),
|
||||
);
|
||||
$additional = '';
|
||||
} else {
|
||||
$headers = array(
|
||||
'Authorization: Bearer '.$params['access_token'],
|
||||
'Content-Type: application/json',
|
||||
);
|
||||
}
|
||||
return array(
|
||||
'url' => $url . $call,
|
||||
'postfields' => $additional,
|
||||
'headers' => $headers,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Generate a random string for the request
|
||||
$nonce = md5(microtime(true) . uniqid('', true));
|
||||
$params = array(
|
||||
'oauth_consumer_key' => $this->consumerKey,
|
||||
'oauth_token' => $token->oauth_token,
|
||||
'oauth_signature_method' => $this->sigMethod,
|
||||
'oauth_version' => '1.0',
|
||||
// Generate nonce and timestamp if signature method is HMAC-SHA1
|
||||
'oauth_timestamp' => ($this->sigMethod == 'HMAC-SHA1') ? time() : null,
|
||||
'oauth_nonce' => ($this->sigMethod == 'HMAC-SHA1') ? $nonce : null,
|
||||
);
|
||||
}
|
||||
|
||||
// Merge with the additional request parameters
|
||||
$params = array_merge($params, $additional);
|
||||
ksort($params);
|
||||
|
||||
// URL encode each parameter to RFC3986 for use in the base string
|
||||
$encoded = array();
|
||||
foreach($params as $param => $value) {
|
||||
if ($value !== null) {
|
||||
// If the value is a file upload (prefixed with @), replace it with
|
||||
// the destination filename, the file path will be sent in POSTFIELDS
|
||||
if (isset($value[0]) && $value[0] === '@') $value = $params['filename'];
|
||||
# Prevent spurious PHP warning by only doing non-arrays
|
||||
if (!is_array($value)) $encoded[] = $this->encode($param) . '=' . $this->encode($value);
|
||||
} else {
|
||||
unset($params[$param]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the first part of the string
|
||||
$base = $method . '&' . $this->encode($url . $call) . '&';
|
||||
|
||||
// Re-encode the encoded parameter string and append to $base
|
||||
$base .= $this->encode(implode('&', $encoded));
|
||||
|
||||
// Concatenate the secrets with an ampersand
|
||||
$key = $this->consumerSecret . '&' . $token->oauth_token_secret;
|
||||
|
||||
// Get the signature string based on signature method
|
||||
$signature = $this->getSignature($base, $key);
|
||||
$params['oauth_signature'] = $signature;
|
||||
|
||||
// Build the signed request URL
|
||||
$query = '?' . http_build_query($params, '', '&');
|
||||
|
||||
return array(
|
||||
'url' => $url . $call . $query,
|
||||
'postfields' => $params,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the oauth_signature for a request
|
||||
* @param string $base Signature base string, used by HMAC-SHA1
|
||||
* @param string $key Concatenated consumer and token secrets
|
||||
*/
|
||||
private function getSignature($base, $key)
|
||||
{
|
||||
switch ($this->sigMethod) {
|
||||
case 'PLAINTEXT':
|
||||
$signature = $key;
|
||||
break;
|
||||
case 'HMAC-SHA1':
|
||||
$signature = base64_encode(hash_hmac('sha1', $base, $key, true));
|
||||
break;
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OAuth signature method
|
||||
* @param string $method Either PLAINTEXT or HMAC-SHA1
|
||||
* @return void
|
||||
*/
|
||||
public function setSignatureMethod($method)
|
||||
{
|
||||
$method = strtoupper($method);
|
||||
|
||||
switch ($method) {
|
||||
case 'PLAINTEXT':
|
||||
case 'HMAC-SHA1':
|
||||
$this->sigMethod = $method;
|
||||
break;
|
||||
default:
|
||||
throw new Dropbox_Exception('Unsupported signature method ' . $method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output file
|
||||
* @param resource Resource to stream response data to
|
||||
* @return void
|
||||
*/
|
||||
public function setOutFile($handle)
|
||||
{
|
||||
if (!is_resource($handle) || get_resource_type($handle) != 'stream') {
|
||||
throw new Dropbox_Exception('Outfile must be a stream resource');
|
||||
}
|
||||
$this->outFile = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the input file
|
||||
* @param resource Resource to read data from
|
||||
* @return void
|
||||
*/
|
||||
public function setInFile($handle) {
|
||||
$this->inFile = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse response parameters for a token into an object
|
||||
* Dropbox returns tokens in the response parameters, and
|
||||
* not a JSON encoded object as per other API requests
|
||||
* @link http://oauth.net/core/1.0/#response_parameters
|
||||
* @param string $response
|
||||
* @return object stdClass
|
||||
*/
|
||||
private function parseTokenString($response)
|
||||
{
|
||||
$parts = explode('&', $response);
|
||||
$token = new stdClass();
|
||||
foreach ($parts as $part) {
|
||||
list($k, $v) = explode('=', $part, 2);
|
||||
$k = strtolower($k);
|
||||
$token->$k = $v;
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a value to RFC3986
|
||||
* This is a convenience method to decode ~ symbols encoded
|
||||
* by rawurldecode. This will encode all characters except
|
||||
* the unreserved set, ALPHA, DIGIT, '-', '.', '_', '~'
|
||||
* @link http://tools.ietf.org/html/rfc5849#section-3.6
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function encode($value)
|
||||
{
|
||||
return str_replace('%7E', '~', rawurlencode($value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OAuth consumer using PHP cURL
|
||||
* @author Ben Tadiar <ben@handcraftedbyben.co.uk>
|
||||
* @link https://github.com/benthedesigner/dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Consumer
|
||||
*/
|
||||
|
||||
class Dropbox_Curl extends Dropbox_ConsumerAbstract
|
||||
{
|
||||
/**
|
||||
* Default cURL options
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = array(
|
||||
CURLOPT_VERBOSE => true,
|
||||
CURLOPT_HEADER => true,
|
||||
CURLINFO_HEADER_OUT => false,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Store the last response form the API
|
||||
* @var mixed
|
||||
*/
|
||||
protected $lastResponse = null;
|
||||
|
||||
/**
|
||||
* Set properties and begin authentication
|
||||
* @param string $key
|
||||
* @param string $secret
|
||||
* @param \Dropbox\OAuth\Consumer\StorageInterface $storage
|
||||
* @param string $callback
|
||||
*/
|
||||
public function __construct($key, $oauth2_id, $secret, Dropbox_StorageInterface $storage, $callback = null, $callbackhome = null, $deauthenticate = false, $instance_id = '')
|
||||
{
|
||||
// Check the cURL extension is loaded
|
||||
if (!extension_loaded('curl')) {
|
||||
throw new Dropbox_Exception('The cURL OAuth consumer requires the cURL extension. Please speak to your web hosting provider so that this missing PHP component can be installed.');
|
||||
}
|
||||
|
||||
$this->consumerKey = $key;
|
||||
$this->oauth2_id = $oauth2_id;
|
||||
$this->consumerSecret = $secret;
|
||||
$this->storage = $storage;
|
||||
$this->callback = $callback;
|
||||
$this->callbackhome = $callbackhome;
|
||||
$this->instance_id = $instance_id;
|
||||
|
||||
if ($deauthenticate) {
|
||||
$this->deauthenticate();
|
||||
} else {
|
||||
$this->authenticate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API call
|
||||
* @todo Improve error handling
|
||||
* @param string $method The HTTP method
|
||||
* @param string $url The API endpoint
|
||||
* @param string $call The API method to call
|
||||
* @param array $additional Additional parameters
|
||||
* @return string|object stdClass
|
||||
*/
|
||||
public function fetch($method, $url, $call, array $additional = array())
|
||||
{
|
||||
// Get the signed request URL
|
||||
$request = $this->getSignedRequest($method, $url, $call, $additional);
|
||||
|
||||
// Initialise and execute a cURL request
|
||||
$handle = curl_init($request['url']);
|
||||
|
||||
// Get the default options array
|
||||
$options = $this->defaultOptions;
|
||||
if (!UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts')) {
|
||||
$options[CURLOPT_CAINFO] = UPDRAFTPLUS_DIR.'/includes/cacert.pem';
|
||||
}
|
||||
if (UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify')) {
|
||||
$options[CURLOPT_SSL_VERIFYPEER] = false;
|
||||
} else {
|
||||
$options[CURLOPT_SSL_VERIFYPEER] = true;
|
||||
}
|
||||
|
||||
if (!class_exists('WP_HTTP_Proxy')) require_once(ABSPATH.WPINC.'/class-http.php');
|
||||
$proxy = new WP_HTTP_Proxy();
|
||||
|
||||
if ($proxy->is_enabled()) {
|
||||
# WP_HTTP_Proxy returns empty strings if nothing is set
|
||||
$user = $proxy->username();
|
||||
$pass = $proxy->password();
|
||||
$host = $proxy->host();
|
||||
$port = (int)$proxy->port();
|
||||
if (empty($port)) $port = 8080;
|
||||
if (!empty($host) && $proxy->send_through_proxy($request['url'])) {
|
||||
$options[CURLOPT_PROXY] = $host;
|
||||
$options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
|
||||
$options[CURLOPT_PROXYPORT] = $port;
|
||||
if (!empty($user) && !empty($pass)) {
|
||||
$options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY;
|
||||
$options[CURLOPT_PROXYUSERPWD] = sprintf('%s:%s', $user, $pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Add check to see if it's an API v2 call if so then json encode the contents. This is so that it is backwards compatible with API v1 endpoints.
|
||||
*/
|
||||
if (isset($additional['api_v2']) && !empty($request['postfields'])) {
|
||||
$request['postfields'] = json_encode($request['postfields']);
|
||||
} elseif (empty($request['postfields'])) {
|
||||
// if the postfields are empty then we don't want to send the application/json header if it's set as Dropbox will return an error
|
||||
$key = array_search('Content-Type: application/json', $request['headers']);
|
||||
if (false !== $key) unset($request['headers'][$key]);
|
||||
}
|
||||
|
||||
if (isset($request['headers']) && !empty($request['headers'])) $options[CURLOPT_HTTPHEADER] = $request['headers'];
|
||||
|
||||
if ($method == 'GET' && $this->outFile) { // GET
|
||||
$options[CURLOPT_RETURNTRANSFER] = false;
|
||||
$options[CURLOPT_HEADER] = false;
|
||||
$options[CURLOPT_FILE] = $this->outFile;
|
||||
$options[CURLOPT_BINARYTRANSFER] = true;
|
||||
$options[CURLOPT_FAILONERROR] = true;
|
||||
$this->outFile = null;
|
||||
} elseif ($method == 'POST' && $this->outFile) { // POST
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_RETURNTRANSFER] = false;
|
||||
$options[CURLOPT_HEADER] = false;
|
||||
$options[CURLOPT_FILE] = $this->outFile;
|
||||
$options[CURLOPT_BINARYTRANSFER] = true;
|
||||
$options[CURLOPT_FAILONERROR] = true;
|
||||
$this->outFile = null;
|
||||
} elseif ($method == 'POST' && $this->inFile) { // POST
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POSTFIELDS] = $this->inFile;
|
||||
} elseif ($method == 'POST') { // POST
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POSTFIELDS] = $request['postfields'];
|
||||
} elseif ($method == 'PUT' && $this->inFile) { // PUT
|
||||
$options[CURLOPT_PUT] = true;
|
||||
$options[CURLOPT_INFILE] = $this->inFile;
|
||||
// @todo Update so the data is not loaded into memory to get its size
|
||||
$options[CURLOPT_INFILESIZE] = strlen(stream_get_contents($this->inFile));
|
||||
fseek($this->inFile, 0);
|
||||
$this->inFile = null;
|
||||
}
|
||||
|
||||
if (isset($additional['timeout'])) {
|
||||
$options[CURLOPT_TIMEOUT] = $additional['timeout'];
|
||||
}
|
||||
|
||||
// Set the cURL options at once
|
||||
curl_setopt_array($handle, $options);
|
||||
// Execute, get any error and close
|
||||
$response = curl_exec($handle);
|
||||
$error = curl_error($handle);
|
||||
$getinfo = curl_getinfo($handle);
|
||||
|
||||
curl_close($handle);
|
||||
|
||||
//Check if a cURL error has occured
|
||||
if ($response === false) {
|
||||
throw new Dropbox_CurlException($error);
|
||||
} else {
|
||||
// Parse the response if it is a string
|
||||
if (is_string($response)) {
|
||||
$response = $this->parse($response);
|
||||
}
|
||||
|
||||
// Set the last response
|
||||
$this->lastResponse = $response;
|
||||
|
||||
$code = (!empty($response['code'])) ? $response['code'] : $getinfo['http_code'];
|
||||
|
||||
// The API doesn't return an error message for the 304 status code...
|
||||
// 304's are only returned when the path supplied during metadata calls has not been modified
|
||||
if ($code == 304) {
|
||||
$response['body'] = new stdClass;
|
||||
$response['body']->error = 'The folder contents have not changed';
|
||||
}
|
||||
|
||||
// Check if an error occurred and throw an Exception
|
||||
if (!empty($response['body']->error) || $code >= 400) {
|
||||
// Dropbox returns error messages inconsistently...
|
||||
if (!empty($response['body']->error) && $response['body']->error instanceof stdClass) {
|
||||
$array = array_values((array) $response['body']->error);
|
||||
//Dropbox API v2 only throws 409 errors if this error is a incorrect_offset then we need the entire error array not just the message. PHP Exception messages have to be a string so JSON encode the array.
|
||||
if (strpos($array[0] , 'incorrect_offset') !== false) {
|
||||
$message = json_encode($array);
|
||||
} elseif (strpos($array[0] , 'lookup_failed') !== false ) {
|
||||
//re-structure the array so it is correctly formatted for API
|
||||
//Note: Dropbox v2 returns different errors at different stages hence this fix
|
||||
$correctOffset = array(
|
||||
'0' => $array[1]->{'.tag'},
|
||||
'1' => $array[1]->correct_offset
|
||||
);
|
||||
|
||||
$message = json_encode($correctOffset);
|
||||
} else {
|
||||
$message = $array[0];
|
||||
}
|
||||
} elseif (!empty($response['body']->error)) {
|
||||
$message = $response['body']->error;
|
||||
} elseif (is_string($response['body'])) {
|
||||
// 31 Mar 2017 - This case has been found to exist; though the docs imply that there's always an 'error' property and that what is returned in JSON, we found a case of this being returned just as a simple string, but detectable via an HTTP 400: Error in call to API function "files/upload_session/append_v2": HTTP header "Dropbox-API-Arg": cursor.offset: expected integer, got string
|
||||
$message = $response['body'];
|
||||
} else {
|
||||
$message = "HTTP bad response code: $code";
|
||||
}
|
||||
|
||||
// Throw an Exception with the appropriate with the appropriate message and code
|
||||
switch ($code) {
|
||||
case 304:
|
||||
throw new Dropbox_NotModifiedException($message, 304);
|
||||
case 400:
|
||||
throw new Dropbox_BadRequestException($message, 400);
|
||||
case 404:
|
||||
throw new Dropbox_NotFoundException($message, 404);
|
||||
case 406:
|
||||
throw new Dropbox_NotAcceptableException($message, 406);
|
||||
case 415:
|
||||
throw new Dropbox_UnsupportedMediaTypeException($message, 415);
|
||||
case 401:
|
||||
//401 means oauth token is expired continue to manually handle the exception depending on the situation
|
||||
break;
|
||||
case 409:
|
||||
//409 in API V2 every error will return with a 409 to find out what the error is the error description should be checked.
|
||||
throw new Dropbox_Exception($message, $code);
|
||||
default:
|
||||
throw new Dropbox_Exception($message, $code);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a cURL response
|
||||
* @param string $response
|
||||
* @return array
|
||||
*/
|
||||
private function parse($response)
|
||||
{
|
||||
// Explode the response into headers and body parts (separated by double EOL)
|
||||
list($headers, $response) = explode("\r\n\r\n", $response, 2);
|
||||
|
||||
// Explode response headers
|
||||
$lines = explode("\r\n", $headers);
|
||||
|
||||
// If the status code is 100, the API server must send a final response
|
||||
// We need to explode the response again to get the actual response
|
||||
if (preg_match('#^HTTP/[\.\d]+ 100#i', $lines[0])) {
|
||||
list($headers, $response) = explode("\r\n\r\n", $response, 2);
|
||||
$lines = explode("\r\n", $headers);
|
||||
}
|
||||
|
||||
// Get the HTTP response code from the first line
|
||||
$first = array_shift($lines);
|
||||
$pattern = '#^HTTP/[\.\d]+ ([0-9]{3})#i';
|
||||
preg_match($pattern, $first, $matches);
|
||||
$code = $matches[1];
|
||||
|
||||
// Parse the remaining headers into an associative array
|
||||
$headers = array();
|
||||
foreach ($lines as $line) {
|
||||
list($k, $v) = explode(': ', $line, 2);
|
||||
$headers[strtolower($k)] = $v;
|
||||
}
|
||||
|
||||
// If the response body is not a JSON encoded string
|
||||
// we'll return the entire response body
|
||||
if (!$body = json_decode($response)) {
|
||||
$body = $response;
|
||||
}
|
||||
|
||||
if (is_string($body)) {
|
||||
$body_lines = explode("\r\n", $body);
|
||||
if (preg_match('#^HTTP/[\.\d]+ 100#i', $body_lines[0]) && preg_match('#^HTTP/\d#i', $body_lines[2])) {
|
||||
return $this->parse($body);
|
||||
}
|
||||
}
|
||||
|
||||
return array('code' => $code, 'body' => $body, 'headers' => $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the response for the last API request
|
||||
* @return mixed
|
||||
*/
|
||||
public function getlastResponse()
|
||||
{
|
||||
return $this->lastResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OAuth consumer using the WordPress API
|
||||
* @author David Anderson <david@updraftplus.com>
|
||||
* @link https://github.com/DavidAnderson684/Dropbox
|
||||
* @package Dropbox\OAuth
|
||||
* @subpackage Consumer
|
||||
*/
|
||||
|
||||
class Dropbox_ConsumerWordPress extends Dropbox_ConsumerAbstract
|
||||
{
|
||||
|
||||
/**
|
||||
* Set properties and begin authentication
|
||||
* @param string $key
|
||||
* @param string $secret
|
||||
* @param \Dropbox\OAuth\Consumer\StorageInterface $storage
|
||||
* @param string $callback
|
||||
*/
|
||||
public function __construct($key, $secret, Dropbox_StorageInterface $storage, $callback = null)
|
||||
{
|
||||
// Check we are in a WordPress environment
|
||||
if (!defined('ABSPATH')) {
|
||||
throw new Dropbox_Exception('The WordPress OAuth consumer requires a WordPress environment');
|
||||
}
|
||||
|
||||
$this->consumerKey = $key;
|
||||
$this->consumerSecret = $secret;
|
||||
$this->storage = $storage;
|
||||
$this->callback = $callback;
|
||||
$this->authenticate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API call
|
||||
* @param string $method The HTTP method
|
||||
* @param string $url The API endpoint
|
||||
* @param string $call The API method to call
|
||||
* @param array $additional Additional parameters
|
||||
* @return array
|
||||
*/
|
||||
public function fetch($method, $url, $call, array $additional = array())
|
||||
{
|
||||
// Get the signed request URL
|
||||
$request = $this->getSignedRequest($method, $url, $call, $additional);
|
||||
if ($method == 'GET') {
|
||||
$args = array ( );
|
||||
$response = wp_remote_get($request['url'], $args);
|
||||
$this->outFile = null;
|
||||
} elseif ($method == 'POST') {
|
||||
$args = array( 'body' => $request['postfields'] );
|
||||
$response = wp_remote_post($request['url'], $args );
|
||||
} elseif ($method == 'PUT' && $this->inFile) {
|
||||
return new WP_Error('unsupported', "WordPress does not have a native HTTP PUT function");
|
||||
}
|
||||
|
||||
// If the response body is not a JSON encoded string
|
||||
// we'll return the entire response body
|
||||
// Important to do this first, as the next section relies on the decoding having taken place
|
||||
if (!$body = json_decode(wp_remote_retrieve_body($response))) {
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
}
|
||||
|
||||
// Check if an error occurred and throw an Exception. This is part of the authentication process - don't modify.
|
||||
if (!empty($body->error)) {
|
||||
$message = $body->error . ' (Status Code: ' . wp_remote_retrieve_response_code($response) . ')';
|
||||
throw new Dropbox_Exception($message);
|
||||
}
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
$message = $response->get_error_message();
|
||||
throw new Dropbox_Exception($message);
|
||||
}
|
||||
|
||||
$results = array ( 'body' => $body, 'code' => wp_remote_retrieve_response_code($response), 'headers' => $response['headers'] );
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user