mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 09:49:30 +02:00
Add Dropbox catalog
Fix Google Music streaming
This commit is contained in:
parent
305b9294ce
commit
0461f2218f
47 changed files with 4747 additions and 267 deletions
|
@ -47,7 +47,7 @@ switch ($_REQUEST['action']) {
|
|||
if ($_REQUEST['catalogs'] ) {
|
||||
foreach ($_REQUEST['catalogs'] as $catalog_id) {
|
||||
$catalog = Catalog::create_from_id($catalog_id);
|
||||
$catalog->add_to_catalog();
|
||||
$catalog->add_to_catalog($_POST);
|
||||
}
|
||||
}
|
||||
$url = Config::get('web_path') . '/admin/catalog.php';
|
||||
|
@ -180,7 +180,7 @@ switch ($_REQUEST['action']) {
|
|||
if ($_POST['add_path'] != '/' AND strlen($_POST['add_path'])) {
|
||||
if ($catalog_id = Catalog_local::get_from_path($_POST['add_path'])) {
|
||||
$catalog = Catalog::create_from_id($catalog_id);
|
||||
$catalog->run_add(array('subdirectory'=>$_POST['add_path']));
|
||||
$catalog->add_to_catalog(array('subdirectory'=>$_POST['add_path']));
|
||||
}
|
||||
} // end if add
|
||||
|
||||
|
@ -226,7 +226,7 @@ switch ($_REQUEST['action']) {
|
|||
$catalog = Catalog::create_from_id($catalog_id);
|
||||
|
||||
// Run our initial add
|
||||
$catalog->run_add($_POST);
|
||||
$catalog->add_to_catalog($_POST);
|
||||
|
||||
UI::show_box_top(T_('Catalog Created'), 'box box_catalog_created');
|
||||
echo "<h2>" . T_('Catalog Created') . "</h2>";
|
||||
|
|
|
@ -52,17 +52,32 @@ abstract class Catalog extends database_object {
|
|||
abstract public function get_type();
|
||||
abstract public function get_description();
|
||||
abstract public function get_version();
|
||||
abstract public function get_create_help();
|
||||
abstract public function is_installed();
|
||||
abstract public function install();
|
||||
abstract public function uninstall();
|
||||
abstract public function run_add($options);
|
||||
abstract public function add_to_catalog();
|
||||
abstract public function add_to_catalog($options = null);
|
||||
abstract public function verify_catalog_proc();
|
||||
abstract public function clean_catalog_proc();
|
||||
abstract public function catalog_fields();
|
||||
abstract public function get_rel_path($file_path);
|
||||
abstract public function prepare_media($media);
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
* This removes the remote catalog
|
||||
*/
|
||||
public function uninstall() {
|
||||
|
||||
$sql = "DELETE FROM `catalog` WHERE `catalog_type` = ?";
|
||||
$db_results = Dba::query($sql, array($this->get_type()));
|
||||
|
||||
$sql = "DROP TABLE `catalog_" . $this->get_type() ."`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return true;
|
||||
|
||||
} // uninstall
|
||||
|
||||
public static function create_from_id($id) {
|
||||
|
||||
$sql = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?';
|
||||
|
@ -120,10 +135,25 @@ abstract class Catalog extends database_object {
|
|||
$seltypes .= '<option value="' . $type . '">' . $type . '</option>';
|
||||
echo "type_fields['" . $type . "'] = \"";
|
||||
$fields = $catalog->catalog_fields();
|
||||
$help = $catalog->get_create_help();
|
||||
if (!empty($help)) {
|
||||
echo "<tr><td></td><td>" . $help . "</td></tr>";
|
||||
}
|
||||
foreach ($fields as $key=>$field) {
|
||||
echo "<tr><td style='width: 25%;'>" . $field['description'] . ":</td><td><input type='";
|
||||
echo ($field['type'] == 'password') ? 'password' : 'text';
|
||||
echo "' size='60' name='" . $key . "' /></td></tr>";
|
||||
echo "<tr><td style='width: 25%;'>" . $field['description'] . ":</td><td>";
|
||||
|
||||
switch ($field['type']) {
|
||||
case 'checkbox':
|
||||
echo "<input type='checkbox' size='10' name='" . $key . "' value='1' " . (($field['value']) ? 'checked' : '') . "/>";
|
||||
break;
|
||||
case 'password':
|
||||
echo "<input type='password' size='60' name='" . $key . "' value='" . $field['value'] . "' />";
|
||||
break;
|
||||
default:
|
||||
echo "<input type='text' size='60' name='" . $key . "' value='" . $field['value'] . "' />";
|
||||
break;
|
||||
}
|
||||
echo "</td></tr>";
|
||||
}
|
||||
echo "\";";
|
||||
}
|
||||
|
@ -170,6 +200,22 @@ abstract class Catalog extends database_object {
|
|||
|
||||
} // get_catalog_types
|
||||
|
||||
public static function is_audio_file($file) {
|
||||
$pattern = "/\.(" . Config::get('catalog_file_pattern');
|
||||
if ($options['parse_m3u']) {
|
||||
$pattern .= "|m3u)$/i";
|
||||
}
|
||||
else {
|
||||
$pattern .= ")$/i";
|
||||
}
|
||||
return preg_match($pattern, $file);
|
||||
}
|
||||
|
||||
public static function is_video_file($file) {
|
||||
$video_pattern = "/\.(" . Config::get('catalog_video_pattern') . ")$/i";
|
||||
return preg_match($video_pattern, $file);
|
||||
}
|
||||
|
||||
public function get_info($id, $table = 'catalog') {
|
||||
$info = parent::get_info($id, $table);
|
||||
|
||||
|
|
|
@ -47,6 +47,8 @@ class Core {
|
|||
* more than we need.
|
||||
*/
|
||||
public static function autoload($class) {
|
||||
// Ignore class with namespace, not used by Ampache
|
||||
if (strpos($class, '\\') === false) {
|
||||
$file = Config::get('prefix') . '/lib/class/' .
|
||||
strtolower($class) . '.class.php';
|
||||
|
||||
|
@ -63,6 +65,7 @@ class Core {
|
|||
debug_event('autoload', "'$class' not found!", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* form_register
|
||||
|
|
|
@ -38,6 +38,7 @@ class vainfo {
|
|||
|
||||
protected $_raw = array();
|
||||
protected $_getID3 = '';
|
||||
protected $_forcedSize = 0;
|
||||
|
||||
protected $_file_encoding = '';
|
||||
protected $_file_pattern = '';
|
||||
|
@ -52,8 +53,9 @@ class vainfo {
|
|||
* This function just sets up the class, it doesn't pull the information.
|
||||
*
|
||||
*/
|
||||
public function __construct($file, $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern, $file_pattern) {
|
||||
public function __construct($file, $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern = '', $file_pattern ='', $islocal = true) {
|
||||
|
||||
$this->islocal = $islocal;
|
||||
$this->filename = $file;
|
||||
$this->encoding = $encoding ?: Config::get('site_charset');
|
||||
|
||||
|
@ -71,6 +73,7 @@ class vainfo {
|
|||
}
|
||||
$this->_pathinfo['extension'] = strtolower($this->_pathinfo['extension']);
|
||||
|
||||
if ($this->islocal) {
|
||||
// Initialize getID3 engine
|
||||
$this->_getID3 = new getID3();
|
||||
|
||||
|
@ -131,6 +134,11 @@ class vainfo {
|
|||
|
||||
$this->_getID3->encoding_id3v1 = $this->encoding_id3v1;
|
||||
}
|
||||
}
|
||||
|
||||
public function forceSize($size) {
|
||||
$this->_forcedSize = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* _detect_encoding
|
||||
|
@ -180,12 +188,14 @@ class vainfo {
|
|||
return true;
|
||||
}
|
||||
|
||||
if ($this->islocal) {
|
||||
try {
|
||||
$this->_raw = $this->_getID3->analyze($this->filename);
|
||||
}
|
||||
catch (Exception $error) {
|
||||
debug_event('getID2', 'Unable to catalog file: ' . $error->message, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Figure out what type of file we are dealing with */
|
||||
$this->type = $this->_get_type();
|
||||
|
@ -196,7 +206,7 @@ class vainfo {
|
|||
$this->tags['filename'] = $this->_parse_filename($this->filename);
|
||||
}
|
||||
|
||||
if (in_array('getID3', $enabled_sources)) {
|
||||
if (in_array('getID3', $enabled_sources) && $this->islocal) {
|
||||
$this->tags['getID3'] = $this->_get_tags();
|
||||
}
|
||||
|
||||
|
@ -460,10 +470,10 @@ class vainfo {
|
|||
$parsed['bitrate'] = $tags['audio']['bitrate'];
|
||||
$parsed['channels'] = intval($tags['audio']['channels']);
|
||||
$parsed['rate'] = intval($tags['audio']['sample_rate']);
|
||||
$parsed['size'] = intval($tags['filesize']);
|
||||
$parsed['size'] = $this->_forcedSize ?: intval($tags['filesize']);
|
||||
$parsed['encoding'] = $tags['encoding'];
|
||||
$parsed['mime'] = $tags['mime_type'];
|
||||
$parsed['time'] = $tags['playtime_seconds'];
|
||||
$parsed['time'] = ($this->_forcedSize ? ((($this->_forcedSize - $tags['avdataoffset']) * 8) / $tags['bitrate']) : $tags['playtime_seconds']);
|
||||
$parsed['video_codec'] = $tags['video']['fourcc'];
|
||||
$parsed['audio_codec'] = $tags['audio']['dataformat'];
|
||||
$parsed['resolution_x'] = $tags['video']['resolution_x'];
|
||||
|
@ -725,6 +735,7 @@ class vainfo {
|
|||
*/
|
||||
private function _parse_filename($filename) {
|
||||
|
||||
$origin = $filename;
|
||||
$results = array();
|
||||
|
||||
// Correctly detect the slash we need to use here
|
||||
|
@ -738,6 +749,13 @@ class vainfo {
|
|||
// Combine the patterns
|
||||
$pattern = preg_quote($this->_dir_pattern) . $slash_type . preg_quote($this->_file_pattern);
|
||||
|
||||
// Remove first left directories from filename to match pattern
|
||||
$cntslash = substr_count($pattern, $slash_type) + 1;
|
||||
$filepart = explode($slash_type, $filename);
|
||||
if (count($filepart) > $cntslash) {
|
||||
$filename = implode($slash_type, array_slice($filepart, count($filepart) - $cntslash));
|
||||
}
|
||||
|
||||
// Pull out the pattern codes into an array
|
||||
preg_match_all('/\%\w/', $pattern, $elements);
|
||||
|
||||
|
@ -764,7 +782,9 @@ class vainfo {
|
|||
}
|
||||
|
||||
$results['title'] = $results['title'] ?: basename($filename);
|
||||
$results['size'] = filesize($filename);
|
||||
if ($this->islocal) {
|
||||
$results['size'] = filesize($origin);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
|
238
modules/Dropbox/AppInfo.php
Normal file
238
modules/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 = file_get_contents($path);
|
||||
$jsonArr = json_decode($str, TRUE);
|
||||
|
||||
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
modules/Dropbox/AppInfoLoadException.php
Normal file
18
modules/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);
|
||||
}
|
||||
}
|
58
modules/Dropbox/ArrayEntryStore.php
Normal file
58
modules/Dropbox/ArrayEntryStore.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* A class that gives get/put/clear access to a single entry in an array.
|
||||
*/
|
||||
class ArrayEntryStore implements ValueStore
|
||||
{
|
||||
private $array;
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $array
|
||||
* The array that we'll be accessing.
|
||||
*
|
||||
* @param object $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]);
|
||||
}
|
||||
}
|
85
modules/Dropbox/AuthInfo.php
Normal file
85
modules/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 = file_get_contents($path);
|
||||
$jsonArr = json_decode($str, TRUE);
|
||||
|
||||
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
modules/Dropbox/AuthInfoLoadException.php
Normal file
18
modules/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
modules/Dropbox/Checker.php
Normal file
94
modules/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)");
|
||||
}
|
||||
}
|
1366
modules/Dropbox/Client.php
Normal file
1366
modules/Dropbox/Client.php
Normal file
File diff suppressed because it is too large
Load diff
92
modules/Dropbox/Curl.php
Normal file
92
modules/Dropbox/Curl.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* A minimal wrapper around a cURL handle.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Curl
|
||||
{
|
||||
/** @var resource */
|
||||
public $handle;
|
||||
|
||||
public $maxsize;
|
||||
|
||||
/** @var string[] */
|
||||
private $headers = array();
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*/
|
||||
function __construct($url, $maxsize = 0)
|
||||
{
|
||||
$this->maxsize = $maxsize;
|
||||
|
||||
// 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);
|
||||
|
||||
// Force SSL and use our own certificate list.
|
||||
$this->set(CURLOPT_SSL_VERIFYPEER, true);
|
||||
$this->set(CURLOPT_SSL_VERIFYHOST, 2);
|
||||
$this->set(CURLOPT_SSLVERSION, 3); // Force SSL v3.
|
||||
$this->set(CURLOPT_CAINFO, __DIR__."/trusted-certs.crt");
|
||||
|
||||
if ($this->maxsize > 0) {
|
||||
// Check if we get excepted minimum length
|
||||
$this->set(CURLOPT_BUFFERSIZE, 1024);
|
||||
$this->set(CURLOPT_NOPROGRESS, false);
|
||||
$this->set(CURLOPT_PROGRESSFUNCTION, array($this, 'progressChunk'));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
function progressChunk($downloadSize, $downloaded, $uploadSize, $uploaded) {
|
||||
return ($this->maxsize > 0 && $downloaded >= $this->maxsize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
if ($this->maxsize == 0 || curl_errno($this->handle) != CURLE_ABORTED_BY_CALLBACK) {
|
||||
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);
|
||||
}
|
||||
}
|
47
modules/Dropbox/CurlStreamRelay.php
Normal file
47
modules/Dropbox/CurlStreamRelay.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?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);
|
||||
ob_flush();
|
||||
}
|
||||
|
||||
return strlen($data);
|
||||
}
|
||||
|
||||
function getErrorBody()
|
||||
{
|
||||
return implode($this->errorData);
|
||||
}
|
||||
}
|
||||
|
19
modules/Dropbox/DeserializeException.php
Normal file
19
modules/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
modules/Dropbox/DropboxMetadataHeaderCatcher.php
Normal file
83
modules/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
modules/Dropbox/Exception.php
Normal file
16
modules/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
modules/Dropbox/Exception/BadRequest.php
Normal file
17
modules/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
modules/Dropbox/Exception/BadResponse.php
Normal file
17
modules/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
modules/Dropbox/Exception/BadResponseCode.php
Normal file
33
modules/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
modules/Dropbox/Exception/InvalidAccessToken.php
Normal file
18
modules/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
modules/Dropbox/Exception/NetworkIO.php
Normal file
16
modules/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);
|
||||
}
|
||||
}
|
17
modules/Dropbox/Exception/ProtocolError.php
Normal file
17
modules/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
modules/Dropbox/Exception/RetryLater.php
Normal file
17
modules/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
modules/Dropbox/Exception/ServerError.php
Normal file
15
modules/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
modules/Dropbox/Host.php
Normal file
99
modules/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
modules/Dropbox/HttpResponse.php
Normal file
17
modules/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;
|
||||
}
|
||||
}
|
158
modules/Dropbox/Path.php
Normal file
158
modules/Dropbox/Path.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?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 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));
|
||||
}
|
||||
}
|
252
modules/Dropbox/RequestUtil.php
Normal file
252
modules/Dropbox/RequestUtil.php
Normal file
|
@ -0,0 +1,252 @@
|
|||
<?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 (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 buildUrl($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 mkCurlWithoutAuth($clientIdentifier, $url, $maxsize=0)
|
||||
{
|
||||
$curl = new Curl($url, $maxsize);
|
||||
|
||||
$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");
|
||||
|
||||
return $curl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientIdentifier
|
||||
* @param string $url
|
||||
* @param string $accessToken
|
||||
* @return Curl
|
||||
*/
|
||||
static function mkCurl($clientIdentifier, $url, $accessToken, $maxsize=0)
|
||||
{
|
||||
$curl = self::mkCurlWithoutAuth($clientIdentifier, $url, $maxsize);
|
||||
$curl->addHeader("Authorization: Bearer $accessToken");
|
||||
return $curl;
|
||||
}
|
||||
|
||||
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 $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::mkCurl($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 $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::buildUrl($userLocale, $host, $path, $params);
|
||||
|
||||
$curl = self::mkCurl($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);
|
||||
|
||||
return new Exception_BadResponse("Unexpected $message");
|
||||
}
|
||||
|
||||
/**
|
||||
* @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");
|
||||
}
|
||||
|
||||
}
|
65
modules/Dropbox/Security.php
Normal file
65
modules/Dropbox/Security.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?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 \InvalidArgumentException("Expecting 2 args, got ".func_num_args().".");
|
||||
}
|
||||
Checker::argString("a", $a);
|
||||
Checker::argString("b", $b);
|
||||
|
||||
if (strlen($a) !== strlen($b)) return false;
|
||||
$result = 0;
|
||||
for ($i = 0; $i < strlen($a); $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.
|
||||
assert(False, "no suitable random number source available");
|
||||
}
|
||||
}
|
16
modules/Dropbox/StreamReadException.php
Normal file
16
modules/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);
|
||||
}
|
||||
}
|
61
modules/Dropbox/ValueStore.php
Normal file
61
modules/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();
|
||||
}
|
276
modules/Dropbox/WebAuth.php
Normal file
276
modules/Dropbox/WebAuth.php
Normal file
|
@ -0,0 +1,276 @@
|
|||
<?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.
|
||||
*
|
||||
* @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()}.
|
||||
*
|
||||
* @return array
|
||||
* The URL to redirect the user to.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function start($urlState = null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private static function encodeCsrfToken($string)
|
||||
{
|
||||
return strtr(base64_encode($string), '+/', '-_');
|
||||
}
|
||||
|
||||
private static function decodeCsrfToken($string)
|
||||
{
|
||||
return base64_decode(strtr($string, '-_', '+/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this after the user has visited the authorize URL ({@link start()}), approved your app,
|
||||
* and was redirected to your redirect URI.
|
||||
*
|
||||
* @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;
|
||||
if (isset($queryParams['error'])) {
|
||||
$error = $queryParams['error'];
|
||||
Checker::argString("queryParams['error']", $error);
|
||||
$errorDescription = null;
|
||||
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();
|
||||
}
|
||||
// Sanity check to make sure something hasn't gone terribly wrong.
|
||||
assert(strlen($csrfTokenFromSession) > 20);
|
||||
|
||||
$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 ".Client::q($csrfTokenFromSession).
|
||||
", got ".Client::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);
|
||||
}
|
||||
}
|
136
modules/Dropbox/WebAuthBase.php
Normal file
136
modules/Dropbox/WebAuthBase.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
namespace Dropbox;
|
||||
|
||||
/**
|
||||
* The base class for the two auth options.
|
||||
*/
|
||||
class WebAuthBase
|
||||
{
|
||||
/**
|
||||
* Whatever AppInfo was passed into the constructor.
|
||||
*
|
||||
* @return AppInfo
|
||||
*/
|
||||
function getAppInfo() { return $this->appInfo; }
|
||||
|
||||
/** @var AppInfo */
|
||||
private $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 */
|
||||
private $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 */
|
||||
private $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);
|
||||
Checker::argStringNonEmpty("clientIdentifier", $clientIdentifier);
|
||||
Checker::argStringNonEmptyOrNull("userLocale", $userLocale);
|
||||
|
||||
$this->appInfo = $appInfo;
|
||||
$this->clientIdentifier = $clientIdentifier;
|
||||
$this->userLocale = $userLocale;
|
||||
}
|
||||
|
||||
protected function _getAuthorizeUrl($redirectUri, $state)
|
||||
{
|
||||
return RequestUtil::buildUrl(
|
||||
$this->userLocale,
|
||||
$this->appInfo->getHost()->getWeb(),
|
||||
"1/oauth2/authorize",
|
||||
array(
|
||||
"client_id" => $this->appInfo->getKey(),
|
||||
"response_type" => "code",
|
||||
"redirect_uri" => $redirectUri,
|
||||
"state" => $state,
|
||||
));
|
||||
}
|
||||
|
||||
protected function _finish($code, $originalRedirectUri)
|
||||
{
|
||||
$url = RequestUtil::buildUri($this->appInfo->getHost()->getApi(), "1/oauth2/token");
|
||||
$params = array(
|
||||
"grant_type" => "authorization_code",
|
||||
"code" => $code,
|
||||
"redirect_uri" => $originalRedirectUri,
|
||||
"locale" => $this->userLocale,
|
||||
);
|
||||
|
||||
$curl = RequestUtil::mkCurlWithoutAuth($this->clientIdentifier, $url);
|
||||
|
||||
// Add Basic auth header.
|
||||
$basic_auth = $this->appInfo->getKey() . ":" . $this->appInfo->getSecret();
|
||||
$curl->addHeader("Authorization: Basic ".base64_encode($basic_auth));
|
||||
|
||||
$curl->set(CURLOPT_POST, true);
|
||||
$curl->set(CURLOPT_POSTFIELDS, RequestUtil::buildPostBody($params));
|
||||
|
||||
$curl->set(CURLOPT_RETURNTRANSFER, true);
|
||||
$response = $curl->exec();
|
||||
|
||||
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
|
||||
|
||||
$parts = RequestUtil::parseResponseJson($response->body);
|
||||
|
||||
if (!array_key_exists('token_type', $parts) or !is_string($parts['token_type'])) {
|
||||
throw new Exception_BadResponse("Missing \"token_type\" field.");
|
||||
}
|
||||
$tokenType = $parts['token_type'];
|
||||
if (!array_key_exists('access_token', $parts) or !is_string($parts['access_token'])) {
|
||||
throw new Exception_BadResponse("Missing \"access_token\" field.");
|
||||
}
|
||||
$accessToken = $parts['access_token'];
|
||||
if (!array_key_exists('uid', $parts) or !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 "
|
||||
.Client::q($tokenType));
|
||||
}
|
||||
|
||||
return array($accessToken, $userId);
|
||||
}
|
||||
}
|
20
modules/Dropbox/WebAuthException/BadRequest.php
Normal file
20
modules/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);
|
||||
}
|
||||
}
|
21
modules/Dropbox/WebAuthException/BadState.php
Normal file
21
modules/Dropbox/WebAuthException/BadState.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct("Missing CSRF token in session.");
|
||||
}
|
||||
}
|
21
modules/Dropbox/WebAuthException/Csrf.php
Normal file
21
modules/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
modules/Dropbox/WebAuthException/NotApproved.php
Normal file
18
modules/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
modules/Dropbox/WebAuthException/Provider.php
Normal file
18
modules/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);
|
||||
}
|
||||
}
|
78
modules/Dropbox/WebAuthNoRedirect.php
Normal file
78
modules/Dropbox/WebAuthNoRedirect.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?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");
|
||||
* $code = \trim(\readline("4. Enter the authorization code here: "));
|
||||
*
|
||||
* 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}.
|
||||
*
|
||||
* @return string
|
||||
* An authorization URL. Redirect 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.
|
||||
*
|
||||
* @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
modules/Dropbox/WriteMode.php
Normal file
116
modules/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
modules/Dropbox/autoload.php
Normal file
31
modules/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');
|
341
modules/Dropbox/trusted-certs.crt
Normal file
341
modules/Dropbox/trusted-certs.crt
Normal file
|
@ -0,0 +1,341 @@
|
|||
# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com <server-certs@thawte.com>
|
||||
# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress= server-certs@thawte.com
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
|
||||
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
|
||||
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
|
||||
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
|
||||
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
|
||||
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
|
||||
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
|
||||
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
|
||||
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
|
||||
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
|
||||
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
|
||||
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
|
||||
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
|
||||
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>
|
||||
# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
|
||||
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
|
||||
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
|
||||
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
|
||||
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
|
||||
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
|
||||
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
|
||||
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
|
||||
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
|
||||
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
|
||||
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
|
||||
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
|
||||
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
|
||||
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
|
||||
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
|
||||
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
|
||||
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority
|
||||
# Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPDCCAaUCEDJQM89Q0VbzXIGtZVxPyCUwDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||
cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||
MDEyOTAwMDAwMFoXDTIwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt
|
||||
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||
ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f
|
||||
zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi
|
||||
TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G
|
||||
CSqGSIb3DQEBAgUAA4GBAEtEZmBoZOSYG/OwcuaViXzde7OVwB0u2NgZ0C00PcZQ
|
||||
mhCGjKo/O6gE/DdSlcPZydvN8oYGxLEb8IKIMEKOF1AcZHq4PplJdJf8rAJD+5YM
|
||||
VgQlDHx8h50kp9jwMim1pN9dokzFFjKoQvZFprY2ueC/ZTaTwtLXa9zeWdaiNfhF
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
|
||||
# Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
|
||||
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
|
||||
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
|
||||
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
|
||||
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
|
||||
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
|
||||
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
|
||||
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
|
||||
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
|
||||
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
|
||||
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority
|
||||
# Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG
|
||||
A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD
|
||||
VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0
|
||||
MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV
|
||||
BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy
|
||||
dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ
|
||||
ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII
|
||||
0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI
|
||||
uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI
|
||||
hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3
|
||||
YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc
|
||||
1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1
|
||||
# Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
|
||||
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
|
||||
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
|
||||
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
|
||||
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
|
||||
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
|
||||
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
|
||||
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
|
||||
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
|
||||
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
|
||||
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
|
||||
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
|
||||
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
|
||||
# Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
|
||||
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
|
||||
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
|
||||
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
|
||||
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
|
||||
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
|
||||
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
|
||||
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
|
||||
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
|
||||
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
|
||||
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
|
||||
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
|
||||
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
|
||||
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
|
||||
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
|
||||
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
|
||||
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
|
||||
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
|
||||
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
|
||||
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
|
||||
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
|
||||
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
|
||||
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
|
||||
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
|
||||
# Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
|
||||
MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
|
||||
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
|
||||
dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
|
||||
UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
|
||||
ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
|
||||
c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
|
||||
OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
|
||||
mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
|
||||
BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
|
||||
qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
|
||||
gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
|
||||
BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
|
||||
bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
|
||||
dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
|
||||
6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
|
||||
h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
|
||||
/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
|
||||
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
|
||||
pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||
# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
|
||||
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
|
||||
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
|
||||
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
|
||||
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
|
||||
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
|
||||
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
|
||||
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
|
||||
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
|
||||
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
|
||||
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
|
||||
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
|
||||
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
|
||||
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
|
||||
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
|
||||
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
|
||||
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
|
||||
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
|
||||
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
|
||||
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
|
||||
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
|
||||
ReYNnyicsbkqWletNw+vHX/bvZ8=
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository<http://certificates.godaddy.com/repository>, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
|
||||
# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
|
||||
ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
|
||||
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
|
||||
MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
|
||||
QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
|
||||
b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
|
||||
b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
|
||||
YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
|
||||
KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
|
||||
VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
|
||||
SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
|
||||
cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
|
||||
6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
|
||||
MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
|
||||
kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
|
||||
BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
|
||||
BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
|
||||
c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
|
||||
AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
|
||||
OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
|
||||
A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
|
||||
0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
|
||||
RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
|
||||
qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
|
||||
U+4=
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
|
||||
# Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
|
||||
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
|
||||
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
|
||||
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
|
||||
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
|
||||
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
|
||||
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
|
||||
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
|
||||
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
|
||||
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
|
||||
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
|
||||
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
|
||||
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
|
||||
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
|
||||
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
|
||||
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
|
||||
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
|
||||
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
|
||||
4uJEvlz36hz1
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
|
||||
# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
|
||||
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
|
||||
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
|
||||
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
|
||||
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
|
||||
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
|
||||
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
|
||||
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
|
||||
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
|
||||
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
|
||||
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
|
||||
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
|
||||
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
|
||||
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
|
||||
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
|
||||
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
|
||||
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
|
||||
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
|
||||
# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
|
||||
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
|
||||
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
|
||||
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
|
||||
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
|
||||
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
|
||||
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
|
||||
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
|
||||
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
|
||||
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
|
||||
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
|
||||
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
|
||||
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
|
||||
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
|
||||
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
|
||||
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
|
||||
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
|
||||
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
|
||||
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
|
||||
# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
|
||||
bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
|
||||
Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
|
||||
QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
|
||||
BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX
|
||||
DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE
|
||||
YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0
|
||||
aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC
|
||||
ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
|
||||
2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q
|
||||
N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO
|
||||
r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN
|
||||
f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH
|
||||
U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU
|
||||
TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb
|
||||
VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg
|
||||
SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv
|
||||
biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg
|
||||
MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw
|
||||
AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv
|
||||
ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu
|
||||
Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd
|
||||
IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv
|
||||
bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1
|
||||
QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O
|
||||
WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf
|
||||
SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==
|
||||
-----END CERTIFICATE-----
|
||||
# Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>
|
||||
# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
|
||||
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
|
||||
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
|
||||
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
|
||||
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
|
||||
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
|
||||
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
|
||||
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
|
||||
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
|
||||
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
|
||||
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
|
||||
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
|
||||
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
|
||||
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
|
||||
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
|
||||
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
491
modules/catalog/dropbox.catalog.php
Normal file
491
modules/catalog/dropbox.catalog.php
Normal file
|
@ -0,0 +1,491 @@
|
|||
<?php
|
||||
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
|
||||
/**
|
||||
*
|
||||
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
||||
* Copyright 2001 - 2013 Ampache.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License v2
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
require_once Config::get('prefix') . '/modules/Dropbox/autoload.php';
|
||||
|
||||
/**
|
||||
* Dropbox Catalog Class
|
||||
*
|
||||
* This class handles all actual work in regards to remote Dropbox catalogs.
|
||||
*
|
||||
*/
|
||||
class Catalog_dropbox extends Catalog {
|
||||
|
||||
private $version = '000001';
|
||||
private $type = 'dropbox';
|
||||
private $description = 'Remote Dropbox Catalog';
|
||||
|
||||
/**
|
||||
* get_description
|
||||
* This returns the description of this catalog
|
||||
*/
|
||||
public function get_description() {
|
||||
|
||||
return $this->description;
|
||||
|
||||
} // get_description
|
||||
|
||||
/**
|
||||
* get_version
|
||||
* This returns the current version
|
||||
*/
|
||||
public function get_version() {
|
||||
|
||||
return $this->version;
|
||||
|
||||
} // get_version
|
||||
|
||||
/**
|
||||
* get_type
|
||||
* This returns the current catalog type
|
||||
*/
|
||||
public function get_type() {
|
||||
|
||||
return $this->type;
|
||||
|
||||
} // get_type
|
||||
|
||||
/**
|
||||
* get_create_help
|
||||
* This returns hints on catalog creation
|
||||
*/
|
||||
public function get_create_help() {
|
||||
|
||||
$help = "<ul><li>Go to https://www.dropbox.com/developers/apps/create</li>" .
|
||||
"<li>Select 'Dropbox API app'</li>" .
|
||||
"<li>Select 'Files and datastores'</li>" .
|
||||
"<li>Select 'No' at 'Can your app be limited to its own, private folder?'</li>" .
|
||||
"<li>Select 'Specific file types' at 'What type of files does your app need access to?'</li>" .
|
||||
"<li>Check Videos and Audio files</li>" .
|
||||
"<li>Give a name to your application and create it</li>" .
|
||||
//"<li>Add the following OAuth redirect URIs: <i>" . Config::get('web_path') . "/admin/catalog.php</i></li>" .
|
||||
"<li>Copy your App key and App secret here</li></ul>";
|
||||
return $help;
|
||||
|
||||
} // get_create_help
|
||||
|
||||
/**
|
||||
* is_installed
|
||||
* This returns true or false if remote catalog is installed
|
||||
*/
|
||||
public function is_installed() {
|
||||
|
||||
$sql = "DESCRIBE `catalog_dropbox`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return Dba::num_rows($db_results);
|
||||
|
||||
|
||||
} // is_installed
|
||||
|
||||
/**
|
||||
* install
|
||||
* This function installs the remote catalog
|
||||
*/
|
||||
public function install() {
|
||||
|
||||
$sql = "CREATE TABLE `catalog_dropbox` (`id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , ".
|
||||
"`apikey` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`secret` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`path` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`authtoken` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||
"`getchunk` TINYINT(1) NOT NULL, " .
|
||||
"`catalog_id` INT( 11 ) NOT NULL" .
|
||||
") ENGINE = MYISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return true;
|
||||
|
||||
} // install
|
||||
|
||||
public function catalog_fields() {
|
||||
|
||||
$fields['apikey'] = array('description' => T_('API Key'), 'type'=>'textbox');
|
||||
$fields['secret'] = array('description' => T_('Secret'), 'type'=>'password');
|
||||
$fields['path'] = array('description' => T_('Path'), 'type'=>'textbox', 'value' => '/');
|
||||
$fields['getchunk'] = array('description' => T_('Get chunked files on analyze'), 'type'=>'checkbox', 'value' => true);
|
||||
|
||||
return $fields;
|
||||
|
||||
}
|
||||
|
||||
public $apikey;
|
||||
public $secret;
|
||||
public $path;
|
||||
public $authtoken;
|
||||
public $getchunk;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Catalog class constructor, pulls catalog information
|
||||
*/
|
||||
public function __construct($catalog_id = null) {
|
||||
if ($catalog_id) {
|
||||
$this->id = intval($catalog_id);
|
||||
$info = $this->get_info($catalog_id);
|
||||
|
||||
foreach ($info as $key=>$value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create_type
|
||||
*
|
||||
* This creates a new catalog type entry for a catalog
|
||||
* It checks to make sure its parameters is not already used before creating
|
||||
* the catalog.
|
||||
*/
|
||||
public static function create_type($catalog_id, $data) {
|
||||
|
||||
$apikey = $data['apikey'];
|
||||
$secret = $data['secret'];
|
||||
$path = $data['path'];
|
||||
$getchunk = $data['getchunk'];
|
||||
|
||||
if (!strlen($apikey) OR !strlen($secret)) {
|
||||
Error::add('general', T_('Error: API Key and Secret Required for Dropbox Catalogs'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$pathError = Dropbox\Path::findError($path);
|
||||
if ($pathError !== null) {
|
||||
Error::add('general', T_('Invalid <dropbox-path>: ' . $pathError));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this app isn't already in use by an existing catalog
|
||||
$sql = 'SELECT `id` FROM `catalog_dropbox` WHERE `apikey` = ?';
|
||||
$db_results = Dba::read($sql, array($apikey));
|
||||
|
||||
if (Dba::num_rows($db_results)) {
|
||||
debug_event('catalog', 'Cannot add catalog with duplicate key ' . $apikey, 1);
|
||||
Error::add('general', sprintf(T_('Error: Catalog with %s already exists'), $apikey));
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = 'INSERT INTO `catalog_dropbox` (`apikey`, `secret`, `path`, `getchunk`, `catalog_id`) VALUES (?, ?, ?, ?, ?)';
|
||||
Dba::write($sql, array($apikey, $secret, $path, ($getchunk ? 1 : 0), $catalog_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getWebAuth() {
|
||||
$appInfo = new Dropbox\AppInfo($this->apikey, $this->secret);
|
||||
$webAuth = new Dropbox\WebAuthNoRedirect($appInfo, "ampache", "en");
|
||||
|
||||
return $webAuth;
|
||||
}
|
||||
|
||||
protected function showAuthToken() {
|
||||
$webAuth = $this->getWebAuth();
|
||||
$authurl = $webAuth->start();
|
||||
echo "<br />Go to <strong><a href='" . $authurl . "' target='_blank'>" . $authurl . "</a></strong> to generate the authorization code, then enter it bellow.<br />";
|
||||
echo "<form action='' method='post' enctype='multipart/form-data'>";
|
||||
if ($_POST['action']) {
|
||||
echo "<input type='hidden' name='action' value='add_to_catalog' />";
|
||||
echo "<input type='hidden' name='catalogs[]' value='". $this->id ."' />";
|
||||
}
|
||||
echo "<input type='text' name='authcode' size='30' />";
|
||||
echo "<input type='submit' value='Ok' />";
|
||||
echo "</form>";
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
protected function completeAuthToken() {
|
||||
$webAuth = $this->getWebAuth();
|
||||
list($accessToken, $userId) = $webAuth->finish($this->authcode);
|
||||
debug_event('dropbox_catalog', 'Dropbox authentication token generated for user ' . $userId . '.', 1);
|
||||
$this->authtoken = $accessToken;
|
||||
|
||||
$sql = 'UPDATE `catalog_dropbox` SET `authtoken` = ? WHERE `catalog_id` = ?';
|
||||
Dba::write($sql, array($this->authtoken, $this->catalog_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* add_to_catalog
|
||||
* this function adds new files to an
|
||||
* existing catalog
|
||||
*/
|
||||
public function add_to_catalog($options = null) {
|
||||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
if ($options != null) {
|
||||
$this->authcode = $options['authcode'];
|
||||
}
|
||||
|
||||
UI::show_box_top(T_('Running Dropbox Remote Update') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
UI::show_box_bottom();
|
||||
|
||||
return true;
|
||||
} // add_to_catalog
|
||||
|
||||
public function createClient() {
|
||||
if ($this->authcode) {
|
||||
$this->completeAuthToken();
|
||||
}
|
||||
if (!$this->authtoken) {
|
||||
$this->showAuthToken();
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Dropbox\Client($this->authtoken, "ampache", "en");
|
||||
} catch (Dropbox\Exception $e) {
|
||||
debug_event('dropbox_catalog', 'Dropbox authentication error: ' . $ex->getMessage(), 1);
|
||||
$this->showAuthToken();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update_remote_catalog
|
||||
*
|
||||
* Pulls the data from a remote catalog and adds any missing songs to the
|
||||
* database.
|
||||
*/
|
||||
public function update_remote_catalog() {
|
||||
$client = $this->createClient();
|
||||
if ($client != null) {
|
||||
$this->count = 0;
|
||||
$this->add_files($client, $this->path);
|
||||
|
||||
echo "\n<br />" .
|
||||
printf(T_('Catalog Update Finished. Total Songs: [%s]'), $this->count);
|
||||
echo '<br />';
|
||||
if ($this->count == 0) {
|
||||
echo T_('No songs updated, do you respect the patterns?') . '<br />';
|
||||
}
|
||||
echo '<br />';
|
||||
}else {
|
||||
echo "<p>" . T_('API Error: cannot connect to Dropbox.') . "</p><hr />\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* add_files
|
||||
*
|
||||
* Recurses through directories and pulls out all media files
|
||||
*/
|
||||
public function add_files($client, $path) {
|
||||
$metadata = $client->getMetadataWithChildren($path);
|
||||
if ($metadata != null) {
|
||||
// If it's a folder, remove the 'contents' list from $metadata; print that stuff out after.
|
||||
$children = null;
|
||||
if ($metadata['is_dir']) {
|
||||
$children = $metadata['contents'];
|
||||
if ($children !== null && count($children) > 0) {
|
||||
foreach ($children as $child) {
|
||||
if ($child['is_dir']) {
|
||||
$this->add_files($client, $child['path']);
|
||||
} else {
|
||||
$this->add_file($client, $child);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->add_file($client, $metadata);
|
||||
}
|
||||
} else {
|
||||
echo "<p>" . T_('API Error: Cannot access file/folder at ' . $this->path . '.') . "</p><hr />\n";
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function add_file($client, $data) {
|
||||
$file = $data['path'];
|
||||
$filesize = $data['bytes'];
|
||||
if ($filesize > 0) {
|
||||
$is_audio_file = Catalog::is_audio_file($file);
|
||||
|
||||
if ($is_audio_file) {
|
||||
$this->insert_song($client, $file, $filesize);
|
||||
}else {
|
||||
debug_event('read', $data['path'] . " ignored, unknown media file type", 5);
|
||||
}
|
||||
} else {
|
||||
debug_event('read', $data['path'] . " ignored, 0 bytes", 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* _insert_local_song
|
||||
*
|
||||
* Insert a song that isn't already in the database.
|
||||
*/
|
||||
private function insert_song($client, $file, $filesize) {
|
||||
|
||||
if ($this->check_remote_song($this->get_virtual_path($file))) {
|
||||
debug_event('dropbox_catalog', 'Skipping existing song ' . $file, 5);
|
||||
} else {
|
||||
$origin = $file;
|
||||
$islocal = false;
|
||||
$fpchunk = 0;
|
||||
// Get temporary chunked file from Dropbox to (hope) read metadata
|
||||
if ($this->getchunk) {
|
||||
$fpchunk = tmpfile();
|
||||
$metadata = $client->getFile($file, $fpchunk, null, 40960);
|
||||
if ($metadata == null) {
|
||||
debug_event('dropbox_catalog', 'Cannot get Dropbox file: ' . $file, 5);
|
||||
}
|
||||
$file = stream_get_meta_data($fpchunk)['uri'];
|
||||
$islocal = true;
|
||||
}
|
||||
|
||||
$vainfo = new vainfo($file, '', '', '', $this->sort_pattern, $this->rename_pattern, $islocal);
|
||||
$vainfo->forceSize($filesize);
|
||||
$vainfo->get_info();
|
||||
|
||||
$key = vainfo::get_tag_type($vainfo->tags);
|
||||
$results = vainfo::clean_tag_info($vainfo->tags, $key, $file);
|
||||
|
||||
// Remove temp file
|
||||
if ($fpchunk) {
|
||||
fclose($fpchunk);
|
||||
}
|
||||
|
||||
// Set the remote path
|
||||
$results['file'] = $origin;
|
||||
$results['catalog'] = $this->id;
|
||||
|
||||
if (!empty($results['artist']) && !empty($results['album'])) {
|
||||
$results['file'] = $this->get_virtual_path($results['file']);
|
||||
|
||||
Song::insert($results);
|
||||
$this->count++;
|
||||
} else {
|
||||
debug_event('results', $results['file'] . " ignored because it is an orphan songs. Please check your catalog patterns.", 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function verify_catalog_proc() {
|
||||
return array('total' => 0, 'updated' => 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* clean_catalog_proc
|
||||
*
|
||||
* Removes songs that no longer exist.
|
||||
*/
|
||||
public function clean_catalog_proc() {
|
||||
$dead = 0;
|
||||
|
||||
$client = $this->createClient();
|
||||
if ($client != null) {
|
||||
$sql = 'SELECT `id`, `file` FROM `song` WHERE `catalog` = ?';
|
||||
$db_results = Dba::read($sql, array($this->id));
|
||||
while ($row = Dba::fetch_assoc($db_results)) {
|
||||
debug_event('dropbox-clean', 'Starting work on ' . $row['file'] . '(' . $row['id'] . ')', 5, 'ampache-catalog');
|
||||
$file = $this->get_rel_path($row['file']);
|
||||
$metadata = $client->getMetadata($file);
|
||||
if ($metadata) {
|
||||
debug_event('dropbox-clean', 'keeping song', 5, 'ampache-catalog');
|
||||
}
|
||||
else {
|
||||
debug_event('dropbox-clean', 'removing song', 5, 'ampache-catalog');
|
||||
$dead++;
|
||||
Dba::write('DELETE FROM `song` WHERE `id` = ?', array($row['id']));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "<p>" . T_('API Error: cannot connect to Dropbox.') . "</p><hr />\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
return $dead;
|
||||
}
|
||||
|
||||
/**
|
||||
* check_remote_song
|
||||
*
|
||||
* checks to see if a remote song exists in the database or not
|
||||
* if it find a song it returns the UID
|
||||
*/
|
||||
public function check_remote_song($file) {
|
||||
|
||||
$sql = 'SELECT `id` FROM `song` WHERE `file` = ?';
|
||||
$db_results = Dba::read($sql, array($file));
|
||||
|
||||
if ($results = Dba::fetch_assoc($db_results)) {
|
||||
return $results['id'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get_virtual_path($file) {
|
||||
return $this->apikey . '|' . $file;
|
||||
}
|
||||
|
||||
public function get_rel_path($file_path) {
|
||||
$p = strpos($file_path, "|");
|
||||
if ($p !== false) {
|
||||
$p++;
|
||||
}
|
||||
return substr($file_path, $p);
|
||||
}
|
||||
|
||||
/**
|
||||
* format
|
||||
*
|
||||
* This makes the object human-readable.
|
||||
*/
|
||||
public function format() {
|
||||
parent::format();
|
||||
$this->f_info = UI::truncate($this->apikey, Config::get('ellipse_threshold_title'));
|
||||
}
|
||||
|
||||
public function prepare_media($media) {
|
||||
|
||||
$client = $this->createClient();
|
||||
if ($client != null) {
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
// Generate browser class for sending headers
|
||||
$browser = new Horde_Browser();
|
||||
$media_name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
|
||||
$browser->downloadHeaders($media_name, $media->mime, false, $media->size);
|
||||
$file = $this->get_rel_path($media->file);
|
||||
|
||||
$output = fopen('php://output', 'w');
|
||||
$metadata = $client->getFile($file, $output);
|
||||
if ($metadata == null) {
|
||||
debug_event('play', 'File not found on Dropbox: ' . $file, 5);
|
||||
}
|
||||
fclose($output);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
} // end of catalog class
|
||||
|
||||
?>
|
|
@ -62,6 +62,16 @@ class Catalog_googlemusic extends Catalog {
|
|||
|
||||
} // get_type
|
||||
|
||||
/**
|
||||
* get_create_help
|
||||
* This returns hints on catalog creation
|
||||
*/
|
||||
public function get_create_help() {
|
||||
|
||||
return "";
|
||||
|
||||
} // get_create_help
|
||||
|
||||
/**
|
||||
* is_installed
|
||||
* This returns true or false if remote catalog is installed
|
||||
|
@ -94,25 +104,11 @@ class Catalog_googlemusic extends Catalog {
|
|||
|
||||
} // install
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
* This removes the remote catalog
|
||||
*/
|
||||
public function uninstall() {
|
||||
|
||||
$sql = "DROP TABLE `catalog_googlemusic`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return true;
|
||||
|
||||
} // uninstall
|
||||
|
||||
public function catalog_fields() {
|
||||
|
||||
$fields['email'] = array('description' => T_('Email'),'type'=>'textbox');
|
||||
$fields['password'] = array('description' => T_('Password'),'type'=>'password');
|
||||
// devicdeid not required for streaming
|
||||
//$fields['deviceid'] = array('description' => T_('Device ID'),'type'=>'textbox');
|
||||
$fields['deviceid'] = array('description' => T_('Device ID'),'type'=>'textbox');
|
||||
|
||||
return $fields;
|
||||
|
||||
|
@ -173,30 +169,12 @@ class Catalog_googlemusic extends Catalog {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* run_add
|
||||
*
|
||||
* This runs the add to catalog function
|
||||
* it includes the javascript refresh stuff and then starts rolling
|
||||
* throught the path for this catalog
|
||||
*/
|
||||
public function run_add($options) {
|
||||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
UI::show_box_top(T_('Running Google Music Remote Sync') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
UI::show_box_bottom();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_to_catalog
|
||||
* this function adds new files to an
|
||||
* existing catalog
|
||||
*/
|
||||
public function add_to_catalog() {
|
||||
public function add_to_catalog($options = null) {
|
||||
|
||||
UI::show_box_top(T_('Running Google Music Remote Update') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
|
@ -209,7 +187,7 @@ class Catalog_googlemusic extends Catalog {
|
|||
$api = new GMApi();
|
||||
$api->setDebug(Config::get('debug'));
|
||||
$api->enableRestore(false);
|
||||
$api->enableMACAddressCheck(false);
|
||||
$api->enableMACAddressCheck(true);
|
||||
$api->enableSessionFile(false);
|
||||
|
||||
if(!$api->login($this->email, $this->password, $this->deviceid)) {
|
||||
|
@ -290,7 +268,7 @@ class Catalog_googlemusic extends Catalog {
|
|||
/**
|
||||
* clean_catalog_proc
|
||||
*
|
||||
* Removes subsonic songs that no longer exist.
|
||||
* Removes songs that no longer exist.
|
||||
*/
|
||||
public function clean_catalog_proc() {
|
||||
$api = $this->createClient();
|
||||
|
@ -343,7 +321,7 @@ class Catalog_googlemusic extends Catalog {
|
|||
|
||||
public function get_rel_path($file_path) {
|
||||
$info = $this->_get_info();
|
||||
$catalog_path = rtrim($info->uri, "/");
|
||||
$catalog_path = rtrim($info->email, "/");
|
||||
return( str_replace( $catalog_path . "/", "", $file_path ) );
|
||||
}
|
||||
|
||||
|
@ -371,10 +349,11 @@ class Catalog_googlemusic extends Catalog {
|
|||
$api = $this->createClient();
|
||||
if ($api != null) {
|
||||
$songid = $this->url_to_songid($media->file);
|
||||
|
||||
$song = $api->get_stream_url($songid);
|
||||
if ($song) {
|
||||
header('Location: ' . $url['url']);
|
||||
debug_event('play', 'Started remote stream - ' . $url['url'], 5);
|
||||
header('Location: ' . $song);
|
||||
debug_event('play', 'Started remote stream - ' . $song, 5);
|
||||
} else {
|
||||
debug_event('play', 'Cannot get remote stream for song ' . $media->file, 5);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,16 @@ class Catalog_local extends Catalog {
|
|||
|
||||
} // get_type
|
||||
|
||||
/**
|
||||
* get_create_help
|
||||
* This returns hints on catalog creation
|
||||
*/
|
||||
public function get_create_help() {
|
||||
|
||||
return "";
|
||||
|
||||
} // get_create_help
|
||||
|
||||
/**
|
||||
* is_installed
|
||||
* This returns true or false if local catalog is installed
|
||||
|
@ -92,19 +102,6 @@ class Catalog_local extends Catalog {
|
|||
|
||||
} // install
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
* This removes the local catalog
|
||||
*/
|
||||
public function uninstall() {
|
||||
|
||||
$sql = "DROP TABLE `catalog_local`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return true;
|
||||
|
||||
} // uninstall
|
||||
|
||||
public function catalog_fields() {
|
||||
|
||||
$fields['path'] = array('description' => T_('Path'),'type'=>'textbox');
|
||||
|
@ -209,42 +206,6 @@ class Catalog_local extends Catalog {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* run_add
|
||||
*
|
||||
* This runs the add to catalog function
|
||||
* it includes the javascript refresh stuff and then starts rolling
|
||||
* throught the path for this catalog
|
||||
*/
|
||||
public function run_add($options) {
|
||||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
$start_time = time();
|
||||
|
||||
require Config::get('prefix') . '/templates/show_adds_catalog.inc.php';
|
||||
flush();
|
||||
|
||||
$this->add_files($this->path, $options);
|
||||
|
||||
// If they have checked the box then go ahead and gather the art
|
||||
if ($options['gather_art']) {
|
||||
$catalog_id = $this->id;
|
||||
require Config::get('prefix') . '/templates/show_gather_art.inc.php';
|
||||
flush();
|
||||
$this->gather_art();
|
||||
}
|
||||
|
||||
// Handle m3u-ness
|
||||
if ($options['parse_m3u'] AND count($this->_playlists)) {
|
||||
foreach ($this->_playlists as $playlist_file) {
|
||||
$result = $this->import_m3u($playlist_file);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_files
|
||||
*
|
||||
|
@ -336,24 +297,9 @@ class Catalog_local extends Catalog {
|
|||
continue;
|
||||
} //it's a directory
|
||||
|
||||
/* If it's not a dir let's roll with it
|
||||
* next we need to build the pattern that we will use
|
||||
* to detect if it's an audio file
|
||||
*/
|
||||
$pattern = "/\.(" . Config::get('catalog_file_pattern');
|
||||
if ($options['parse_m3u']) {
|
||||
$pattern .= "|m3u)$/i";
|
||||
}
|
||||
else {
|
||||
$pattern .= ")$/i";
|
||||
}
|
||||
|
||||
$is_audio_file = preg_match($pattern,$file);
|
||||
|
||||
// Define the Video file pattern
|
||||
$is_audio_file = Catalog::is_audio_file($file);
|
||||
if (!$is_audio_file AND Config::get('catalog_video_pattern')) {
|
||||
$video_pattern = "/\.(" . Config::get('catalog_video_pattern') . ")$/i";
|
||||
$is_video_file = preg_match($video_pattern,$file);
|
||||
$is_video_file = Catalog::is_video_file($file);
|
||||
}
|
||||
|
||||
/* see if this is a valid audio file or playlist file */
|
||||
|
@ -431,7 +377,14 @@ class Catalog_local extends Catalog {
|
|||
* this function adds new files to an
|
||||
* existing catalog
|
||||
*/
|
||||
public function add_to_catalog() {
|
||||
public function add_to_catalog($options = null) {
|
||||
|
||||
if ($options == null) {
|
||||
$options = array(
|
||||
'gather_art' => true,
|
||||
'parse_m3u' => true
|
||||
);
|
||||
}
|
||||
|
||||
require Config::get('prefix') . '/templates/show_adds_catalog.inc.php';
|
||||
flush();
|
||||
|
@ -447,27 +400,27 @@ class Catalog_local extends Catalog {
|
|||
set_time_limit(0);
|
||||
|
||||
/* Get the songs and then insert them into the db */
|
||||
$this->add_files($this->path,$type,0,$verbose);
|
||||
$this->add_files($this->path, $options);
|
||||
|
||||
if ($options['parse_m3u'] && count($this->_playlists)) {
|
||||
// Foreach Playlists we found
|
||||
foreach ($this->_playlists as $full_file) {
|
||||
$result = $this->import_m3u($full_file);
|
||||
if ($result['success']) {
|
||||
$file = basename($full_file);
|
||||
if ($verbose) {
|
||||
echo " " . T_('Added Playlist From') . " $file . . . .<br />\n";
|
||||
flush();
|
||||
}
|
||||
} // end if import worked
|
||||
} // end foreach playlist files
|
||||
}
|
||||
|
||||
/* Do a little stats mojo here */
|
||||
$current_time = time();
|
||||
|
||||
if ($options['gather_art']) {
|
||||
$catalog_id = $this->id;
|
||||
require Config::get('prefix') . '/templates/show_gather_art.inc.php';
|
||||
flush();
|
||||
$this->gather_art();
|
||||
}
|
||||
|
||||
/* Update the Catalog last_update */
|
||||
$this->update_last_add();
|
||||
|
|
|
@ -62,6 +62,16 @@ class Catalog_remote extends Catalog {
|
|||
|
||||
} // get_type
|
||||
|
||||
/**
|
||||
* get_create_help
|
||||
* This returns hints on catalog creation
|
||||
*/
|
||||
public function get_create_help() {
|
||||
|
||||
return "";
|
||||
|
||||
} // get_create_help
|
||||
|
||||
/**
|
||||
* is_installed
|
||||
* This returns true or false if remote catalog is installed
|
||||
|
@ -94,19 +104,6 @@ class Catalog_remote extends Catalog {
|
|||
|
||||
} // install
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
* This removes the remote catalog
|
||||
*/
|
||||
public function uninstall() {
|
||||
|
||||
$sql = "DROP TABLE `catalog_remote`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return true;
|
||||
|
||||
} // uninstall
|
||||
|
||||
public function catalog_fields() {
|
||||
|
||||
$fields['uri'] = array('description' => T_('Uri'),'type'=>'textbox');
|
||||
|
@ -176,30 +173,12 @@ class Catalog_remote extends Catalog {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* run_add
|
||||
*
|
||||
* This runs the add to catalog function
|
||||
* it includes the javascript refresh stuff and then starts rolling
|
||||
* throught the path for this catalog
|
||||
*/
|
||||
public function run_add($options) {
|
||||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
UI::show_box_top(T_('Running Remote Sync') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
UI::show_box_bottom();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_to_catalog
|
||||
* this function adds new files to an
|
||||
* existing catalog
|
||||
*/
|
||||
public function add_to_catalog() {
|
||||
public function add_to_catalog($options = null) {
|
||||
|
||||
UI::show_box_top(T_('Running Remote Update') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
|
|
|
@ -62,6 +62,16 @@ class Catalog_subsonic extends Catalog {
|
|||
|
||||
} // get_type
|
||||
|
||||
/**
|
||||
* get_create_help
|
||||
* This returns hints on catalog creation
|
||||
*/
|
||||
public function get_create_help() {
|
||||
|
||||
return "";
|
||||
|
||||
} // get_create_help
|
||||
|
||||
/**
|
||||
* is_installed
|
||||
* This returns true or false if remote catalog is installed
|
||||
|
@ -94,19 +104,6 @@ class Catalog_subsonic extends Catalog {
|
|||
|
||||
} // install
|
||||
|
||||
/**
|
||||
* uninstall
|
||||
* This removes the remote catalog
|
||||
*/
|
||||
public function uninstall() {
|
||||
|
||||
$sql = "DROP TABLE `catalog_subsonic`";
|
||||
$db_results = Dba::query($sql);
|
||||
|
||||
return true;
|
||||
|
||||
} // uninstall
|
||||
|
||||
public function catalog_fields() {
|
||||
|
||||
$fields['uri'] = array('description' => T_('Uri'),'type'=>'textbox');
|
||||
|
@ -177,30 +174,14 @@ class Catalog_subsonic extends Catalog {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* run_add
|
||||
*
|
||||
* This runs the add to catalog function
|
||||
* it includes the javascript refresh stuff and then starts rolling
|
||||
* throught the path for this catalog
|
||||
*/
|
||||
public function run_add($options) {
|
||||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
UI::show_box_top(T_('Running Subsonic Remote Sync') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
UI::show_box_bottom();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_to_catalog
|
||||
* this function adds new files to an
|
||||
* existing catalog
|
||||
*/
|
||||
public function add_to_catalog() {
|
||||
public function add_to_catalog($options = null) {
|
||||
// Prevent the script from timing out
|
||||
set_time_limit(0);
|
||||
|
||||
UI::show_box_top(T_('Running Subsonic Remote Update') . '. . .');
|
||||
$this->update_remote_catalog();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue