mirror of
https://ghproxy.net/https://github.com/elementor/wp2static.git
synced 2025-08-30 13:43:54 +08:00
parent
c64c0235d6
commit
8121e976a8
52 changed files with 5913 additions and 4 deletions
238
library/Dropbox/AppInfo.php
Normal file
238
library/Dropbox/AppInfo.php
Normal file
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Your app's API key and secret.
|
||||
*/
|
||||
final class AppInfo
|
||||
{
|
||||
/**
|
||||
* Your Dropbox <em>app key</em> (OAuth calls this the <em>consumer key</em>). You can
|
||||
* create an app key and secret on the <a href="http://dropbox.com/developers/apps">Dropbox developer website</a>.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getKey() { return $this->key; }
|
||||
|
||||
/** @var string */
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* Your Dropbox <em>app secret</em> (OAuth calls this the <em>consumer secret</em>). You can
|
||||
* create an app key and secret on the <a href="http://dropbox.com/developers/apps">Dropbox developer website</a>.
|
||||
*
|
||||
* Make sure that this is kept a secret. Someone with your app secret can impesonate your
|
||||
* application. People sometimes ask for help on the Dropbox API forums and
|
||||
* copy/paste code that includes their app secret. Do not do that.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getSecret() { return $this->secret; }
|
||||
|
||||
/** @var string */
|
||||
private $secret;
|
||||
|
||||
/**
|
||||
* The set of servers your app will use. This defaults to the standard Dropbox servers
|
||||
* {@link Host::getDefault}.
|
||||
*
|
||||
* @return Host
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function getHost() { return $this->host; }
|
||||
|
||||
/** @var Host */
|
||||
private $host;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $key
|
||||
* See {@link getKey()}
|
||||
* @param string $secret
|
||||
* See {@link getSecret()}
|
||||
*/
|
||||
function __construct($key, $secret)
|
||||
{
|
||||
self::checkKeyArg($key);
|
||||
self::checkSecretArg($secret);
|
||||
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
|
||||
// The $host parameter is sort of internal. We don't include it in the param list because
|
||||
// we don't want it to be included in the documentation. Use PHP arg list hacks to get at
|
||||
// it.
|
||||
$host = null;
|
||||
if (\func_num_args() == 3) {
|
||||
$host = \func_get_arg(2);
|
||||
Host::checkArgOrNull("host", $host);
|
||||
}
|
||||
if ($host === null) {
|
||||
$host = Host::getDefault();
|
||||
}
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a JSON file containing information about your app. At a minimum, the file must include
|
||||
* the "key" and "secret" fields. Run 'php authorize.php' in the examples directory
|
||||
* for details about what this file should look like.
|
||||
*
|
||||
* @param string $path
|
||||
* Path to a JSON file
|
||||
*
|
||||
* @return AppInfo
|
||||
*
|
||||
* @throws AppInfoLoadException
|
||||
*/
|
||||
static function loadFromJsonFile($path)
|
||||
{
|
||||
list($rawJson, $appInfo) = self::loadFromJsonFileWithRaw($path);
|
||||
return $appInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a JSON file containing information about your app. At a minimum, the file must include
|
||||
* the "key" and "secret" fields. Run 'php authorize.php' in the examples directory
|
||||
* for details about what this file should look like.
|
||||
*
|
||||
* @param string $path
|
||||
* Path to a JSON file
|
||||
*
|
||||
* @return array
|
||||
* A list of two items. The first is a PHP array representation of the raw JSON, the second
|
||||
* is an AppInfo object that is the parsed version of the JSON.
|
||||
*
|
||||
* @throws AppInfoLoadException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function loadFromJsonFileWithRaw($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
throw new AppInfoLoadException("File doesn't exist: \"$path\"");
|
||||
}
|
||||
|
||||
$str = Util::stripUtf8Bom(file_get_contents($path));
|
||||
$jsonArr = json_decode($str, true, 10);
|
||||
|
||||
if (is_null($jsonArr)) {
|
||||
throw new AppInfoLoadException("JSON parse error: \"$path\"");
|
||||
}
|
||||
|
||||
$appInfo = self::loadFromJson($jsonArr);
|
||||
|
||||
return array($jsonArr, $appInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JSON object to build an AppInfo object. If you would like to load this from a file,
|
||||
* use the loadFromJsonFile() method.
|
||||
*
|
||||
* @param array $jsonArr Output from json_decode($str, true)
|
||||
*
|
||||
* @return AppInfo
|
||||
*
|
||||
* @throws AppInfoLoadException
|
||||
*/
|
||||
static function loadFromJson($jsonArr)
|
||||
{
|
||||
if (!is_array($jsonArr)) {
|
||||
throw new AppInfoLoadException("Expecting JSON object, got something else");
|
||||
}
|
||||
|
||||
$requiredKeys = array("key", "secret");
|
||||
foreach ($requiredKeys as $key) {
|
||||
if (!array_key_exists($key, $jsonArr)) {
|
||||
throw new AppInfoLoadException("Missing field \"$key\"");
|
||||
}
|
||||
|
||||
if (!is_string($jsonArr[$key])) {
|
||||
throw new AppInfoLoadException("Expecting field \"$key\" to be a string");
|
||||
}
|
||||
}
|
||||
|
||||
// Check app_key and app_secret
|
||||
$appKey = $jsonArr["key"];
|
||||
$appSecret = $jsonArr["secret"];
|
||||
|
||||
$tokenErr = self::getTokenPartError($appKey);
|
||||
if (!is_null($tokenErr)) {
|
||||
throw new AppInfoLoadException("Field \"key\" doesn't look like a valid app key: $tokenErr");
|
||||
}
|
||||
|
||||
$tokenErr = self::getTokenPartError($appSecret);
|
||||
if (!is_null($tokenErr)) {
|
||||
throw new AppInfoLoadException("Field \"secret\" doesn't look like a valid app secret: $tokenErr");
|
||||
}
|
||||
|
||||
// Check for the optional 'host' field
|
||||
if (!array_key_exists('host', $jsonArr)) {
|
||||
$host = null;
|
||||
}
|
||||
else {
|
||||
$baseHost = $jsonArr["host"];
|
||||
if (!is_string($baseHost)) {
|
||||
throw new AppInfoLoadException("Optional field \"host\" must be a string");
|
||||
}
|
||||
|
||||
$api = "api-$baseHost";
|
||||
$content = "api-content-$baseHost";
|
||||
$web = "meta-$baseHost";
|
||||
|
||||
$host = new Host($api, $content, $web);
|
||||
}
|
||||
|
||||
return new AppInfo($appKey, $appSecret, $host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to check that a function argument is of type <code>AppInfo</code>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArg($argName, $argValue)
|
||||
{
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to check that a function argument is either <code>null</code> or of type
|
||||
* <code>AppInfo</code>.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArgOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static function getTokenPartError($s)
|
||||
{
|
||||
if ($s === null) return "can't be null";
|
||||
if (strlen($s) === 0) return "can't be empty";
|
||||
if (strstr($s, ' ')) return "can't contain a space";
|
||||
return null; // 'null' means "no error"
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static function checkKeyArg($key)
|
||||
{
|
||||
$error = self::getTokenPartError($key);
|
||||
if ($error === null) return;
|
||||
throw new \InvalidArgumentException("Bad 'key': \"$key\": $error.");
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static function checkSecretArg($secret)
|
||||
{
|
||||
$error = self::getTokenPartError($secret);
|
||||
if ($error === null) return;
|
||||
throw new \InvalidArgumentException("Bad 'secret': \"$secret\": $error.");
|
||||
}
|
||||
|
||||
}
|
18
library/Dropbox/AppInfoLoadException.php
Normal file
18
library/Dropbox/AppInfoLoadException.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown by the <code>AppInfo::loadXXX</code> methods if something goes wrong.
|
||||
*/
|
||||
final class AppInfoLoadException extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
61
library/Dropbox/ArrayEntryStore.php
Normal file
61
library/Dropbox/ArrayEntryStore.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* A class that gives get/put/clear access to a single entry in an array.
|
||||
*/
|
||||
class ArrayEntryStore implements ValueStore
|
||||
{
|
||||
/** @var array */
|
||||
private $array;
|
||||
|
||||
/** @var mixed */
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $array
|
||||
* The array that we'll be accessing.
|
||||
*
|
||||
* @param mixed $key
|
||||
* The key for the array element we'll be accessing.
|
||||
*/
|
||||
function __construct(&$array, $key)
|
||||
{
|
||||
$this->array = &$array;
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry's current value or <code>null</code> if nothing is set.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function get()
|
||||
{
|
||||
if (isset($this->array[$this->key])) {
|
||||
return $this->array[$this->key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the array entry to the given value.
|
||||
*
|
||||
* @param object $value
|
||||
*/
|
||||
function set($value)
|
||||
{
|
||||
$this->array[$this->key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the entry.
|
||||
*/
|
||||
function clear()
|
||||
{
|
||||
unset($this->array[$this->key]);
|
||||
}
|
||||
}
|
75
library/Dropbox/AuthBase.php
Normal file
75
library/Dropbox/AuthBase.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Base class for API authorization-related classes.
|
||||
*/
|
||||
class AuthBase
|
||||
{
|
||||
/**
|
||||
* Whatever AppInfo was passed into the constructor.
|
||||
*
|
||||
* @return AppInfo
|
||||
*/
|
||||
function getAppInfo() { return $this->appInfo; }
|
||||
|
||||
/** @var AppInfo */
|
||||
protected $appInfo;
|
||||
|
||||
/**
|
||||
* An identifier for the API client, typically of the form "Name/Version".
|
||||
* This is used to set the HTTP <code>User-Agent</code> header when making API requests.
|
||||
* Example: <code>"PhotoEditServer/1.3"</code>
|
||||
*
|
||||
* If you're the author a higher-level library on top of the basic SDK, and the
|
||||
* "Photo Edit" app's server code is using your library to access Dropbox, you should append
|
||||
* your library's name and version to form the full identifier. For example,
|
||||
* if your library is called "File Picker", you might set this field to:
|
||||
* <code>"PhotoEditServer/1.3 FilePicker/0.1-beta"</code>
|
||||
*
|
||||
* The exact format of the <code>User-Agent</code> header is described in
|
||||
* <a href="http://tools.ietf.org/html/rfc2616#section-3.8">section 3.8 of the HTTP specification</a>.
|
||||
*
|
||||
* Note that underlying HTTP client may append other things to the <code>User-Agent</code>, such as
|
||||
* the name of the library being used to actually make the HTTP request (such as cURL).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getClientIdentifier() { return $this->clientIdentifier; }
|
||||
|
||||
/** @var string */
|
||||
protected $clientIdentifier;
|
||||
|
||||
/**
|
||||
* The locale of the user of your application. Some API calls return localized
|
||||
* data and error messages; this "user locale" setting determines which locale
|
||||
* the server should use to localize those strings.
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
function getUserLocale() { return $this->userLocale; }
|
||||
|
||||
/** @var string */
|
||||
protected $userLocale;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AppInfo $appInfo
|
||||
* See {@link getAppInfo()}
|
||||
* @param string $clientIdentifier
|
||||
* See {@link getClientIdentifier()}
|
||||
* @param null|string $userLocale
|
||||
* See {@link getUserLocale()}
|
||||
*/
|
||||
function __construct($appInfo, $clientIdentifier, $userLocale = null)
|
||||
{
|
||||
AppInfo::checkArg("appInfo", $appInfo);
|
||||
Client::checkClientIdentifierArg("clientIdentifier", $clientIdentifier);
|
||||
Checker::argStringNonEmptyOrNull("userLocale", $userLocale);
|
||||
|
||||
$this->appInfo = $appInfo;
|
||||
$this->clientIdentifier = $clientIdentifier;
|
||||
$this->userLocale = $userLocale;
|
||||
}
|
||||
}
|
85
library/Dropbox/AuthInfo.php
Normal file
85
library/Dropbox/AuthInfo.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* This class contains methods to load an AppInfo and AccessToken from a JSON file.
|
||||
* This can help simplify simple scripts (such as the example programs that come with the
|
||||
* SDK) but is probably not useful in typical Dropbox API apps.
|
||||
*
|
||||
*/
|
||||
final class AuthInfo
|
||||
{
|
||||
/**
|
||||
* Loads a JSON file containing authorization information for your app. 'php authorize.php'
|
||||
* in the examples directory for details about what this file should look like.
|
||||
*
|
||||
* @param string $path
|
||||
* Path to a JSON file
|
||||
* @return array
|
||||
* A <code>list(string $accessToken, Host $host)</code>.
|
||||
*
|
||||
* @throws AuthInfoLoadException
|
||||
*/
|
||||
static function loadFromJsonFile($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
throw new AuthInfoLoadException("File doesn't exist: \"$path\"");
|
||||
}
|
||||
|
||||
$str = Util::stripUtf8Bom(file_get_contents($path));
|
||||
$jsonArr = json_decode($str, true, 10);
|
||||
|
||||
if (is_null($jsonArr)) {
|
||||
throw new AuthInfoLoadException("JSON parse error: \"$path\"");
|
||||
}
|
||||
|
||||
return self::loadFromJson($jsonArr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JSON object to build an AuthInfo object. If you would like to load this from a file,
|
||||
* please use the @see loadFromJsonFile method.
|
||||
*
|
||||
* @param array $jsonArr
|
||||
* A parsed JSON object, typcally the result of json_decode(..., true).
|
||||
* @return array
|
||||
* A <code>list(string $accessToken, Host $host)</code>.
|
||||
*
|
||||
* @throws AuthInfoLoadException
|
||||
*/
|
||||
private static function loadFromJson($jsonArr)
|
||||
{
|
||||
if (!is_array($jsonArr)) {
|
||||
throw new AuthInfoLoadException("Expecting JSON object, found something else");
|
||||
}
|
||||
|
||||
// Check access_token
|
||||
if (!array_key_exists('access_token', $jsonArr)) {
|
||||
throw new AuthInfoLoadException("Missing field \"access_token\"");
|
||||
}
|
||||
|
||||
$accessToken = $jsonArr['access_token'];
|
||||
if (!is_string($accessToken)) {
|
||||
throw new AuthInfoLoadException("Expecting field \"access_token\" to be a string");
|
||||
}
|
||||
|
||||
// Check for the optional 'host' field
|
||||
if (!array_key_exists('host', $jsonArr)) {
|
||||
$host = null;
|
||||
}
|
||||
else {
|
||||
$baseHost = $jsonArr["host"];
|
||||
if (!is_string($baseHost)) {
|
||||
throw new AuthInfoLoadException("Optional field \"host\" must be a string");
|
||||
}
|
||||
|
||||
$api = "api-$baseHost";
|
||||
$content = "api-content-$baseHost";
|
||||
$web = "meta-$baseHost";
|
||||
|
||||
$host = new Host($api, $content, $web);
|
||||
}
|
||||
|
||||
return array($accessToken, $host);
|
||||
}
|
||||
}
|
18
library/Dropbox/AuthInfoLoadException.php
Normal file
18
library/Dropbox/AuthInfoLoadException.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown by the <code>AuthInfo::loadXXX</code> methods if something goes wrong.
|
||||
*/
|
||||
final class AuthInfoLoadException extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
94
library/Dropbox/Checker.php
Normal file
94
library/Dropbox/Checker.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Helper functions to validate arguments.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Checker
|
||||
{
|
||||
static function throwError($argName, $argValue, $expectedTypeName)
|
||||
{
|
||||
if ($argValue === null) throw new \InvalidArgumentException("'$argName' must not be null");
|
||||
|
||||
if (is_object($argValue)) {
|
||||
// Class type.
|
||||
$argTypeName = get_class($argValue);
|
||||
} else {
|
||||
// Built-in type.
|
||||
$argTypeName = gettype($argValue);
|
||||
}
|
||||
throw new \InvalidArgumentException("'$argName' has bad type; expecting $expectedTypeName, got $argTypeName");
|
||||
}
|
||||
|
||||
static function argResource($argName, $argValue)
|
||||
{
|
||||
if (!is_resource($argValue)) self::throwError($argName, $argValue, "resource");
|
||||
}
|
||||
|
||||
static function argCallable($argName, $argValue)
|
||||
{
|
||||
if (!is_callable($argValue)) self::throwError($argName, $argValue, "callable");
|
||||
}
|
||||
|
||||
static function argBool($argName, $argValue)
|
||||
{
|
||||
if (!is_bool($argValue)) self::throwError($argName, $argValue, "boolean");
|
||||
}
|
||||
|
||||
static function argArray($argName, $argValue)
|
||||
{
|
||||
if (!is_array($argValue)) self::throwError($argName, $argValue, "array");
|
||||
}
|
||||
|
||||
static function argString($argName, $argValue)
|
||||
{
|
||||
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||
}
|
||||
|
||||
static function argStringOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||
}
|
||||
|
||||
static function argStringNonEmpty($argName, $argValue)
|
||||
{
|
||||
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||
if (strlen($argValue) === 0) throw new \InvalidArgumentException("'$argName' must be non-empty");
|
||||
}
|
||||
|
||||
static function argStringNonEmptyOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
|
||||
if (strlen($argValue) === 0) throw new \InvalidArgumentException("'$argName' must be non-empty");
|
||||
}
|
||||
|
||||
static function argNat($argName, $argValue)
|
||||
{
|
||||
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||
if ($argValue < 0) throw new \InvalidArgumentException("'$argName' must be non-negative (you passed in $argValue)");
|
||||
}
|
||||
|
||||
static function argNatOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||
if ($argValue < 0) throw new \InvalidArgumentException("'$argName' must be non-negative (you passed in $argValue)");
|
||||
}
|
||||
|
||||
static function argIntPositive($argName, $argValue)
|
||||
{
|
||||
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||
if ($argValue < 1) throw new \InvalidArgumentException("'$argName' must be positive (you passed in $argValue)");
|
||||
}
|
||||
|
||||
static function argIntPositiveOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
|
||||
if ($argValue < 1) throw new \InvalidArgumentException("'$argName' must be positive (you passed in $argValue)");
|
||||
}
|
||||
}
|
1495
library/Dropbox/Client.php
Normal file
1495
library/Dropbox/Client.php
Normal file
File diff suppressed because it is too large
Load diff
126
library/Dropbox/Curl.php
Normal file
126
library/Dropbox/Curl.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* A minimal wrapper around a cURL handle.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Curl
|
||||
{
|
||||
/** @var resource */
|
||||
public $handle;
|
||||
|
||||
/** @var string[] */
|
||||
private $headers = array();
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*/
|
||||
function __construct($url)
|
||||
{
|
||||
// Make sure there aren't any spaces in the URL (i.e. the caller forgot to URL-encode).
|
||||
if (strpos($url, ' ') !== false) {
|
||||
throw new \InvalidArgumentException("Found space in \$url; it should be encoded");
|
||||
}
|
||||
|
||||
$this->handle = curl_init($url);
|
||||
|
||||
// NOTE: Though we turn on all the correct SSL settings, many PHP installations
|
||||
// don't respect these settings. Run "examples/test-ssl.php" to run some basic
|
||||
// SSL tests to see how well your PHP implementation behaves.
|
||||
|
||||
// Use our own certificate list.
|
||||
$this->set(CURLOPT_SSL_VERIFYPEER, true); // Enforce certificate validation
|
||||
$this->set(CURLOPT_SSL_VERIFYHOST, 2); // Enforce hostname validation
|
||||
|
||||
// Force the use of TLS (SSL v2 and v3 are not secure).
|
||||
// TODO: Use "CURL_SSLVERSION_TLSv1" instead of "1" once we can rely on PHP 5.5+.
|
||||
$this->set(CURLOPT_SSLVERSION, 1);
|
||||
|
||||
// Limit the set of ciphersuites used.
|
||||
global $sslCiphersuiteList;
|
||||
if ($sslCiphersuiteList !== null) {
|
||||
$this->set(CURLOPT_SSL_CIPHER_LIST, $sslCiphersuiteList);
|
||||
}
|
||||
|
||||
list($rootCertsFilePath, $rootCertsFolderPath) = RootCertificates::getPaths();
|
||||
// Certificate file.
|
||||
$this->set(CURLOPT_CAINFO, $rootCertsFilePath);
|
||||
// Certificate folder. If not specified, some PHP installations will use
|
||||
// the system default, even when CURLOPT_CAINFO is specified.
|
||||
$this->set(CURLOPT_CAPATH, $rootCertsFolderPath);
|
||||
|
||||
// Limit vulnerability surface area. Supported in cURL 7.19.4+
|
||||
if (defined('CURLOPT_PROTOCOLS')) $this->set(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
|
||||
if (defined('CURLOPT_REDIR_PROTOCOLS')) $this->set(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $header
|
||||
*/
|
||||
function addHeader($header)
|
||||
{
|
||||
$this->headers[] = $header;
|
||||
}
|
||||
|
||||
function exec()
|
||||
{
|
||||
$this->set(CURLOPT_HTTPHEADER, $this->headers);
|
||||
|
||||
$body = curl_exec($this->handle);
|
||||
if ($body === false) {
|
||||
throw new Exception_NetworkIO("Error executing HTTP request: " . curl_error($this->handle));
|
||||
}
|
||||
|
||||
$statusCode = curl_getinfo($this->handle, CURLINFO_HTTP_CODE);
|
||||
|
||||
return new HttpResponse($statusCode, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $option
|
||||
* @param mixed $value
|
||||
*/
|
||||
function set($option, $value)
|
||||
{
|
||||
curl_setopt($this->handle, $option, $value);
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
curl_close($this->handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Different cURL SSL backends use different names for ciphersuites.
|
||||
$curlVersion = \curl_version();
|
||||
$curlSslBackend = $curlVersion['ssl_version'];
|
||||
if (\substr_compare($curlSslBackend, "NSS/", 0, strlen("NSS/")) === 0) {
|
||||
// Can't figure out how to reliably set ciphersuites for NSS.
|
||||
$sslCiphersuiteList = null;
|
||||
}
|
||||
else {
|
||||
// Use the OpenSSL names for all other backends. We may have to
|
||||
// refine this if users report errors.
|
||||
$sslCiphersuiteList =
|
||||
'ECDHE-RSA-AES256-GCM-SHA384:'.
|
||||
'ECDHE-RSA-AES128-GCM-SHA256:'.
|
||||
'ECDHE-RSA-AES256-SHA384:'.
|
||||
'ECDHE-RSA-AES128-SHA256:'.
|
||||
'ECDHE-RSA-AES256-SHA:'.
|
||||
'ECDHE-RSA-AES128-SHA:'.
|
||||
'ECDHE-RSA-RC4-SHA:'.
|
||||
'DHE-RSA-AES256-GCM-SHA384:'.
|
||||
'DHE-RSA-AES128-GCM-SHA256:'.
|
||||
'DHE-RSA-AES256-SHA256:'.
|
||||
'DHE-RSA-AES128-SHA256:'.
|
||||
'DHE-RSA-AES256-SHA:'.
|
||||
'DHE-RSA-AES128-SHA:'.
|
||||
'AES256-GCM-SHA384:'.
|
||||
'AES128-GCM-SHA256:'.
|
||||
'AES256-SHA256:'.
|
||||
'AES128-SHA256:'.
|
||||
'AES256-SHA:'.
|
||||
'AES128-SHA';
|
||||
}
|
45
library/Dropbox/CurlStreamRelay.php
Normal file
45
library/Dropbox/CurlStreamRelay.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* A CURLOPT_WRITEFUNCTION that will write HTTP response data to $outStream if
|
||||
* it's an HTTP 200 response. For all other HTTP status codes, it'll save the
|
||||
* output in a string, which you can retrieve it via {@link getErrorBody}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class CurlStreamRelay
|
||||
{
|
||||
var $outStream;
|
||||
var $errorData;
|
||||
var $isError;
|
||||
|
||||
function __construct($ch, $outStream)
|
||||
{
|
||||
$this->outStream = $outStream;
|
||||
$this->errorData = array();
|
||||
$isError = null;
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'writeData'));
|
||||
}
|
||||
|
||||
function writeData($ch, $data)
|
||||
{
|
||||
if ($this->isError === null) {
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$this->isError = ($statusCode !== 200);
|
||||
}
|
||||
|
||||
if ($this->isError) {
|
||||
$this->errorData[] = $data;
|
||||
} else {
|
||||
fwrite($this->outStream, $data);
|
||||
}
|
||||
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
function getErrorBody()
|
||||
{
|
||||
return implode($this->errorData);
|
||||
}
|
||||
}
|
19
library/Dropbox/DeserializeException.php
Normal file
19
library/Dropbox/DeserializeException.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* If, when loading a serialized {@link RequestToken} or {@link AccessToken}, the input string is
|
||||
* malformed, this exception will be thrown.
|
||||
*/
|
||||
final class DeserializeException extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
83
library/Dropbox/DropboxMetadataHeaderCatcher.php
Normal file
83
library/Dropbox/DropboxMetadataHeaderCatcher.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DropboxMetadataHeaderCatcher
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
var $metadata = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
var $error = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
var $skippedFirstLine = false;
|
||||
|
||||
/**
|
||||
* @param resource $ch
|
||||
*/
|
||||
function __construct($ch)
|
||||
{
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerFunction'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $ch
|
||||
* @param string $header
|
||||
* @return int
|
||||
* @throws Exception_BadResponse
|
||||
*/
|
||||
function headerFunction($ch, $header)
|
||||
{
|
||||
// The first line is the HTTP status line (Ex: "HTTP/1.1 200 OK").
|
||||
if (!$this->skippedFirstLine) {
|
||||
$this->skippedFirstLine = true;
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
// If we've encountered an error on a previous callback, then there's nothing left to do.
|
||||
if ($this->error !== null) {
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
// case-insensitive starts-with check.
|
||||
if (\substr_compare($header, "x-dropbox-metadata:", 0, 19, true) !== 0) {
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
if ($this->metadata !== null) {
|
||||
$this->error = "Duplicate X-Dropbox-Metadata header";
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
$headerValue = substr($header, 19);
|
||||
$parsed = json_decode($headerValue, true, 10);
|
||||
|
||||
if ($parsed === null) {
|
||||
$this->error = "Bad JSON in X-Dropbox-Metadata header";
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
$this->metadata = $parsed;
|
||||
return strlen($header);
|
||||
}
|
||||
|
||||
function getMetadata()
|
||||
{
|
||||
if ($this->error !== null) {
|
||||
throw new Exception_BadResponse($this->error);
|
||||
}
|
||||
if ($this->metadata === null) {
|
||||
throw new Exception_BadResponse("Missing X-Dropbox-Metadata header");
|
||||
}
|
||||
return $this->metadata;
|
||||
}
|
||||
}
|
16
library/Dropbox/Exception.php
Normal file
16
library/Dropbox/Exception.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The base class for all API call exceptions.
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message, $cause = null)
|
||||
{
|
||||
parent::__construct($message, 0, $cause);
|
||||
}
|
||||
}
|
17
library/Dropbox/Exception/BadRequest.php
Normal file
17
library/Dropbox/Exception/BadRequest.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown when the server tells us that our request was invalid. This is typically due to an
|
||||
* HTTP 400 response from the server.
|
||||
*/
|
||||
final class Exception_BadRequest extends Exception_ProtocolError
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message = "")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
17
library/Dropbox/Exception/BadResponse.php
Normal file
17
library/Dropbox/Exception/BadResponse.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* When this SDK can't understand the response from the server. This could be due to a bug in this
|
||||
* SDK or a buggy response from the Dropbox server.
|
||||
*/
|
||||
class Exception_BadResponse extends Exception_ProtocolError
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
33
library/Dropbox/Exception/BadResponseCode.php
Normal file
33
library/Dropbox/Exception/BadResponseCode.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown when the the Dropbox server responds with an HTTP status code we didn't expect.
|
||||
*/
|
||||
final class Exception_BadResponseCode extends Exception_BadResponse
|
||||
{
|
||||
/** @var int */
|
||||
private $statusCode;
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message, $statusCode)
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->statusCode = $statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTTP status code returned by the Dropbox server.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
}
|
18
library/Dropbox/Exception/InvalidAccessToken.php
Normal file
18
library/Dropbox/Exception/InvalidAccessToken.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The Dropbox server said that the access token you used is invalid or expired. You should
|
||||
* probably ask the user to go through the OAuth authorization flow again to get a new access
|
||||
* token.
|
||||
*/
|
||||
final class Exception_InvalidAccessToken extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message = "")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
16
library/Dropbox/Exception/NetworkIO.php
Normal file
16
library/Dropbox/Exception/NetworkIO.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* There was a network I/O error when making the request.
|
||||
*/
|
||||
final class Exception_NetworkIO extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message, $cause = null)
|
||||
{
|
||||
parent::__construct($message, $cause);
|
||||
}
|
||||
}
|
16
library/Dropbox/Exception/OverQuota.php
Normal file
16
library/Dropbox/Exception/OverQuota.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* User is over Dropbox storage quota.
|
||||
*/
|
||||
final class Exception_OverQuota extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
17
library/Dropbox/Exception/ProtocolError.php
Normal file
17
library/Dropbox/Exception/ProtocolError.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* There was an protocol misunderstanding between this SDK and the server. One of us didn't
|
||||
* understand what the other one was saying.
|
||||
*/
|
||||
class Exception_ProtocolError extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
17
library/Dropbox/Exception/RetryLater.php
Normal file
17
library/Dropbox/Exception/RetryLater.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The Dropbox server said it couldn't fulfil our request right now, but that we should try
|
||||
* again later.
|
||||
*/
|
||||
final class Exception_RetryLater extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
15
library/Dropbox/Exception/ServerError.php
Normal file
15
library/Dropbox/Exception/ServerError.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The Dropbox server said that there was an internal error when trying to fulfil our request.
|
||||
* This usually corresponds to an HTTP 500 response.
|
||||
*/
|
||||
final class Exception_ServerError extends Exception
|
||||
{
|
||||
/** @internal */
|
||||
function __construct($message = "")
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
99
library/Dropbox/Host.php
Normal file
99
library/Dropbox/Host.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The Dropbox web API accesses three hosts; this structure holds the
|
||||
* names of those three hosts. This is primarily for mocking things out
|
||||
* during testing. Most of the time you won't have to deal with this class
|
||||
* directly, and even when you do, you'll just use the default
|
||||
* value: {@link Host::getDefault()}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Host
|
||||
{
|
||||
/**
|
||||
* Returns a Host object configured with the three standard Dropbox host: "api.dropbox.com",
|
||||
* "api-content.dropbox.com", and "www.dropbox.com"
|
||||
*
|
||||
* @return Host
|
||||
*/
|
||||
static function getDefault()
|
||||
{
|
||||
if (!self::$defaultValue) {
|
||||
self::$defaultValue = new Host("api.dropbox.com", "api-content.dropbox.com", "www.dropbox.com");
|
||||
}
|
||||
return self::$defaultValue;
|
||||
}
|
||||
private static $defaultValue;
|
||||
|
||||
/** @var string */
|
||||
private $api;
|
||||
/** @var string */
|
||||
private $content;
|
||||
/** @var string */
|
||||
private $web;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $api
|
||||
* See {@link getApi()}
|
||||
* @param string $content
|
||||
* See {@link getContent()}
|
||||
* @param string $web
|
||||
* See {@link getWeb()}
|
||||
*/
|
||||
function __construct($api, $content, $web)
|
||||
{
|
||||
$this->api = $api;
|
||||
$this->content = $content;
|
||||
$this->web = $web;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name of the main Dropbox API server.
|
||||
* The default is "api.dropbox.com".
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getApi() { return $this->api; }
|
||||
|
||||
/**
|
||||
* Returns the host name of the Dropbox API content server.
|
||||
* The default is "api-content.dropbox.com".
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getContent() { return $this->content; }
|
||||
|
||||
/**
|
||||
* Returns the host name of the Dropbox web server. Used during user authorization.
|
||||
* The default is "www.dropbox.com".
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getWeb() { return $this->web; }
|
||||
|
||||
/**
|
||||
* Check that a function argument is of type <code>Host</code>.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArg($argName, $argValue)
|
||||
{
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a function argument is either <code>null</code> or of type
|
||||
* <code>Host</code>.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArgOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
}
|
17
library/Dropbox/HttpResponse.php
Normal file
17
library/Dropbox/HttpResponse.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class HttpResponse
|
||||
{
|
||||
public $statusCode;
|
||||
public $body;
|
||||
|
||||
function __construct($statusCode, $body)
|
||||
{
|
||||
$this->statusCode = $statusCode;
|
||||
$this->body = $body;
|
||||
}
|
||||
}
|
61
library/Dropbox/OAuth1AccessToken.php
Normal file
61
library/Dropbox/OAuth1AccessToken.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Use with {@link OAuth1Upgrader} to convert old OAuth 1 access tokens
|
||||
* to OAuth 2 access tokens. This SDK doesn't support using OAuth 1
|
||||
* access tokens for regular API calls.
|
||||
*/
|
||||
class OAuth1AccessToken
|
||||
{
|
||||
/**
|
||||
* The OAuth 1 access token key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getKey() { return $this->key; }
|
||||
|
||||
/** @var string */
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* The OAuth 1 access token secret.
|
||||
*
|
||||
* Make sure that this is kept a secret. Someone with your app secret can impesonate your
|
||||
* application. People sometimes ask for help on the Dropbox API forums and
|
||||
* copy/paste code that includes their app secret. Do not do that.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getSecret() { return $this->secret; }
|
||||
|
||||
/** @var secret */
|
||||
private $secret;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $key
|
||||
* {@link getKey()}
|
||||
* @param string $secret
|
||||
* {@link getSecret()}
|
||||
*/
|
||||
function __construct($key, $secret)
|
||||
{
|
||||
AppInfo::checkKeyArg($key);
|
||||
AppInfo::checkSecretArg($secret);
|
||||
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to check that a function argument is of type <code>AppInfo</code>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArg($argName, $argValue)
|
||||
{
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
}
|
104
library/Dropbox/OAuth1Upgrader.php
Normal file
104
library/Dropbox/OAuth1Upgrader.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Lets you convert OAuth 1 access tokens to OAuth 2 access tokens. First call {@link
|
||||
* OAuth1AccessTokenUpgrader::createOAuth2AccessToken()} to get an OAuth 2 access token.
|
||||
* If that succeeds, call {@link OAuth1AccessTokenUpgrader::disableOAuth1AccessToken()}
|
||||
* to disable the OAuth 1 access token.
|
||||
*
|
||||
* <code>
|
||||
* use \Dropbox as dbx;
|
||||
* $appInfo = dbx\AppInfo::loadFromJsonFile(...);
|
||||
* $clientIdentifier = "my-app/1.0";
|
||||
* $oauth1AccessToken = dbx\OAuth1AccessToken(...);
|
||||
*
|
||||
* $upgrader = new dbx\OAuth1AccessTokenUpgrader($appInfo, $clientIdentifier, ...);
|
||||
* $oauth2AccessToken = $upgrader->getOAuth2AccessToken($oauth1AccessToken);
|
||||
* $upgrader->disableOAuth1AccessToken($oauth1AccessToken);
|
||||
* </code>
|
||||
*/
|
||||
class OAuth1Upgrader extends AuthBase
|
||||
{
|
||||
/**
|
||||
* Given an existing active OAuth 1 access token, make a Dropbox API call to get a new OAuth 2
|
||||
* access token that represents the same user and app.
|
||||
*
|
||||
* See <a href="https://www.dropbox.com/developers/core/docs#oa1-from-oa1">/oauth2/token_from_oauth1</a>.
|
||||
*
|
||||
* @param OAuth1AccessToken $oauth1AccessToken
|
||||
*
|
||||
* @return string
|
||||
* The OAuth 2 access token.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function createOAuth2AccessToken($oauth1AccessToken)
|
||||
{
|
||||
OAuth1AccessToken::checkArg("oauth1AccessToken", $oauth1AccessToken);
|
||||
|
||||
$response = self::doPost($oauth1AccessToken, "1/oauth2/token_from_oauth1");
|
||||
|
||||
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
|
||||
|
||||
$parts = RequestUtil::parseResponseJson($response->body);
|
||||
|
||||
if (!array_key_exists('token_type', $parts) || !is_string($parts['token_type'])) {
|
||||
throw new Exception_BadResponse("Missing \"token_type\" field.");
|
||||
}
|
||||
$tokenType = $parts['token_type'];
|
||||
if (!array_key_exists('access_token', $parts) || !is_string($parts['access_token'])) {
|
||||
throw new Exception_BadResponse("Missing \"access_token\" field.");
|
||||
}
|
||||
$accessToken = $parts['access_token'];
|
||||
|
||||
if ($tokenType !== "Bearer" && $tokenType !== "bearer") {
|
||||
throw new Exception_BadResponse("Unknown \"token_type\"; expecting \"Bearer\", got "
|
||||
. Util::q($tokenType));
|
||||
}
|
||||
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Dropbox API call to disable the given OAuth 1 access token.
|
||||
*
|
||||
* See <a href="https://www.dropbox.com/developers/core/docs#disable-token">/disable_access_token</a>.
|
||||
*
|
||||
* @param OAuth1AccessToken $oauth1AccessToken
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function disableOAuth1AccessToken($oauth1AccessToken)
|
||||
{
|
||||
OAuth1AccessToken::checkArg("oauth1AccessToken", $oauth1AccessToken);
|
||||
|
||||
$response = self::doPost($oauth1AccessToken, "1/disable_access_token");
|
||||
|
||||
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OAuth1AccessToken $oauth1AccessToken
|
||||
* @param string $path
|
||||
*
|
||||
* @return HttpResponse
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function doPost($oauth1AccessToken, $path)
|
||||
{
|
||||
// Construct the OAuth 1 header.
|
||||
$signature = rawurlencode($this->appInfo->getSecret()) . "&" . rawurlencode($oauth1AccessToken->getSecret());
|
||||
$authHeaderValue = "OAuth oauth_signature_method=\"PLAINTEXT\""
|
||||
. ", oauth_consumer_key=\"" . rawurlencode($this->appInfo->getKey()) . "\""
|
||||
. ", oauth_token=\"" . rawurlencode($oauth1AccessToken->getKey()) . "\""
|
||||
. ", oauth_signature=\"" . $signature . "\"";
|
||||
|
||||
return RequestUtil::doPostWithSpecificAuth(
|
||||
$this->clientIdentifier, $authHeaderValue, $this->userLocale,
|
||||
$this->appInfo->getHost()->getApi(),
|
||||
$path,
|
||||
null);
|
||||
}
|
||||
}
|
171
library/Dropbox/Path.php
Normal file
171
library/Dropbox/Path.php
Normal file
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Path validation functions.
|
||||
*/
|
||||
final class Path
|
||||
{
|
||||
/**
|
||||
* Return whether the given path is a valid Dropbox path.
|
||||
*
|
||||
* @param string $path
|
||||
* The path you want to check for validity.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the path was valid or not.
|
||||
*/
|
||||
static function isValid($path)
|
||||
{
|
||||
$error = self::findError($path);
|
||||
return ($error === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given path is a valid non-root Dropbox path.
|
||||
* This is the same as {@link isValid} except <code>"/"</code> is not allowed.
|
||||
*
|
||||
* @param string $path
|
||||
* The path you want to check for validity.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the path was valid or not.
|
||||
*/
|
||||
static function isValidNonRoot($path)
|
||||
{
|
||||
$error = self::findErrorNonRoot($path);
|
||||
return ($error === null);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given path is a valid Dropbox path, return <code>null</code>,
|
||||
* otherwise return an English string error message describing what is wrong with the path.
|
||||
*
|
||||
* @param string $path
|
||||
* The path you want to check for validity.
|
||||
*
|
||||
* @return string|null
|
||||
* If the path was valid, return <code>null</code>. Otherwise, returns
|
||||
* an English string describing the problem.
|
||||
*/
|
||||
static function findError($path)
|
||||
{
|
||||
Checker::argString("path", $path);
|
||||
|
||||
$matchResult = preg_match('%^(?:
|
||||
[\x09\x0A\x0D\x20-\x7E] # ASCII
|
||||
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|
||||
| \xE0[\xA0-\xBF][\x80-\xBD] # excluding overlongs, FFFE, and FFFF
|
||||
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|
||||
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|
||||
)*$%xs', $path);
|
||||
|
||||
if ($matchResult !== 1) {
|
||||
return "must be valid UTF-8; BMP only, no surrogates, no U+FFFE or U+FFFF";
|
||||
}
|
||||
|
||||
if (\substr_compare($path, "/", 0, 1) !== 0) return "must start with \"/\"";
|
||||
$l = strlen($path);
|
||||
if ($l === 1) return null; // Special case for "/"
|
||||
|
||||
if ($path[$l-1] === "/") return "must not end with \"/\"";
|
||||
|
||||
// TODO: More checks.
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given path is a valid non-root Dropbox path, return <code>null</code>,
|
||||
* otherwise return an English string error message describing what is wrong with the path.
|
||||
* This is the same as {@link findError} except <code>"/"</code> will yield an error message.
|
||||
*
|
||||
* @param string $path
|
||||
* The path you want to check for validity.
|
||||
*
|
||||
* @return string|null
|
||||
* If the path was valid, return <code>null</code>. Otherwise, returns
|
||||
* an English string describing the problem.
|
||||
*/
|
||||
static function findErrorNonRoot($path)
|
||||
{
|
||||
if ($path == "/") return "root path not allowed";
|
||||
return self::findError($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last component of a path (the file or folder name).
|
||||
*
|
||||
* <code>
|
||||
* Path::getName("/Misc/Notes.txt") // "Notes.txt"
|
||||
* Path::getName("/Misc") // "Misc"
|
||||
* Path::getName("/") // null
|
||||
* </code>
|
||||
*
|
||||
* @param string $path
|
||||
* The full path you want to get the last component of.
|
||||
*
|
||||
* @return null|string
|
||||
* The last component of <code>$path</code> or <code>null</code> if the given
|
||||
* <code>$path</code> was <code>"/"<code>.
|
||||
*/
|
||||
static function getName($path)
|
||||
{
|
||||
Checker::argString("path", $path);
|
||||
|
||||
if (\substr_compare($path, "/", 0, 1) !== 0) {
|
||||
throw new \InvalidArgumentException("'path' must start with \"/\"");
|
||||
}
|
||||
$l = strlen($path);
|
||||
if ($l === 1) return null;
|
||||
if ($path[$l-1] === "/") {
|
||||
throw new \InvalidArgumentException("'path' must not end with \"/\"");
|
||||
}
|
||||
|
||||
$lastSlash = strrpos($path, "/");
|
||||
return substr($path, $lastSlash+1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param string $argName
|
||||
* @param mixed $value
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
static function checkArg($argName, $value)
|
||||
{
|
||||
if ($value === null) throw new \InvalidArgumentException("'$argName' must not be null");
|
||||
if (!is_string($value)) throw new \InvalidArgumentException("'$argName' must be a string");
|
||||
$error = self::findError($value);
|
||||
if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param string $argName
|
||||
* @param mixed $value
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
static function checkArgOrNull($argName, $value)
|
||||
{
|
||||
if ($value === null) return;
|
||||
self::checkArg($argName, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param string $argName
|
||||
* @param mixed $value
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
static function checkArgNonRoot($argName, $value)
|
||||
{
|
||||
if ($value === null) throw new \InvalidArgumentException("'$argName' must not be null");
|
||||
if (!is_string($value)) throw new \InvalidArgumentException("'$argName' must be a string");
|
||||
$error = self::findErrorNonRoot($value);
|
||||
if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
|
||||
}
|
||||
}
|
302
library/Dropbox/RequestUtil.php
Normal file
302
library/Dropbox/RequestUtil.php
Normal file
|
@ -0,0 +1,302 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
if (!function_exists('curl_init')) {
|
||||
throw new \Exception("The Dropbox SDK requires the cURL PHP extension, but it looks like you don't have it (couldn't find function \"curl_init\"). Library: \"" . __FILE__ . "\".");
|
||||
}
|
||||
|
||||
if (!function_exists('json_decode')) {
|
||||
throw new \Exception("The Dropbox SDK requires the JSON PHP extension, but it looks like you don't have it (couldn't find function \"json_decode\"). Library: \"" . __FILE__ . "\".");
|
||||
}
|
||||
|
||||
// If mbstring.func_overload is set, it changes the behavior of the standard string functions in
|
||||
// ways that makes this library break.
|
||||
$mbstring_func_overload = ini_get("mbstring.func_overload");
|
||||
if ($mbstring_func_overload & 2 == 2) {
|
||||
throw new \Exception("The Dropbox SDK doesn't work when mbstring.func_overload is set to overload the standard string functions (value = ".var_export($mbstring_func_overload, true)."). Library: \"" . __FILE__ . "\".");
|
||||
}
|
||||
|
||||
if (strlen((string) PHP_INT_MAX) < 19) {
|
||||
// Looks like we're running on a 32-bit build of PHP. This could cause problems because some of the numbers
|
||||
// we use (file sizes, quota, etc) can be larger than 32-bit ints can handle.
|
||||
throw new \Exception("The Dropbox SDK uses 64-bit integers, but it looks like we're running on a version of PHP that doesn't support 64-bit integers (PHP_INT_MAX=" . ((string) PHP_INT_MAX) . "). Library: \"" . __FILE__ . "\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RequestUtil
|
||||
{
|
||||
/**
|
||||
* @param string $userLocale
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
static function buildUrlForGetOrPut($userLocale, $host, $path, $params = null)
|
||||
{
|
||||
$url = self::buildUri($host, $path);
|
||||
$url .= "?locale=" . rawurlencode($userLocale);
|
||||
|
||||
if ($params !== null) {
|
||||
foreach ($params as $key => $value) {
|
||||
Checker::argStringNonEmpty("key in 'params'", $key);
|
||||
if ($value !== null) {
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? "true" : "false";
|
||||
}
|
||||
else if (is_int($value)) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
else if (!is_string($value)) {
|
||||
throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
|
||||
}
|
||||
$url .= "&" . rawurlencode($key) . "=" . rawurlencode($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
static function buildUri($host, $path)
|
||||
{
|
||||
Checker::argStringNonEmpty("host", $host);
|
||||
Checker::argStringNonEmpty("path", $path);
|
||||
return "https://" . $host . "/" . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $url
|
||||
* @return Curl
|
||||
*/
|
||||
static function mkCurl($clientIdentifier, $url)
|
||||
{
|
||||
$curl = new Curl($url);
|
||||
|
||||
$curl->set(CURLOPT_CONNECTTIMEOUT, 10);
|
||||
|
||||
// If the transfer speed is below 1kB/sec for 10 sec, abort.
|
||||
$curl->set(CURLOPT_LOW_SPEED_LIMIT, 1024);
|
||||
$curl->set(CURLOPT_LOW_SPEED_TIME, 10);
|
||||
|
||||
//$curl->set(CURLOPT_VERBOSE, true); // For debugging.
|
||||
// TODO: Figure out how to encode clientIdentifier (urlencode?)
|
||||
$curl->addHeader("User-Agent: ".$clientIdentifier." Dropbox-PHP-SDK/".SdkVersion::VERSION);
|
||||
|
||||
return $curl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $url
|
||||
* @param string $authHeaderValue
|
||||
* @return Curl
|
||||
*/
|
||||
static function mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue)
|
||||
{
|
||||
$curl = self::mkCurl($clientIdentifier, $url);
|
||||
$curl->addHeader("Authorization: $authHeaderValue");
|
||||
return $curl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $url
|
||||
* @param string $accessToken
|
||||
* @return Curl
|
||||
*/
|
||||
static function mkCurlWithOAuth($clientIdentifier, $url, $accessToken)
|
||||
{
|
||||
return self::mkCurlWithAuth($clientIdentifier, $url, "Bearer $accessToken");
|
||||
}
|
||||
|
||||
static function buildPostBody($params)
|
||||
{
|
||||
if ($params === null) return "";
|
||||
|
||||
$pairs = array();
|
||||
foreach ($params as $key => $value) {
|
||||
Checker::argStringNonEmpty("key in 'params'", $key);
|
||||
if ($value !== null) {
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? "true" : "false";
|
||||
}
|
||||
else if (is_int($value)) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
else if (!is_string($value)) {
|
||||
throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
|
||||
}
|
||||
$pairs[] = rawurlencode($key) . "=" . rawurlencode((string) $value);
|
||||
}
|
||||
}
|
||||
return implode("&", $pairs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $accessToken
|
||||
* @param string $userLocale
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return HttpResponse
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
static function doPost($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
|
||||
{
|
||||
Checker::argStringNonEmpty("accessToken", $accessToken);
|
||||
|
||||
$url = self::buildUri($host, $path);
|
||||
|
||||
if ($params === null) $params = array();
|
||||
$params['locale'] = $userLocale;
|
||||
|
||||
$curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
|
||||
$curl->set(CURLOPT_POST, true);
|
||||
$curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
|
||||
|
||||
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||
return $curl->exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $authHeaderValue
|
||||
* @param string $userLocale
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return HttpResponse
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
static function doPostWithSpecificAuth($clientIdentifier, $authHeaderValue, $userLocale, $host, $path, $params = null)
|
||||
{
|
||||
Checker::argStringNonEmpty("authHeaderValue", $authHeaderValue);
|
||||
|
||||
$url = self::buildUri($host, $path);
|
||||
|
||||
if ($params === null) $params = array();
|
||||
$params['locale'] = $userLocale;
|
||||
|
||||
$curl = self::mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue);
|
||||
$curl->set(CURLOPT_POST, true);
|
||||
$curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
|
||||
|
||||
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||
return $curl->exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $accessToken
|
||||
* @param string $userLocale
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return HttpResponse
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
static function doGet($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
|
||||
{
|
||||
Checker::argStringNonEmpty("accessToken", $accessToken);
|
||||
|
||||
$url = self::buildUrlForGetOrPut($userLocale, $host, $path, $params);
|
||||
|
||||
$curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
|
||||
$curl->set(CURLOPT_HTTPGET, true);
|
||||
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
return $curl->exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $responseBody
|
||||
* @return mixed
|
||||
* @throws Exception_BadResponse
|
||||
*/
|
||||
static function parseResponseJson($responseBody)
|
||||
{
|
||||
$obj = json_decode($responseBody, true, 10);
|
||||
if ($obj === null) {
|
||||
throw new Exception_BadResponse("Got bad JSON from server: $responseBody");
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
static function unexpectedStatus($httpResponse)
|
||||
{
|
||||
$sc = $httpResponse->statusCode;
|
||||
|
||||
$message = "HTTP status $sc";
|
||||
if (is_string($httpResponse->body)) {
|
||||
// TODO: Maybe only include the first ~200 chars of the body?
|
||||
$message .= "\n".$httpResponse->body;
|
||||
}
|
||||
|
||||
if ($sc === 400) return new Exception_BadRequest($message);
|
||||
if ($sc === 401) return new Exception_InvalidAccessToken($message);
|
||||
if ($sc === 500 || $sc === 502) return new Exception_ServerError($message);
|
||||
if ($sc === 503) return new Exception_RetryLater($message);
|
||||
if ($sc === 507) return new Exception_OverQuota($message);
|
||||
|
||||
return new Exception_BadResponseCode("Unexpected $message", $sc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $maxRetries
|
||||
* The number of times to retry it the action if it fails with one of the transient
|
||||
* API errors. A value of 1 means we'll try the action once and if it fails, we
|
||||
* will retry once.
|
||||
*
|
||||
* @param callable $action
|
||||
* The the action you want to retry.
|
||||
*
|
||||
* @return mixed
|
||||
* Whatever is returned by the $action callable.
|
||||
*/
|
||||
static function runWithRetry($maxRetries, $action)
|
||||
{
|
||||
Checker::argNat("maxRetries", $maxRetries);
|
||||
|
||||
$retryDelay = 1;
|
||||
$numRetries = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return $action();
|
||||
}
|
||||
// These exception types are the ones we think are possibly transient errors.
|
||||
catch (Exception_NetworkIO $ex) {
|
||||
$savedEx = $ex;
|
||||
}
|
||||
catch (Exception_ServerError $ex) {
|
||||
$savedEx = $ex;
|
||||
}
|
||||
catch (Exception_RetryLater $ex) {
|
||||
$savedEx = $ex;
|
||||
}
|
||||
|
||||
// We maxed out our retries. Propagate the last exception we got.
|
||||
if ($numRetries >= $maxRetries) throw $savedEx;
|
||||
|
||||
$numRetries++;
|
||||
sleep($retryDelay);
|
||||
$retryDelay *= 2; // Exponential back-off.
|
||||
}
|
||||
throw new \RuntimeException("unreachable");
|
||||
}
|
||||
|
||||
}
|
144
library/Dropbox/RootCertificates.php
Normal file
144
library/Dropbox/RootCertificates.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* See: {@link RootCertificates::useExternalPaths()}
|
||||
*/
|
||||
class RootCertificates
|
||||
{
|
||||
/* @var boolean */
|
||||
private static $useExternalFile = false;
|
||||
|
||||
/* @var string[]|null */
|
||||
private static $paths = null; // A tuple of (rootCertsFilePath, rootCertsFolderPath)
|
||||
|
||||
/**
|
||||
* If you're running within a PHAR, call this method before you use the SDK
|
||||
* to make any network requests.
|
||||
*
|
||||
* Normally, the SDK tells cURL to look in the "certs" folder for root certificate
|
||||
* information. But this won't work if this SDK is running from within a PHAR because
|
||||
* cURL won't read files that are packaged in a PHAR.
|
||||
*/
|
||||
static function useExternalPaths()
|
||||
{
|
||||
if (!self::$useExternalFile and self::$paths !== null) {
|
||||
throw new \Exception("You called \"useExternalFile\" too late. The SDK already used the root ".
|
||||
"certificate file (probably to make an API call).");
|
||||
}
|
||||
|
||||
self::$useExternalFile = true;
|
||||
}
|
||||
|
||||
private static $originalPath = '/certs/trusted-certs.crt';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return string[]
|
||||
* A tuple of (rootCertsFilePath, rootCertsFolderPath). To be used with cURL options CAINFO and CAPATH.
|
||||
*/
|
||||
static function getPaths()
|
||||
{
|
||||
if (self::$paths === null) {
|
||||
if (self::$useExternalFile) {
|
||||
try {
|
||||
$baseFolder = sys_get_temp_dir();
|
||||
$file = self::createExternalCaFile($baseFolder);
|
||||
$folder = self::createExternalCaFolder($baseFolder);
|
||||
}
|
||||
catch (\Exception $ex) {
|
||||
throw new \Exception("Unable to create external root certificate file and folder: ".$ex->getMessage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (substr(__DIR__, 0, 7) === 'phar://') {
|
||||
throw new \Exception("The code appears to be running in a PHAR. You need to call \\Dropbox\\RootCertificates\\useExternalPaths() before making any API calls.");
|
||||
}
|
||||
$file = __DIR__.self::$originalPath;
|
||||
$folder = \dirname($file);
|
||||
}
|
||||
self::$paths = array($file, $folder);
|
||||
}
|
||||
|
||||
return self::$paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseFolder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function createExternalCaFolder($baseFolder)
|
||||
{
|
||||
// This is hacky, but I can't find a simple way to do this.
|
||||
|
||||
// This process isn't atomic, so give it three tries.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$path = \tempnam($baseFolder, "dropbox-php-sdk-trusted-certs-empty-dir");
|
||||
if ($path === false) {
|
||||
throw new \Exception("Couldn't create temp file in folder ".Util::q($baseFolder).".");
|
||||
}
|
||||
if (!\unlink($path)) {
|
||||
throw new \Exception("Couldn't remove temp file to make way for temp dir: ".Util::q($path));
|
||||
}
|
||||
// TODO: Figure out how to make the folder private on Windows. The '700' only works on Unix.
|
||||
if (!\mkdir($path, 700)) {
|
||||
// Someone snuck in between the unlink() and the mkdir() and stole our path.
|
||||
throw new \Exception("Couldn't create temp dir: ".Util::q($path));
|
||||
}
|
||||
\register_shutdown_function(function() use ($path) {
|
||||
\rmdir($path);
|
||||
});
|
||||
return $path;
|
||||
}
|
||||
|
||||
throw new \Exception("Unable to create temp dir in ".Util::q($baseFolder).", there's always something in the way.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseFolder
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function createExternalCaFile($baseFolder)
|
||||
{
|
||||
$path = \tempnam($baseFolder, "dropbox-php-sdk-trusted-certs");
|
||||
if ($path === false) {
|
||||
throw new \Exception("Couldn't create temp file in folder ".Util::q($baseFolder).".");
|
||||
}
|
||||
\register_shutdown_function(function() use ($path) {
|
||||
\unlink($path);
|
||||
});
|
||||
|
||||
// NOTE: Can't use the standard PHP copy(). That would clobber the locked-down
|
||||
// permissions set by tempnam().
|
||||
self::copyInto(__DIR__.self::$originalPath, $path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $src
|
||||
* @param string $dest
|
||||
*/
|
||||
private static function copyInto($src, $dest)
|
||||
{
|
||||
$srcFd = \fopen($src, "r");
|
||||
if ($srcFd === false) {
|
||||
throw new \Exception("Couldn't open " . Util::q($src) . " for reading.");
|
||||
}
|
||||
$destFd = \fopen($dest, "w");
|
||||
if ($destFd === false) {
|
||||
\fclose($srcFd);
|
||||
throw new \Exception("Couldn't open " . Util::q($dest) . " for writing.");
|
||||
}
|
||||
|
||||
\stream_copy_to_stream($srcFd, $destFd);
|
||||
|
||||
fclose($srcFd);
|
||||
if (!\fclose($destFd)) {
|
||||
throw new \Exception("Error closing file ".Util::q($dest).".");
|
||||
}
|
||||
}
|
||||
}
|
128
library/Dropbox/SSLTester.php
Normal file
128
library/Dropbox/SSLTester.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Call the <code>test()</code> method.
|
||||
*/
|
||||
class SSLTester
|
||||
{
|
||||
/**
|
||||
* Peforms a few basic tests of your PHP installation's SSL implementation to see
|
||||
* if it insecure in an obvious way. Results are written with "echo" and the output
|
||||
* is HTML-safe.
|
||||
*
|
||||
* @return bool
|
||||
* Returns <code>true</code> if all the tests passed.
|
||||
*/
|
||||
static function test()
|
||||
{
|
||||
$hostOs = php_uname('s').' '.php_uname('r');
|
||||
$phpVersion = phpversion();
|
||||
$curlVersionInfo = \curl_version();
|
||||
$curlVersion = $curlVersionInfo['version'];
|
||||
$curlSslBackend = $curlVersionInfo['ssl_version'];
|
||||
|
||||
echo "-----------------------------------------------------------------------------\n";
|
||||
echo "Testing your PHP installation's SSL implementation for a few obvious problems...\n";
|
||||
echo "-----------------------------------------------------------------------------\n";
|
||||
echo "- Host OS: $hostOs\n";
|
||||
echo "- PHP version: $phpVersion\n";
|
||||
echo "- cURL version: $curlVersion\n";
|
||||
echo "- cURL SSL backend: $curlSslBackend\n";
|
||||
|
||||
echo "Basic SSL tests\n";
|
||||
$basicFailures = self::testMulti(array(
|
||||
array("www.dropbox.com", 'testAllowed'),
|
||||
array("www.digicert.com", 'testAllowed'),
|
||||
array("www.v.dropbox.com", 'testHostnameMismatch'),
|
||||
array("testssl-expire.disig.sk", 'testUntrustedCert'),
|
||||
));
|
||||
|
||||
echo "Pinned certificate tests\n";
|
||||
$pinnedCertFailures = self::testMulti(array(
|
||||
array("www.verisign.com", 'testUntrustedCert'),
|
||||
array("www.globalsign.fr", 'testUntrustedCert'),
|
||||
));
|
||||
|
||||
if ($basicFailures) {
|
||||
echo "-----------------------------------------------------------------------------\n";
|
||||
echo "WARNING: Your PHP installation's SSL support is COMPLETELY INSECURE.\n";
|
||||
echo "Your app's communication with the Dropbox API servers can be viewed and\n";
|
||||
echo "manipulated by others. Try upgrading your version of PHP.\n";
|
||||
echo "-----------------------------------------------------------------------------\n";
|
||||
return false;
|
||||
}
|
||||
else if ($pinnedCertFailures) {
|
||||
echo "-----------------------------------------------------------------------------\n";
|
||||
echo "WARNING: Your PHP installation's cURL module doesn't support SSL certificate\n";
|
||||
echo "pinning, which is an important security feature of the Dropbox SDK.\n";
|
||||
echo "\n";
|
||||
echo "This SDK uses CURLOPT_CAINFO and CURLOPT_CAPATH to tell PHP cURL to only trust\n";
|
||||
echo "our custom certificate list. But your PHP installation's cURL module seems to\n";
|
||||
echo "trust certificates that aren't on that list.\n";
|
||||
echo "\n";
|
||||
echo "More information on SSL certificate pinning:\n";
|
||||
echo "https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#What_Is_Pinning.3F\n";
|
||||
echo "-----------------------------------------------------------------------------\n";
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static function testMulti($tests)
|
||||
{
|
||||
$anyFailed = false;
|
||||
foreach ($tests as $test) {
|
||||
list($host, $testType) = $test;
|
||||
|
||||
echo " - ".str_pad("$testType ($host) ", 50, ".");
|
||||
$url = "https://$host/";
|
||||
$passed = self::$testType($url);
|
||||
if ($passed) {
|
||||
echo " ok\n";
|
||||
} else {
|
||||
echo " FAILED\n";
|
||||
$anyFailed = true;
|
||||
}
|
||||
}
|
||||
return $anyFailed;
|
||||
}
|
||||
|
||||
private static function testAllowed($url)
|
||||
{
|
||||
$curl = RequestUtil::mkCurl("test-ssl", $url);
|
||||
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||
$curl->exec();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function testUntrustedCert($url)
|
||||
{
|
||||
return self::testDisallowed($url, 'Error executing HTTP request: SSL certificate problem, verify that the CA cert is OK');
|
||||
}
|
||||
|
||||
private static function testHostnameMismatch($url)
|
||||
{
|
||||
return self::testDisallowed($url, 'Error executing HTTP request: SSL certificate problem: Invalid certificate chain');
|
||||
}
|
||||
|
||||
private static function testDisallowed($url, $expectedExceptionMessage)
|
||||
{
|
||||
$curl = RequestUtil::mkCurl("test-ssl", $url);
|
||||
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||
try {
|
||||
$curl->exec();
|
||||
}
|
||||
catch (Exception_NetworkIO $ex) {
|
||||
if (strpos($ex->getMessage(), $expectedExceptionMessage) == 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
12
library/Dropbox/SdkVersion.php
Normal file
12
library/Dropbox/SdkVersion.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SdkVersion {
|
||||
// When doing a release:
|
||||
// 1. Set VERSION to the version you're about to release, tag the relase, then commit+push.
|
||||
// 2. Immediately afterwards, append "-dev", then commit+push.
|
||||
const VERSION = "1.1.6";
|
||||
}
|
67
library/Dropbox/Security.php
Normal file
67
library/Dropbox/Security.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Helper functions for security-related things.
|
||||
*/
|
||||
class Security
|
||||
{
|
||||
/**
|
||||
* A string equality function that compares strings in a way that isn't suceptible to timing
|
||||
* attacks. An attacker can figure out the length of the string, but not the string's value.
|
||||
*
|
||||
* Use this when comparing two strings where:
|
||||
* - one string could be influenced by an attacker
|
||||
* - the other string contains data an attacker shouldn't know
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
* @return bool
|
||||
*/
|
||||
static function stringEquals($a, $b)
|
||||
{
|
||||
// Be strict with arguments. PHP's liberal types could get us pwned.
|
||||
if (func_num_args() !== 2) {
|
||||
throw new \InvalidArgumentException("Expecting 2 args, got ".func_num_args().".");
|
||||
}
|
||||
Checker::argString("a", $a);
|
||||
Checker::argString("b", $b);
|
||||
|
||||
$len = strlen($a);
|
||||
if (strlen($b) !== $len) return false;
|
||||
|
||||
$result = 0;
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$result |= ord($a[$i]) ^ ord($b[$i]);
|
||||
}
|
||||
return $result === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns cryptographically strong secure random bytes (as a PHP string).
|
||||
*
|
||||
* @param int $numBytes
|
||||
* The number of bytes of random data to return.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static function getRandomBytes($numBytes)
|
||||
{
|
||||
Checker::argIntPositive("numBytes", $numBytes);
|
||||
|
||||
// openssl_random_pseudo_bytes had some issues prior to PHP 5.3.4
|
||||
if (function_exists('openssl_random_pseudo_bytes')
|
||||
&& version_compare(PHP_VERSION, '5.3.4') >= 0) {
|
||||
$s = openssl_random_pseudo_bytes($numBytes, $isCryptoStrong);
|
||||
if ($isCryptoStrong) return $s;
|
||||
}
|
||||
|
||||
if (function_exists('mcrypt_create_iv')) {
|
||||
return mcrypt_create_iv($numBytes);
|
||||
}
|
||||
|
||||
// Hopefully the above two options cover all our users. But if not, there are
|
||||
// other platform-specific options we could add.
|
||||
throw new \Exception("no suitable random number source available");
|
||||
}
|
||||
}
|
16
library/Dropbox/StreamReadException.php
Normal file
16
library/Dropbox/StreamReadException.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown when there's an error reading from a stream that was passed in by the caller.
|
||||
*/
|
||||
class StreamReadException extends \Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message, $cause = null)
|
||||
{
|
||||
parent::__construct($message, 0, $cause);
|
||||
}
|
||||
}
|
33
library/Dropbox/Util.php
Normal file
33
library/Dropbox/Util.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
class Util
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function q($object)
|
||||
{
|
||||
return var_export($object, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given string begins with the UTF-8 BOM (byte order mark), remove it and
|
||||
* return whatever is left. Otherwise, return the original string untouched.
|
||||
*
|
||||
* Though it's not recommended for UTF-8 to have a BOM, the standard allows it to
|
||||
* support software that isn't Unicode-aware.
|
||||
*
|
||||
* @param string $string
|
||||
* A UTF-8 encoded string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function stripUtf8Bom($string)
|
||||
{
|
||||
if (\substr_compare($string, "\xEF\xBB\xBF", 0, 3) === 0) {
|
||||
$string = \substr($string, 3);
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
}
|
61
library/Dropbox/ValueStore.php
Normal file
61
library/Dropbox/ValueStore.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* A contract for a class which provides simple get/set/clear access to a single string
|
||||
* value. {@link ArrayEntryStore} provides an implementation of this for storing a value
|
||||
* in a single array element.
|
||||
*
|
||||
* Example implementation for a Memcache-based backing store:
|
||||
*
|
||||
* <code>
|
||||
* class MemcacheValueStore implements ValueStore
|
||||
* {
|
||||
* private $key;
|
||||
* private $memcache;
|
||||
*
|
||||
* function __construct($memcache, $key)
|
||||
* {
|
||||
* $this->memcache = $memcache;
|
||||
* $this->key = $key;
|
||||
* }
|
||||
*
|
||||
* function get()
|
||||
* {
|
||||
* $value = $this->memcache->get($this->getKey());
|
||||
* return $value === false ? null : base64_decode($value);
|
||||
* }
|
||||
*
|
||||
* function set($value)
|
||||
* {
|
||||
* $this->memcache->set($this->key, base64_encode($value));
|
||||
* }
|
||||
*
|
||||
* function clear()
|
||||
* {
|
||||
* $this->memcache->delete($this->key);
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
interface ValueStore
|
||||
{
|
||||
/**
|
||||
* Returns the entry's current value or <code>null</code> if nothing is set.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get();
|
||||
|
||||
/**
|
||||
* Set the entry to the given value.
|
||||
*
|
||||
* @param string $value
|
||||
*/
|
||||
function set($value);
|
||||
|
||||
/**
|
||||
* Remove the value.
|
||||
*/
|
||||
function clear();
|
||||
}
|
278
library/Dropbox/WebAuth.php
Normal file
278
library/Dropbox/WebAuth.php
Normal file
|
@ -0,0 +1,278 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* OAuth 2 "authorization code" flow. (This SDK does not support the "token" flow.)
|
||||
*
|
||||
* Use {@link WebAuth::start()} and {@link WebAuth::finish()} to guide your
|
||||
* user through the process of giving your app access to their Dropbox account.
|
||||
* At the end, you will have an access token, which you can pass to {@link Client}
|
||||
* and start making API calls.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <code>
|
||||
* use \Dropbox as dbx;
|
||||
*
|
||||
* function getWebAuth()
|
||||
* {
|
||||
* $appInfo = dbx\AppInfo::loadFromJsonFile(...);
|
||||
* $clientIdentifier = "my-app/1.0";
|
||||
* $redirectUri = "https://example.org/dropbox-auth-finish";
|
||||
* $csrfTokenStore = new dbx\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
|
||||
* return new dbx\WebAuth($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, ...);
|
||||
* }
|
||||
*
|
||||
* // ----------------------------------------------------------
|
||||
* // In the URL handler for "/dropbox-auth-start"
|
||||
*
|
||||
* $authorizeUrl = getWebAuth()->start();
|
||||
* header("Location: $authorizeUrl");
|
||||
*
|
||||
* // ----------------------------------------------------------
|
||||
* // In the URL handler for "/dropbox-auth-finish"
|
||||
*
|
||||
* try {
|
||||
* list($accessToken, $userId, $urlState) = getWebAuth()->finish($_GET);
|
||||
* assert($urlState === null); // Since we didn't pass anything in start()
|
||||
* }
|
||||
* catch (dbx\WebAuthException_BadRequest $ex) {
|
||||
* error_log("/dropbox-auth-finish: bad request: " . $ex->getMessage());
|
||||
* // Respond with an HTTP 400 and display error page...
|
||||
* }
|
||||
* catch (dbx\WebAuthException_BadState $ex) {
|
||||
* // Auth session expired. Restart the auth process.
|
||||
* header('Location: /dropbox-auth-start');
|
||||
* }
|
||||
* catch (dbx\WebAuthException_Csrf $ex) {
|
||||
* error_log("/dropbox-auth-finish: CSRF mismatch: " . $ex->getMessage());
|
||||
* // Respond with HTTP 403 and display error page...
|
||||
* }
|
||||
* catch (dbx\WebAuthException_NotApproved $ex) {
|
||||
* error_log("/dropbox-auth-finish: not approved: " . $ex->getMessage());
|
||||
* }
|
||||
* catch (dbx\WebAuthException_Provider $ex) {
|
||||
* error_log("/dropbox-auth-finish: error redirect from Dropbox: " . $ex->getMessage());
|
||||
* }
|
||||
* catch (dbx\Exception $ex) {
|
||||
* error_log("/dropbox-auth-finish: error communicating with Dropbox API: " . $ex->getMessage());
|
||||
* }
|
||||
*
|
||||
* // We can now use $accessToken to make API requests.
|
||||
* $client = dbx\Client($accessToken, ...);
|
||||
* </code>
|
||||
*
|
||||
*/
|
||||
class WebAuth extends WebAuthBase
|
||||
{
|
||||
/**
|
||||
* The URI that the Dropbox server will redirect the user to after the user finishes
|
||||
* authorizing your app. This URI must be HTTPS-based and
|
||||
* <a href="https://www.dropbox.com/developers/apps">pre-registered with Dropbox</a>,
|
||||
* though "localhost"-based and "127.0.0.1"-based URIs are allowed without pre-registration
|
||||
* and can be either HTTP or HTTPS.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getRedirectUri() { return $this->redirectUri; }
|
||||
|
||||
/** @var string */
|
||||
private $redirectUri;
|
||||
|
||||
/**
|
||||
* A object that lets us save CSRF token string to the user's session. If you're using the
|
||||
* standard PHP <code>$_SESSION</code>, you can pass in something like
|
||||
* <code>new ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token')</code>.
|
||||
*
|
||||
* If you're not using $_SESSION, you might have to create your own class that provides
|
||||
* the same <code>get()</code>/<code>set()</code>/<code>clear()</code> methods as
|
||||
* {@link ArrayEntryStore}.
|
||||
*
|
||||
* @return ValueStore
|
||||
*/
|
||||
function getCsrfTokenStore() { return $this->csrfTokenStore; }
|
||||
|
||||
/** @var object */
|
||||
private $csrfTokenStore;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AppInfo $appInfo
|
||||
* See {@link getAppInfo()}
|
||||
* @param string $clientIdentifier
|
||||
* See {@link getClientIdentifier()}
|
||||
* @param null|string $redirectUri
|
||||
* See {@link getRedirectUri()}
|
||||
* @param null|ValueStore $csrfTokenStore
|
||||
* See {@link getCsrfTokenStore()}
|
||||
* @param null|string $userLocale
|
||||
* See {@link getUserLocale()}
|
||||
*/
|
||||
function __construct($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, $userLocale = null)
|
||||
{
|
||||
parent::__construct($appInfo, $clientIdentifier, $userLocale);
|
||||
|
||||
Checker::argStringNonEmpty("redirectUri", $redirectUri);
|
||||
|
||||
$this->csrfTokenStore = $csrfTokenStore;
|
||||
$this->redirectUri = $redirectUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the OAuth 2 authorization process, which involves redirecting the user to the
|
||||
* returned authorization URL (a URL on the Dropbox website). When the user then
|
||||
* either approves or denies your app access, Dropbox will redirect them to the
|
||||
* <code>$redirectUri</code> given to constructor, at which point you should
|
||||
* call {@link finish()} to complete the authorization process.
|
||||
*
|
||||
* This function will also save a CSRF token using the <code>$csrfTokenStore</code> given to
|
||||
* the constructor. This CSRF token will be checked on {@link finish()} to prevent
|
||||
* request forgery.
|
||||
*
|
||||
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
|
||||
*
|
||||
* @param string|null $urlState
|
||||
* Any data you would like to keep in the URL through the authorization process.
|
||||
* This exact state will be returned to you by {@link finish()}.
|
||||
*
|
||||
* @param boolean|null $forceReapprove
|
||||
* If a user has already approved your app, Dropbox may skip the "approve" step and
|
||||
* redirect immediately to your callback URL. Setting this to <code>true</code> tells
|
||||
* Dropbox to never skip the "approve" step.
|
||||
*
|
||||
* @return array
|
||||
* The URL to redirect the user to.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function start($urlState = null, $forceReapprove = false)
|
||||
{
|
||||
Checker::argStringOrNull("urlState", $urlState);
|
||||
|
||||
$csrfToken = self::encodeCsrfToken(Security::getRandomBytes(16));
|
||||
$state = $csrfToken;
|
||||
if ($urlState !== null) {
|
||||
$state .= "|";
|
||||
$state .= $urlState;
|
||||
}
|
||||
$this->csrfTokenStore->set($csrfToken);
|
||||
|
||||
return $this->_getAuthorizeUrl($this->redirectUri, $state, $forceReapprove);
|
||||
}
|
||||
|
||||
private static function encodeCsrfToken($string)
|
||||
{
|
||||
return strtr(base64_encode($string), '+/', '-_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this after the user has visited the authorize URL ({@link start()}), approved your app,
|
||||
* and was redirected to your redirect URI.
|
||||
*
|
||||
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
|
||||
*
|
||||
* @param array $queryParams
|
||||
* The query parameters on the GET request to your redirect URI.
|
||||
*
|
||||
* @return array
|
||||
* A <code>list(string $accessToken, string $userId, string $urlState)</code>, where
|
||||
* <code>$accessToken</code> can be used to construct a {@link Client}, <code>$userId</code>
|
||||
* is the user ID of the user's Dropbox account, and <code>$urlState</code> is the
|
||||
* value you originally passed in to {@link start()}.
|
||||
*
|
||||
* @throws Exception
|
||||
* Thrown if there's an error getting the access token from Dropbox.
|
||||
* @throws WebAuthException_BadRequest
|
||||
* @throws WebAuthException_BadState
|
||||
* @throws WebAuthException_Csrf
|
||||
* @throws WebAuthException_NotApproved
|
||||
* @throws WebAuthException_Provider
|
||||
*/
|
||||
function finish($queryParams)
|
||||
{
|
||||
Checker::argArray("queryParams", $queryParams);
|
||||
|
||||
$csrfTokenFromSession = $this->csrfTokenStore->get();
|
||||
Checker::argStringOrNull("this->csrfTokenStore->get()", $csrfTokenFromSession);
|
||||
|
||||
// Check well-formedness of request.
|
||||
|
||||
if (!isset($queryParams['state'])) {
|
||||
throw new WebAuthException_BadRequest("Missing query parameter 'state'.");
|
||||
}
|
||||
$state = $queryParams['state'];
|
||||
Checker::argString("queryParams['state']", $state);
|
||||
|
||||
$error = null;
|
||||
$errorDescription = null;
|
||||
if (isset($queryParams['error'])) {
|
||||
$error = $queryParams['error'];
|
||||
Checker::argString("queryParams['error']", $error);
|
||||
if (isset($queryParams['error_description'])) {
|
||||
$errorDescription = $queryParams['error_description'];
|
||||
Checker::argString("queryParams['error_description']", $errorDescription);
|
||||
}
|
||||
}
|
||||
|
||||
$code = null;
|
||||
if (isset($queryParams['code'])) {
|
||||
$code = $queryParams['code'];
|
||||
Checker::argString("queryParams['code']", $code);
|
||||
}
|
||||
|
||||
if ($code !== null && $error !== null) {
|
||||
throw new WebAuthException_BadRequest("Query parameters 'code' and 'error' are both set;".
|
||||
" only one must be set.");
|
||||
}
|
||||
if ($code === null && $error === null) {
|
||||
throw new WebAuthException_BadRequest("Neither query parameter 'code' or 'error' is set.");
|
||||
}
|
||||
|
||||
// Check CSRF token
|
||||
|
||||
if ($csrfTokenFromSession === null) {
|
||||
throw new WebAuthException_BadState();
|
||||
}
|
||||
|
||||
$splitPos = strpos($state, "|");
|
||||
if ($splitPos === false) {
|
||||
$givenCsrfToken = $state;
|
||||
$urlState = null;
|
||||
} else {
|
||||
$givenCsrfToken = substr($state, 0, $splitPos);
|
||||
$urlState = substr($state, $splitPos + 1);
|
||||
}
|
||||
if (!Security::stringEquals($csrfTokenFromSession, $givenCsrfToken)) {
|
||||
throw new WebAuthException_Csrf("Expected ".Util::q($csrfTokenFromSession) .
|
||||
", got ".Util::q($givenCsrfToken) .".");
|
||||
}
|
||||
$this->csrfTokenStore->clear();
|
||||
|
||||
// Check for error identifier
|
||||
|
||||
if ($error !== null) {
|
||||
if ($error === 'access_denied') {
|
||||
// When the user clicks "Deny".
|
||||
if ($errorDescription === null) {
|
||||
throw new WebAuthException_NotApproved("No additional description from Dropbox.");
|
||||
} else {
|
||||
throw new WebAuthException_NotApproved("Additional description from Dropbox: $errorDescription");
|
||||
}
|
||||
} else {
|
||||
// All other errors.
|
||||
$fullMessage = $error;
|
||||
if ($errorDescription !== null) {
|
||||
$fullMessage .= ": ";
|
||||
$fullMessage .= $errorDescription;
|
||||
}
|
||||
throw new WebAuthException_Provider($fullMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// If everything went ok, make the network call to get an access token.
|
||||
|
||||
list($accessToken, $userId) = $this->_finish($code, $this->redirectUri);
|
||||
return array($accessToken, $userId, $urlState);
|
||||
}
|
||||
}
|
67
library/Dropbox/WebAuthBase.php
Normal file
67
library/Dropbox/WebAuthBase.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The base class for the two auth options.
|
||||
*/
|
||||
class WebAuthBase extends AuthBase
|
||||
{
|
||||
protected function _getAuthorizeUrl($redirectUri, $state, $forceReapprove = false)
|
||||
{
|
||||
if ($forceReapprove === false) {
|
||||
$forceReapprove = null; // Don't include it in the URL if it's the default value.
|
||||
}
|
||||
return RequestUtil::buildUrlForGetOrPut(
|
||||
$this->userLocale,
|
||||
$this->appInfo->getHost()->getWeb(),
|
||||
"1/oauth2/authorize",
|
||||
array(
|
||||
"client_id" => $this->appInfo->getKey(),
|
||||
"response_type" => "code",
|
||||
"redirect_uri" => $redirectUri,
|
||||
"state" => $state,
|
||||
"force_reapprove" => $forceReapprove,
|
||||
));
|
||||
}
|
||||
|
||||
protected function _finish($code, $originalRedirectUri)
|
||||
{
|
||||
// This endpoint requires "Basic" auth.
|
||||
$clientCredentials = $this->appInfo->getKey().":".$this->appInfo->getSecret();
|
||||
$authHeaderValue = "Basic ".base64_encode($clientCredentials);
|
||||
|
||||
$response = RequestUtil::doPostWithSpecificAuth(
|
||||
$this->clientIdentifier, $authHeaderValue, $this->userLocale,
|
||||
$this->appInfo->getHost()->getApi(),
|
||||
"1/oauth2/token",
|
||||
array(
|
||||
"grant_type" => "authorization_code",
|
||||
"code" => $code,
|
||||
"redirect_uri" => $originalRedirectUri,
|
||||
));
|
||||
|
||||
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
|
||||
|
||||
$parts = RequestUtil::parseResponseJson($response->body);
|
||||
|
||||
if (!array_key_exists('token_type', $parts) || !is_string($parts['token_type'])) {
|
||||
throw new Exception_BadResponse("Missing \"token_type\" field.");
|
||||
}
|
||||
$tokenType = $parts['token_type'];
|
||||
if (!array_key_exists('access_token', $parts) || !is_string($parts['access_token'])) {
|
||||
throw new Exception_BadResponse("Missing \"access_token\" field.");
|
||||
}
|
||||
$accessToken = $parts['access_token'];
|
||||
if (!array_key_exists('uid', $parts) || !is_string($parts['uid'])) {
|
||||
throw new Exception_BadResponse("Missing \"uid\" string field.");
|
||||
}
|
||||
$userId = $parts['uid'];
|
||||
|
||||
if ($tokenType !== "Bearer" && $tokenType !== "bearer") {
|
||||
throw new Exception_BadResponse("Unknown \"token_type\"; expecting \"Bearer\", got "
|
||||
.Util::q($tokenType));
|
||||
}
|
||||
|
||||
return array($accessToken, $userId);
|
||||
}
|
||||
}
|
20
library/Dropbox/WebAuthException/BadRequest.php
Normal file
20
library/Dropbox/WebAuthException/BadRequest.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown if the redirect URL was missing parameters or if the given parameters were not valid.
|
||||
*
|
||||
* The recommended action is to show an HTTP 400 error page.
|
||||
*/
|
||||
class WebAuthException_BadRequest extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
19
library/Dropbox/WebAuthException/BadState.php
Normal file
19
library/Dropbox/WebAuthException/BadState.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown if all the parameters are correct, but there's no CSRF token in the session. This
|
||||
* probably means that the session expired.
|
||||
*
|
||||
* The recommended action is to redirect the user's browser to try the approval process again.
|
||||
*/
|
||||
class WebAuthException_BadState extends \Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct("Missing CSRF token in session.");
|
||||
}
|
||||
}
|
21
library/Dropbox/WebAuthException/Csrf.php
Normal file
21
library/Dropbox/WebAuthException/Csrf.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session.
|
||||
* This is blocked to prevent CSRF attacks.
|
||||
*
|
||||
* The recommended action is to respond with an HTTP 403 error page.
|
||||
*/
|
||||
class WebAuthException_Csrf extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
18
library/Dropbox/WebAuthException/NotApproved.php
Normal file
18
library/Dropbox/WebAuthException/NotApproved.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown if the user chose not to grant your app access to their Dropbox account.
|
||||
*/
|
||||
class WebAuthException_NotApproved extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
18
library/Dropbox/WebAuthException/Provider.php
Normal file
18
library/Dropbox/WebAuthException/Provider.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Thrown if Dropbox returns some other error about the authorization request.
|
||||
*/
|
||||
class WebAuthException_Provider extends \Exception
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
82
library/Dropbox/WebAuthNoRedirect.php
Normal file
82
library/Dropbox/WebAuthNoRedirect.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* OAuth 2 code-based authorization for apps that can't provide a redirect URI, typically
|
||||
* command-line example apps.
|
||||
*
|
||||
* Use {@link WebAuth::start()} and {@link WebAuth::getToken()} to guide your
|
||||
* user through the process of giving your app access to their Dropbox account. At the end, you
|
||||
* will have an {@link AccessToken}, which you can pass to {@link Client} and start making
|
||||
* API calls.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <code>
|
||||
* use \Dropbox as dbx;
|
||||
* $appInfo = dbx\AppInfo::loadFromJsonFile(...);
|
||||
* $clientIdentifier = "my-app/1.0";
|
||||
* $webAuth = new dbx\WebAuthNoRedirect($appInfo, $clientIdentifier, ...);
|
||||
*
|
||||
* $authorizeUrl = $webAuth->start();
|
||||
*
|
||||
* print("1. Go to: $authorizeUrl\n");
|
||||
* print("2. Click "Allow" (you might have to log in first).\n");
|
||||
* print("3. Copy the authorization code.\n");
|
||||
* print("Enter the authorization code here: ");
|
||||
* $code = \trim(\fgets(STDIN));
|
||||
*
|
||||
* try {
|
||||
* list($accessToken, $userId) = $webAuth->finish($code);
|
||||
* }
|
||||
* catch (dbx\Exception $ex) {
|
||||
* print("Error communicating with Dropbox API: " . $ex->getMessage() . "\n");
|
||||
* }
|
||||
*
|
||||
* $client = dbx\Client($accessToken, $clientIdentifier, ...);
|
||||
* </code>
|
||||
*/
|
||||
class WebAuthNoRedirect extends WebAuthBase
|
||||
{
|
||||
/**
|
||||
* Returns the URL of the authorization page the user must visit. If the user approves
|
||||
* your app, they will be shown the authorization code on the web page. They will need to
|
||||
* copy/paste that code into your application so your app can pass it to
|
||||
* {@link finish}.
|
||||
*
|
||||
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
|
||||
*
|
||||
* @return string
|
||||
* An authorization URL. Direct the user's browser to this URL. After the user decides
|
||||
* whether to authorize your app or not, Dropbox will show the user an authorization code,
|
||||
* which the user will need to give to your application (e.g. via copy/paste).
|
||||
*/
|
||||
function start()
|
||||
{
|
||||
return $this->_getAuthorizeUrl(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this after the user has visited the authorize URL returned by {@link start()},
|
||||
* approved your app, was presented with an authorization code by Dropbox, and has copy/paste'd
|
||||
* that authorization code into your app.
|
||||
*
|
||||
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
|
||||
*
|
||||
* @param string $code
|
||||
* The authorization code provided to the user by Dropbox.
|
||||
*
|
||||
* @return array
|
||||
* A <code>list(string $accessToken, string $userId)</code>, where
|
||||
* <code>$accessToken</code> can be used to construct a {@link Client} and
|
||||
* <code>$userId</code> is the user ID of the user's Dropbox account.
|
||||
*
|
||||
* @throws Exception
|
||||
* Thrown if there's an error getting the access token from Dropbox.
|
||||
*/
|
||||
function finish($code)
|
||||
{
|
||||
Checker::argStringNonEmpty("code", $code);
|
||||
return $this->_finish($code, null);
|
||||
}
|
||||
}
|
116
library/Dropbox/WriteMode.php
Normal file
116
library/Dropbox/WriteMode.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* Describes how a file should be saved when it is written to Dropbox.
|
||||
*/
|
||||
final class WriteMode
|
||||
{
|
||||
/**
|
||||
* The URL parameters to pass to the file uploading endpoint to achieve the
|
||||
* desired write mode.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $extraParams;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private function __construct($extraParams)
|
||||
{
|
||||
$this->extraParams = $extraParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function getExtraParams()
|
||||
{
|
||||
return $this->extraParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link WriteMode} for adding a new file. If a file at the specified path already
|
||||
* exists, the new file will be renamed automatically.
|
||||
*
|
||||
* For example, if you're trying to upload a file to "/Notes/Groceries.txt", but there's
|
||||
* already a file there, your file will be written to "/Notes/Groceries (1).txt".
|
||||
*
|
||||
* You can determine whether your file was renamed by checking the "path" field of the
|
||||
* metadata object returned by the API call.
|
||||
*
|
||||
* @return WriteMode
|
||||
*/
|
||||
static function add()
|
||||
{
|
||||
if (self::$addInstance === null) {
|
||||
self::$addInstance = new WriteMode(array("overwrite" => "false"));
|
||||
}
|
||||
return self::$addInstance;
|
||||
}
|
||||
private static $addInstance = null;
|
||||
|
||||
/**
|
||||
* Returns a {@link WriteMode} for forcing a file to be at a certain path. If there's already
|
||||
* a file at that path, the existing file will be overwritten. If there's a folder at that
|
||||
* path, however, it will not be overwritten and the API call will fail.
|
||||
*
|
||||
* @return WriteMode
|
||||
*/
|
||||
static function force()
|
||||
{
|
||||
if (self::$forceInstance === null) {
|
||||
self::$forceInstance = new WriteMode(array("overwrite" => "true"));
|
||||
}
|
||||
return self::$forceInstance;
|
||||
}
|
||||
private static $forceInstance = null;
|
||||
|
||||
/**
|
||||
* Returns a {@link WriteMode} for updating an existing file. This is useful for when you
|
||||
* have downloaded a file, made modifications, and want to save your modifications back to
|
||||
* Dropbox. You need to specify the revision of the copy of the file you downloaded (it's
|
||||
* the "rev" parameter of the file's metadata object).
|
||||
*
|
||||
* If, when you attempt to save, the revision of the file currently on Dropbox matches
|
||||
* $revToReplace, the file on Dropbox will be overwritten with the new contents you provide.
|
||||
*
|
||||
* If the revision of the file currently on Dropbox doesn't match $revToReplace, Dropbox will
|
||||
* create a new file and save your contents to that file. For example, if the original file
|
||||
* path is "/Notes/Groceries.txt", the new file's path might be
|
||||
* "/Notes/Groceries (conflicted copy).txt".
|
||||
*
|
||||
* You can determine whether your file was renamed by checking the "path" field of the
|
||||
* metadata object returned by the API call.
|
||||
*
|
||||
* @param string $revToReplace
|
||||
* @return WriteMode
|
||||
*/
|
||||
static function update($revToReplace)
|
||||
{
|
||||
return new WriteMode(array("parent_rev" => $revToReplace));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a function argument is of type <code>WriteMode</code>.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArg($argName, $argValue)
|
||||
{
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a function argument is either <code>null</code> or of type
|
||||
* <code>WriteMode</code>.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static function checkArgOrNull($argName, $argValue)
|
||||
{
|
||||
if ($argValue === null) return;
|
||||
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
|
||||
}
|
||||
}
|
31
library/Dropbox/autoload.php
Normal file
31
library/Dropbox/autoload.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
// The Dropbox SDK autoloader. You probably shouldn't be using this. Instead,
|
||||
// use a global autoloader, like the Composer autoloader.
|
||||
//
|
||||
// But if you really don't want to use a global autoloader, do this:
|
||||
//
|
||||
// require_once "<path-to-here>/Dropbox/autoload.php"
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function autoload($name)
|
||||
{
|
||||
// If the name doesn't start with "Dropbox\", then its not once of our classes.
|
||||
if (\substr_compare($name, "Dropbox\\", 0, 8) !== 0) return;
|
||||
|
||||
// Take the "Dropbox\" prefix off.
|
||||
$stem = \substr($name, 8);
|
||||
|
||||
// Convert "\" and "_" to path separators.
|
||||
$pathified_stem = \str_replace(array("\\", "_"), '/', $stem);
|
||||
|
||||
$path = __DIR__ . "/" . $pathified_stem . ".php";
|
||||
if (\is_file($path)) {
|
||||
require_once $path;
|
||||
}
|
||||
}
|
||||
|
||||
\spl_autoload_register('Dropbox\autoload');
|
1396
library/Dropbox/certs/trusted-certs.crt
Normal file
1396
library/Dropbox/certs/trusted-certs.crt
Normal file
File diff suppressed because it is too large
Load diff
13
library/Dropbox/strict.php
Normal file
13
library/Dropbox/strict.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// Throw exceptions on all PHP errors/warnings/notices.
|
||||
// We'd like to do this in all situations (and not just when running tests), but
|
||||
// this is a global setting and other code might not be ready for it.
|
||||
/** @internal */
|
||||
function error_to_exception($errno, $errstr, $errfile, $errline, $context)
|
||||
{
|
||||
// If the error is being suppressed with '@', don't throw an exception.
|
||||
if (error_reporting() === 0) return;
|
||||
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
}
|
||||
set_error_handler('error_to_exception');
|
|
@ -9,7 +9,7 @@
|
|||
* WP Static HTML Output Plugin
|
||||
*/
|
||||
class StaticHtmlOutput {
|
||||
const VERSION = '1.2.2';
|
||||
const VERSION = '1.4';
|
||||
const OPTIONS_KEY = 'wp-static-html-output-options';
|
||||
|
||||
/**
|
||||
|
@ -116,6 +116,7 @@ class StaticHtmlOutput {
|
|||
$uploadsFolderWritable = $uploadDir && is_writable($uploadDir['path']);
|
||||
$supportsZipArchives = extension_loaded('zip');
|
||||
$permalinksStructureDefined = strlen(get_option('permalink_structure'));
|
||||
|
||||
|
||||
if (!$uploadsFolderWritable || !$supportsZipArchives || !$permalinksStructureDefined) {
|
||||
$this->_view
|
||||
|
@ -300,6 +301,39 @@ class StaticHtmlOutput {
|
|||
$manager->transfer();
|
||||
}
|
||||
|
||||
if(filter_input(INPUT_POST, 'sendViaDropbox') == 1) {
|
||||
require_once(__DIR__.'/Dropbox/autoload.php');
|
||||
|
||||
// will exclude the siteroot when copying
|
||||
$siteroot = $archiveName . '/';
|
||||
$dropboxAccessToken = filter_input(INPUT_POST, 'dropboxAccessToken');
|
||||
$dropboxFolder = filter_input(INPUT_POST, 'dropboxFolder');
|
||||
|
||||
$dbxClient = new Dropbox\Client($dropboxAccesstoken, "PHP-Example/1.0");
|
||||
|
||||
function FolderToDropbox($dir, $dbxClient, $siteroot, $dropboxFolder){
|
||||
$files = scandir($dir);
|
||||
foreach($files as $item){
|
||||
if($item != '.' && $item != '..'){
|
||||
if(is_dir($dir.'/'.$item)) {
|
||||
FolderToDropbox($dir.$item, $dbxClient, $siteroot, $dropboxFolder);
|
||||
} else if(is_file($dir.'/'.$item)) {
|
||||
$clean_dir = str_replace($siteroot, '', $dir.'/'.$item);
|
||||
|
||||
$targetPath = '/'.$dropboxFolder.'/'.$clean_dir;
|
||||
$f = fopen($dir.'/'.$item, "rb");
|
||||
|
||||
$result = $dbxClient->uploadFile($targetPath, Dropbox\WriteMode::add(), $f);
|
||||
fclose($f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FolderToDropbox($siteroot, $dbxClient, $siteroot, $dropboxFolder);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO: keep copy of last export folder for incremental addition
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
PROJECT_ROOT=$(pwd)
|
||||
SVN_ROOT=/home/leon/svnplugindir
|
||||
NEW_TAG=1.2.2
|
||||
NEW_TAG=1.4
|
||||
|
||||
# run from project root
|
||||
|
||||
|
|
22
readme.txt
22
readme.txt
|
@ -4,7 +4,7 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_i
|
|||
Tags: static,html,export,performance,security,portable
|
||||
Requires at least: 3.2
|
||||
Tested up to: 4.7.3
|
||||
Stable tag: 1.2.2
|
||||
Stable tag: 1.4
|
||||
|
||||
Allows you to leverage WordPress as a great CMS, but benefit from the speed, security and portability that a static website provides.
|
||||
|
||||
|
@ -84,6 +84,15 @@ See the readme. In brief: you can't use dynamic WordPress functions such as comm
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 1.4 =
|
||||
|
||||
* add Dropbox export option
|
||||
* fix bug some users encountered with 1.3 release
|
||||
|
||||
= 1.3 =
|
||||
|
||||
* reduce plugin download size
|
||||
|
||||
= 1.2.2 =
|
||||
|
||||
* supports Amazon Web Service's S3 as an export option
|
||||
|
@ -174,6 +183,17 @@ Initial release to Wordpress community
|
|||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.4 =
|
||||
|
||||
* add Dropbox export option
|
||||
* fix bug some users encountered with 1.3 release
|
||||
|
||||
= 1.3 =
|
||||
|
||||
From this update on, will only do major point increases, ie 1.3, 1.4, vs 1.3.1, 1.3.2. This is due to way WP plugin directory only reports usage stats across major version numbers.
|
||||
|
||||
* reduce plugin download size
|
||||
|
||||
= 1.2.2 =
|
||||
|
||||
* supports Amazon Web Service's S3 as an export option
|
||||
|
|
|
@ -153,6 +153,9 @@ jQuery(document).ready(function($){
|
|||
$(targetExportSettingsBlock).find('#s3Secret').first().val(decodeURIComponent(settingsBlock.s3Secret));
|
||||
$(targetExportSettingsBlock).find('#s3Region').first().val(decodeURIComponent(settingsBlock.s3Region));
|
||||
$(targetExportSettingsBlock).find('#s3Bucket').first().val(decodeURIComponent(settingsBlock.s3Bucket));
|
||||
$(targetExportSettingsBlock).find('#sendViaDropbox')[0].checked = settingsBlock.sendViaDropbox;
|
||||
$(targetExportSettingsBlock).find('#dropboxAccessToken').first().val(decodeURIComponent(settingsBlock.dropboxAccessToken));
|
||||
$(targetExportSettingsBlock).find('#dropboxFolder').first().val(decodeURIComponent(settingsBlock.dropboxFolder));
|
||||
|
||||
|
||||
// if there are more to come, clone and set target
|
||||
|
@ -173,6 +176,9 @@ jQuery(document).ready(function($){
|
|||
$(targetExportSettingsBlock).find('#s3Secret').val('');
|
||||
$(targetExportSettingsBlock).find('#s3Region').val('');
|
||||
$(targetExportSettingsBlock).find('#s3Bucket').val('');
|
||||
$(targetExportSettingsBlock).find('#sendViaDropbox').first().prop('checked', false);
|
||||
$(targetExportSettingsBlock).find('#dropboxAccessToken').val('');
|
||||
$(targetExportSettingsBlock).find('#dropboxFolder').val('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -286,6 +292,22 @@ jQuery(document).ready(function($){
|
|||
|
||||
<input name="s3Bucket" class="regular-text" id="s3Bucket" value="<?php echo esc_attr($this->s3Bucket) ?>" placeholder="<?= __('S3 Bucket name', 'static-html-output-plugin');?>" />
|
||||
<span class="description">ie, my-static-site</span>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<fieldset>
|
||||
<label for="sendViaDropbox">
|
||||
<input name="sendViaDropbox" id="sendViaDropbox" value="1" type="checkbox" <?php if ($this->sendViaDropbox == 1) echo "checked"; ?> />
|
||||
<span><?= __('Transfer files via Dropbox', 'static-html-output-plugin');?></span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<input name="dropboxAccessToken" class="regular-text" id="dropboxAccessToken" value="<?php echo esc_attr($this->dropboxAccessToken) ?>" placeholder="<?= __('Dropbox access token', 'static-html-output-plugin');?>" />
|
||||
<span class="description"><a href="https://blogs.dropbox.com/developers/2014/05/generate-an-access-token-for-your-own-account/" target="_blank">How do I get this?</a></span>
|
||||
<br>
|
||||
|
||||
<input name="dropboxFolder" class="regular-text" id="dropboxFolder" value="<?php echo esc_attr($this->dropboxFolder) ?>" placeholder="<?= __('Dropbox folder', 'static-html-output-plugin');?>" />
|
||||
<span class="description">ie, where you want this to appear in your Dropbox account</span>
|
||||
<br>
|
||||
|
||||
<p class="submit">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Plugin Name: WP Static HTML Output
|
||||
Plugin URI: https://leonstafford.github.io
|
||||
Description: Benefit from WordPress as a CMS but with the speed, performance and portability of a static site
|
||||
Version: 1.2.2
|
||||
Version: 1.4
|
||||
Author: Leon Stafford
|
||||
Author URI: https://leonstafford.github.io
|
||||
Text Domain: static-html-output-plugin
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue