1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-06 03:49:56 +02:00

Fix Soundcloud and Dropbox catalog modules since SSE

Update php-dropbox to latest version (fix SSLv3 issue)
This commit is contained in:
Afterster 2015-02-14 11:23:52 +01:00
parent 0da311f916
commit 1711cd14ff
27 changed files with 2278 additions and 579 deletions

View file

@ -30,7 +30,22 @@ if (!Access::check('interface','100')) {
UI::show_header();
$sse_catalogs = urlencode(serialize($_REQUEST['catalogs']));
$catalogs = $_REQUEST['catalogs'];
// If only one catalog, check it is ready.
if (is_array($catalogs) && count($catalogs) == 1 && $_REQUEST['action'] !== 'delete_catalog' && $_REQUEST['action'] !== 'show_delete_catalog') {
// If not ready, display the data to make it ready / stop the action.
$catalog = Catalog::create_from_id($catalogs[0]);
if (!$catalog->isReady()) {
if (!isset($_REQUEST['perform_ready'])) {
$catalog->show_ready_process();
UI::show_footer();
exit;
} else {
$catalog->perform_ready();
}
}
}
$sse_catalogs = urlencode(serialize($catalogs));
/* Big switch statement to handle various actions */
switch ($_REQUEST['action']) {
@ -74,14 +89,14 @@ switch ($_REQUEST['action']) {
}
/* Delete the sucker, we don't need to check perms as thats done above */
foreach ($_REQUEST['catalogs'] as $catalog_id) {
foreach ($catalogs as $catalog_id) {
Catalog::delete($catalog_id);
}
$next_url = AmpConfig::get('web_path') . '/admin/catalog.php';
show_confirmation(T_('Catalog Deleted'), T_('The Catalog and all associated records have been deleted'),$next_url);
break;
case 'show_delete_catalog':
$next_url = AmpConfig::get('web_path') . '/admin/catalog.php?action=delete_catalog&catalogs[]=' . implode(',', $_REQUEST['catalogs']);
$next_url = AmpConfig::get('web_path') . '/admin/catalog.php?action=delete_catalog&catalogs[]=' . implode(',', $catalogs);
show_confirmation(T_('Catalog Delete'), T_('Confirm Deletion Request'),$next_url,1,'delete_catalog');
break;
case 'enable_disabled':

View file

@ -157,6 +157,31 @@ abstract class Catalog extends database_object
*/
abstract public function prepare_media($media);
/**
* Check if the catalog is ready to perform actions (configuration completed, ...)
* @return boolean
*/
public function isReady()
{
return true;
}
/**
* Show a message to make the catalog ready.
*/
public function show_ready_process()
{
// Do nothing.
}
/**
* Perform the last step process to make the catalog ready.
*/
public function perform_ready()
{
// Do nothing.
}
/**
* uninstall
* This removes the remote catalog

View file

@ -114,17 +114,23 @@ abstract class Catalog extends \Catalog
*/
public function add_to_catalog($options = null)
{
require AmpConfig::get('prefix') . '/templates/show_adds_catalog.inc.php';
flush();
if (!defined('SSE_OUTPUT')) {
require AmpConfig::get('prefix') . '/templates/show_adds_catalog.inc.php';
flush();
}
set_time_limit(0);
UI::show_box_top(T_('Running Beets Update') . '. . .');
if (!defined('SSE_OUTPUT')) {
UI::show_box_top(T_('Running Beets Update') . '. . .');
}
$parser = $this->getParser();
$parser->setHandler($this, 'addSong');
$parser->start($parser->getTimedCommand($this->listCommand, 'added', $this->last_add));
$this->updateUi('add', $this->addedSongs, null, true);
$this->update_last_add();
UI::show_box_bottom();
if (!defined('SSE_OUTPUT')) {
UI::show_box_bottom();
}
}
/**

View file

@ -116,7 +116,7 @@ final class AppInfo
}
$str = file_get_contents($path);
$jsonArr = json_decode($str, TRUE);
$jsonArr = json_decode($str, true);
if (is_null($jsonArr)) {
throw new AppInfoLoadException("JSON parse error: \"$path\"");
@ -131,7 +131,7 @@ final class 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)
* @param array $jsonArr Output from json_decode($str, true)
*
* @return AppInfo
*

View file

@ -6,7 +6,10 @@ namespace Dropbox;
*/
class ArrayEntryStore implements ValueStore
{
/** @var array */
private $array;
/** @var mixed */
private $key;
/**
@ -15,7 +18,7 @@ class ArrayEntryStore implements ValueStore
* @param array $array
* The array that we'll be accessing.
*
* @param object $key
* @param mixed $key
* The key for the array element we'll be accessing.
*/
function __construct(&$array, $key)

View file

@ -0,0 +1,75 @@
<?php
namespace Dropbox;
/**
* Base class for API authorization-related classes.
*/
class AuthBase
{
/**
* Whatever AppInfo was passed into the constructor.
*
* @return AppInfo
*/
function getAppInfo() { return $this->appInfo; }
/** @var AppInfo */
protected $appInfo;
/**
* An identifier for the API client, typically of the form "Name/Version".
* This is used to set the HTTP <code>User-Agent</code> header when making API requests.
* Example: <code>"PhotoEditServer/1.3"</code>
*
* If you're the author a higher-level library on top of the basic SDK, and the
* "Photo Edit" app's server code is using your library to access Dropbox, you should append
* your library's name and version to form the full identifier. For example,
* if your library is called "File Picker", you might set this field to:
* <code>"PhotoEditServer/1.3 FilePicker/0.1-beta"</code>
*
* The exact format of the <code>User-Agent</code> header is described in
* <a href="http://tools.ietf.org/html/rfc2616#section-3.8">section 3.8 of the HTTP specification</a>.
*
* Note that underlying HTTP client may append other things to the <code>User-Agent</code>, such as
* the name of the library being used to actually make the HTTP request (such as cURL).
*
* @return string
*/
function getClientIdentifier() { return $this->clientIdentifier; }
/** @var string */
protected $clientIdentifier;
/**
* The locale of the user of your application. Some API calls return localized
* data and error messages; this "user locale" setting determines which locale
* the server should use to localize those strings.
*
* @return null|string
*/
function getUserLocale() { return $this->userLocale; }
/** @var string */
protected $userLocale;
/**
* Constructor.
*
* @param AppInfo $appInfo
* See {@link getAppInfo()}
* @param string $clientIdentifier
* See {@link getClientIdentifier()}
* @param null|string $userLocale
* See {@link getUserLocale()}
*/
function __construct($appInfo, $clientIdentifier, $userLocale = null)
{
AppInfo::checkArg("appInfo", $appInfo);
Client::checkClientIdentifierArg("clientIdentifier", $clientIdentifier);
Checker::argStringNonEmptyOrNull("userLocale", $userLocale);
$this->appInfo = $appInfo;
$this->clientIdentifier = $clientIdentifier;
$this->userLocale = $userLocale;
}
}

View file

@ -27,7 +27,7 @@ final class AuthInfo
}
$str = file_get_contents($path);
$jsonArr = json_decode($str, TRUE);
$jsonArr = json_decode($str, true);
if (is_null($jsonArr)) {
throw new AuthInfoLoadException("JSON parse error: \"$path\"");
@ -41,7 +41,7 @@ final class AuthInfo
* please use the @see loadFromJsonFile method.
*
* @param array $jsonArr
* A parsed JSON object, typcally the result of json_decode(..., TRUE).
* A parsed JSON object, typcally the result of json_decode(..., true).
* @return array
* A <code>list(string $accessToken, Host $host)</code>.
*

View file

@ -56,6 +56,13 @@ class Client
/** @var null|string */
private $userLocale;
/**
* The {@link Host} object that determines the hostnames we make requests to.
*
* @return Host
*/
function getHost() { return $this->host; }
/**
* Constructor.
*
@ -68,8 +75,8 @@ class Client
*/
function __construct($accessToken, $clientIdentifier, $userLocale = null)
{
Checker::argStringNonEmpty("accessToken", $accessToken);
Checker::argStringNonEmpty("clientIdentifier", $clientIdentifier);
self::checkAccessTokenArg("accessToken", $accessToken);
self::checkClientIdentifierArg("clientIdentifier", $clientIdentifier);
Checker::argStringNonEmptyOrNull("userLocale", $userLocale);
$this->accessToken = $accessToken;
@ -99,16 +106,40 @@ class Client
private $apiHost;
/** @var string */
private $contentHost;
/** @var string */
private $root;
private function appendFilePath($base, $path)
/**
* Given a <code>$base</code> path for an API endpoint (for example, "/files"), append
* a Dropbox API file path to the end of that URL. Special characters in the file will
* be encoded properly.
*
* This is for endpoints like "/files" takes the path on the URL and not as a separate
* query or POST parameter.
*
* @param string $base
* @param string $path
* @return string
*/
function appendFilePath($base, $path)
{
return $base . "/auto/" . rawurlencode(substr($path, 1));
}
/**
* Returns a basic account and quota information.
* Make an API call to disable the access token that you constructed this <code>Client</code>
* with. After calling this, API calls made with this <code>Client</code> will fail.
*
* See <a href="https://www.dropbox.com/developers/core/docs#disable-token">/disable_access_token</a>.
*
* @throws Exception
*/
function disableAccessToken()
{
$response = $this->doPost($this->apiHost, "1/disable_access_token");
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
}
/**
* Make an API call to get basic account and quota information.
*
* <code>
* $client = ...
@ -117,7 +148,7 @@ class Client
* </code>
*
* @return array
* See <a href="https://www.dropbox.com/developers/core/api#account-info">/account/info</a>.
* See <a href="https://www.dropbox.com/developers/core/docs#account-info">/account/info</a>.
*
* @throws Exception
*/
@ -134,8 +165,9 @@ class Client
*
* <code>
* $client = ...;
* $metadata = $client->getFile("/Photos/Frog.jpeg",
* fopen("./Frog.jpeg", "wb"));
* $fd = fopen("./Frog.jpeg", "wb");
* $metadata = $client->getFile("/Photos/Frog.jpeg", $fd);
* fclose($fd);
* print_r($metadata);
* </code>
*
@ -149,29 +181,25 @@ class Client
* If you want the latest revision of the file at the given path, pass in <code>null</code>.
* If you want a specific version of a file, pass in value of the file metadata's "rev" field.
*
* @param int $maxisze
* Limit the file size to download. 0 to download the complete file.
*
* @return null|array
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
* object</a> for the file at the given $path and $rev, or <code>null</code> if the file
* doesn't exist,
*
* @throws Exception
*/
function getFile($path, $outStream, $rev = null, $maxsize = 0)
function getFile($path, $outStream, $rev = null)
{
Path::checkArgNonRoot("path", $path);
Checker::argResource("outStream", $outStream);
Checker::argStringNonEmptyOrNull("rev", $rev);
$url = RequestUtil::buildUrl(
$this->userLocale,
$url = $this->buildUrlForGetOrPut(
$this->contentHost,
$this->appendFilePath("1/files", $path),
array("rev" => $rev));
$curl = $this->mkCurl($url, $maxsize);
$curl = $this->mkCurl($url);
$metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);
$streamRelay = new CurlStreamRelay($curl->handle, $outStream);
@ -238,7 +266,7 @@ class Client
* than 8 MB.
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
* object</a> for the newly-added file.
*
* @throws Exception
@ -290,7 +318,7 @@ class Client
* The data to use for the contents of the file.
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
* object</a> for the newly-added file.
*
* @throws Exception
@ -333,7 +361,7 @@ class Client
* <code>null</code> and the library will use a reasonable default.
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
* object</a> for the newly-added file.
*
* @throws Exception
@ -370,7 +398,7 @@ class Client
* @param int $chunkSize
*
* @return array
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
* object</a> for the newly-added file.
*/
private function _uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize)
@ -422,14 +450,14 @@ class Client
// An earlier byte offset means the server has lost data we sent earlier.
if ($serverByteOffset < $byteOffset) throw new Exception_BadResponse(
"Server is at an ealier byte offset: us=$byteOffset, server=$serverByteOffset");
// The normal case is that the server is a bit further along than us because of a
// partially-uploaded chunk.
$diff = $serverByteOffset - $byteOffset;
// If the server is past where we think it could possibly be, something went wrong.
if ($diff > $len) throw new Exception_BadResponse(
"Server is more than a chunk ahead: us=$byteOffset, server=$serverByteOffset");
// Finish the rest of this chunk.
// The normal case is that the server is a bit further along than us because of a
// partially-uploaded chunk. Finish it off.
$byteOffset += $diff;
if ($diff === $len) break; // If the server is at the end, we're done.
$data = substr($data, $diff);
}
}
@ -451,7 +479,8 @@ class Client
* bytes have been read or we've reached EOF.
*
* @param resource $inStream
* @param int $limit
* @param int $numBytes
* @throws StreamReadException
* @return string
*/
private static function readFully($inStream, $numBytes)
@ -481,8 +510,7 @@ class Client
WriteMode::checkArg("writeMode", $writeMode);
Checker::argCallable("curlConfigClosure", $curlConfigClosure);
$url = RequestUtil::buildUrl(
$this->userLocale,
$url = $this->buildUrlForGetOrPut(
$this->contentHost,
$this->appendFilePath("1/files_put", $path),
$writeMode->getExtraParams());
@ -629,6 +657,8 @@ class Client
/**
* Creates a file on Dropbox using the accumulated contents of the given chunked upload session.
*
* See <a href="https://www.dropbox.com/developers/core/docs#commit-chunked-upload">/commit_chunked_upload</a>.
*
* @param string $uploadId
* The unique identifier for the chunked upload session. This is obtained via
* {@link chunkedUploadStart}.
@ -643,7 +673,7 @@ class Client
* If <code>null</code>, it means the Dropbox server wasn't aware of the
* <code>$uploadId</code> you gave it.
* Otherwise, you get back the
* <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata object</a>
* <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
* for the newly-created file.
*
* @throws Exception
@ -672,10 +702,11 @@ class Client
* @param string $data
* @return HttpResponse
*/
private function _chunkedUpload($params, $data)
protected function _chunkedUpload($params, $data)
// Marked 'protected' so I can override it in testing.
{
$url = RequestUtil::buildUrl(
$this->userLocale, $this->contentHost, "1/chunked_upload", $params);
$url = $this->buildUrlForGetOrPut(
$this->contentHost, "1/chunked_upload", $params);
$curl = $this->mkCurl($url);
@ -702,7 +733,7 @@ class Client
*
* @return array|null
* If there is a file or folder at the given path, you'll get back the
* <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata object</a>
* <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
* for that file or folder. If not, you'll get back <code>null</code>.
*
* @throws Exception
@ -729,7 +760,7 @@ class Client
*
* @return array|null
* If there is a file or folder at the given path, you'll get back the
* <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata object</a>
* <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
* for that file or folder, along with all immediate children if it's a folder. If not,
* you'll get back <code>null</code>.
*
@ -796,7 +827,7 @@ class Client
* A <code>list(boolean $changed, array $metadata)</code>. If the metadata hasn't changed,
* you'll get <code>list(false, null)</code>. If the metadata of the folder or any of its
* children has changed, you'll get <code>list(true, $newMetadata)</code>. $metadata is a
* <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata object</a>.
* <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>.
*
* @throws Exception
*/
@ -830,8 +861,14 @@ class Client
* If this is the first time you're calling this, pass in <code>null</code>. Otherwise,
* pass in whatever cursor was returned by the previous call.
*
* @param string|null $pathPrefix
* If <code>null</code>, you'll get results for the entire folder (either the user's
* entire Dropbox or your App Folder). If you set <code>$path_prefix</code> to
* "/Photos/Vacation", you'll only get results for that path and any files and folders
* under it.
*
* @return array
* A <a href="https://www.dropbox.com/developers/core/api#delta">delta page</a>, which
* A <a href="https://www.dropbox.com/developers/core/docs#delta">delta page</a>, which
* contains a list of changes to apply along with a new "cursor" that should be passed into
* future <code>getDelta</code> calls. If the "reset" field is <code>true</code>, you
* should clear your local state before applying the changes. If the "has_more" field is
@ -840,11 +877,14 @@ class Client
*
* @throws Exception
*/
function getDelta($cursor = null)
function getDelta($cursor = null, $pathPrefix = null)
{
Checker::argStringNonEmptyOrNull("cursor", $cursor);
Path::checkArgOrNull("pathPrefix", $pathPrefix);
$response = $this->doPost($this->apiHost, "1/delta", array("cursor" => $cursor));
$response = $this->doPost($this->apiHost, "1/delta", array(
"cursor" => $cursor,
"path_prefix" => $pathPrefix));
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
@ -854,7 +894,7 @@ class Client
/**
* Gets the metadata for all the file revisions (up to a limit) for a given path.
*
* See <a href="https://www.dropbox.com/developers/core/api#revisions">/revisions</a>.
* See <a href="https://www.dropbox.com/developers/core/docs#revisions">/revisions</a>.
*
* @param string path
* The Dropbox path that you want file revision metadata for (UTF-8).
@ -863,7 +903,7 @@ class Client
* The maximum number of revisions to return.
*
* @return array|null
* A list of <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
* A list of <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
* objects</a>, one for each file revision. The later revisions appear first in the list.
* If <code>null</code>, then there were too many revisions at that path.
*
@ -889,7 +929,7 @@ class Client
* Takes a copy of the file at the given revision and saves it over the current copy. This
* will create a new revision, but the file contents will match the revision you specified.
*
* See <a href="https://www.dropbox.com/developers/core/api#restore">/restore</a>.
* See <a href="https://www.dropbox.com/developers/core/docs#restore">/restore</a>.
*
* @param string $path
* The Dropbox path of the file to restore (UTF-8).
@ -898,7 +938,7 @@ class Client
* The revision to restore the contents to.
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
* object</a>
*
* @throws Exception
@ -922,6 +962,8 @@ class Client
/**
* Returns metadata for all files and folders whose filename matches the query string.
*
* See <a href="https://www.dropbox.com/developers/core/docs#search">/search</a>.
*
* @param string $basePath
* The path to limit the search to (UTF-8). Pass in "/" to search everything.
*
@ -936,7 +978,7 @@ class Client
* Whether to include deleted files in the results.
*
* @return mixed
* A list of <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
* A list of <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
* objects</a> of files that match the search query.
*
* @throws Exception
@ -967,7 +1009,7 @@ class Client
* used without authentication. The preview page may contain a thumbnail or some other
* preview of the file, along with a download link to download the actual file.
*
* See <a href="https://www.dropbox.com/developers/core/api#shares">/shares</a>.
* See <a href="https://www.dropbox.com/developers/core/docs#shares">/shares</a>.
*
* @param string $path
* The Dropbox path to the file or folder you want to create a shareable link to (UTF-8).
@ -999,13 +1041,15 @@ class Client
* Creates and returns a direct link to a file. This link can be used without authentication.
* This link will expire in a few hours.
*
* See <a href="https://www.dropbox.com/developers/core/docs#media">/media</a>.
*
* @param string $path
* The Dropbox path to a file or folder (UTF-8).
*
* @return array
* A <code>list(string $url, \DateTime $expires) where <code>$url</code> is a direct link to
* the requested file and <code>$expires</code> is a standard PHP <code>\DateTime</code>
* representing when <code>$url</code> will stop working.
* A <code>list(string $url, \DateTime $expires)</code> where <code>$url</code> is a direct
* link to the requested file and <code>$expires</code> is a standard PHP
* <code>\DateTime</code> representing when <code>$url</code> will stop working.
*
* @throws Exception
*/
@ -1035,6 +1079,8 @@ class Client
* another account and call <code>copyFromCopyRef</code> using the copy ref. (You need to use
* the same app key both times.)
*
* See <a href="https://www.dropbox.com/developers/core/docs#copy_ref">/copy_ref</a>.
*
* @param string path
* The Dropbox path of the file or folder you want to create a copy ref for (UTF-8).
*
@ -1061,6 +1107,8 @@ class Client
/**
* Gets a thumbnail image representation of the file at the given path.
*
* See <a href="https://www.dropbox.com/developers/core/docs#thumbnails">/thumbnails</a>.
*
* @param string $path
* The path to the file you want a thumbnail for (UTF-8).
*
@ -1080,7 +1128,7 @@ class Client
* @return array|null
* If the file exists, you'll get <code>list(array $metadata, string $data)</code> where
* <code>$metadata</code> is the file's
* <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata object</a>
* <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
* and $data is the raw data for the thumbnail image. If the file doesn't exist, you'll
* get <code>null</code>.
*
@ -1098,8 +1146,7 @@ class Client
throw new \InvalidArgumentException("Invalid 'size': ".self::q($format));
}
$url = RequestUtil::buildUrl(
$this->userLocale,
$url = $this->buildUrlForGetOrPut(
$this->contentHost,
$this->appendFilePath("1/thumbnails", $path),
array("size" => $size, "format" => $format));
@ -1120,6 +1167,8 @@ class Client
/**
* Copies a file or folder to a new location
*
* See <a href="https://www.dropbox.com/developers/core/docs#fileops-copy">/fileops/copy</a>.
*
* @param string $fromPath
* The Dropbox path of the file or folder you want to copy (UTF-8).
*
@ -1127,7 +1176,7 @@ class Client
* The destination Dropbox path (UTF-8).
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
* object</a> for the new file or folder.
*
* @throws Exception
@ -1155,6 +1204,8 @@ class Client
* Creates a file or folder based on an existing copy ref (possibly from a different Dropbox
* account).
*
* See <a href="https://www.dropbox.com/developers/core/docs#fileops-copy">/fileops/copy</a>.
*
* @param string $copyRef
* A copy ref obtained via the {@link createCopyRef()} call.
*
@ -1162,7 +1213,7 @@ class Client
* The Dropbox path you want to copy the file or folder to (UTF-8).
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
* object</a> for the new file or folder.
*
* @throws Exception
@ -1190,12 +1241,14 @@ class Client
/**
* Creates a folder.
*
* See <a href="https://www.dropbox.com/developers/core/docs#fileops-create-folder">/fileops/create_folder</a>.
*
* @param string $path
* The Dropbox path at which to create the folder (UTF-8).
*
* @return array|null
* If successful, you'll get back the
* <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata object</a>
* <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
* for the newly-created folder. If not successful, you'll get <code>null</code>.
*
* @throws Exception
@ -1221,13 +1274,13 @@ class Client
/**
* Deletes a file or folder
*
* See <a href="https://www.dropbox.com/developers/core/api#fileops-delete">/fileops/delete</a>.
* See <a href="https://www.dropbox.com/developers/core/docs#fileops-delete">/fileops/delete</a>.
*
* @param string $path
* The Dropbox path of the file or folder to delete (UTF-8).
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
* object</a> for the deleted file or folder.
*
* @throws Exception
@ -1252,7 +1305,7 @@ class Client
/**
* Moves a file or folder to a new location.
*
* See <a href="https://www.dropbox.com/developers/core/api#fileops-move">/fileops/move</a>.
* See <a href="https://www.dropbox.com/developers/core/docs#fileops-move">/fileops/move</a>.
*
* @param string $fromPath
* The source Dropbox path (UTF-8).
@ -1261,7 +1314,7 @@ class Client
* The destination Dropbox path (UTF-8).
*
* @return mixed
* The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
* The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
* object</a> for the destination file or folder.
*
* @throws Exception
@ -1286,14 +1339,39 @@ class Client
}
/**
* Build a URL for making a GET or PUT request. Will add the "locale"
* parameter.
*
* @param string $host
* Either the "API" or "API content" hostname from {@link getHost()}.
* @param string $path
* The "path" part of the URL. For example, "/account/info".
* @param array|null $params
* URL parameters. For POST requests, do not put the parameters here.
* Include them in the request body instead.
*
* @return string
*/
function buildUrlForGetOrPut($host, $path, $params = null)
{
return RequestUtil::buildUrlForGetOrPut($this->userLocale, $host, $path, $params);
}
/**
* Perform an OAuth-2-authorized GET request to the Dropbox API. Will automatically
* fill in "User-Agent" and "locale" as well.
*
* @param string $host
* Either the "API" or "API content" hostname from {@link getHost()}.
* @param string $path
* The "path" part of the URL. For example, "/account/info".
* @param array|null $params
* GET parameters.
* @return HttpResponse
*
* @throws Exception
*/
private function doGet($host, $path, $params = null)
function doGet($host, $path, $params = null)
{
Checker::argString("host", $host);
Checker::argString("path", $path);
@ -1302,14 +1380,20 @@ class Client
}
/**
* Perform an OAuth-2-authorized POST request to the Dropbox API. Will automatically
* fill in "User-Agent" and "locale" as well.
*
* @param string $host
* Either the "API" or "API content" hostname from {@link getHost()}.
* @param string $path
* The "path" part of the URL. For example, "/commit_chunked_upload".
* @param array|null $params
* POST parameters.
* @return HttpResponse
*
* @throws Exception
*/
private function doPost($host, $path, $params = null)
function doPost($host, $path, $params = null)
{
Checker::argString("host", $host);
Checker::argString("path", $path);
@ -1318,12 +1402,17 @@ class Client
}
/**
* Create a {@link Curl} object that is pre-configured with {@link getClientIdentifier()},
* and the proper OAuth 2 "Authorization" header.
*
* @param string $url
* Generate this URL using {@link buildUrl()}.
*
* @return Curl
*/
private function mkCurl($url, $maxsize=0)
function mkCurl($url)
{
return RequestUtil::mkCurl($this->clientIdentifier, $url, $this->accessToken, $maxsize);
return RequestUtil::mkCurlWithOAuth($this->clientIdentifier, $url, $this->accessToken);
}
/**
@ -1360,7 +1449,52 @@ class Client
static function getField($j, $fieldName)
{
if (!array_key_exists($fieldName, $j)) throw new Exception_BadResponse(
"missing field \"$fieldName\": $body");
"missing field \"$fieldName\" in ".self::q($j));
return $j[$fieldName];
}
/**
* Given an OAuth 2 access token, returns <code>null</code> if it is well-formed (though
* not necessarily valid). Otherwise, returns a string describing what's wrong with it.
*
* @param string $s
*
* @return string
*/
static function getAccessTokenError($s)
{
if ($s === null) return "can't be null";
if (strlen($s) === 0) return "can't be empty";
if (preg_match('@[^-=_~/A-Za-z0-9\.\+]@', $s) === 1) return "contains invalid character";
return null;
}
/**
* @internal
*/
static function checkAccessTokenArg($argName, $accessToken)
{
$error = self::getAccessTokenError($accessToken);
if ($error !== null) throw new \InvalidArgumentException("'$argName' invalid: $error");
}
/**
* @internal
*/
static function getClientIdentifierError($s)
{
if ($s === null) return "can't be null";
if (strlen($s) === 0) return "can't be empty";
if (preg_match('@[\x00-\x1f\x7f]@', $s) === 1) return "contains control character";
return null;
}
/**
* @internal
*/
static function checkClientIdentifierArg($argName, $accessToken)
{
$error = self::getClientIdentifierError($accessToken);
if ($error !== null) throw new \InvalidArgumentException("'$argName' invalid: $error");
}
}

View file

@ -11,18 +11,14 @@ final class Curl
/** @var resource */
public $handle;
public $maxsize;
/** @var string[] */
private $headers = array();
/**
* @param string $url
*/
function __construct($url, $maxsize = 0)
function __construct($url)
{
$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");
@ -30,28 +26,35 @@ final class Curl
$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");
// NOTE: Though we turn on all the correct SSL settings, many PHP installations
// don't respect these settings. Run "examples/test-ssl.php" to run some basic
// SSL tests to see how well your PHP implementation behaves.
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'));
// Use our own certificate list.
$this->set(CURLOPT_SSL_VERIFYPEER, true); // Enforce certificate validation
$this->set(CURLOPT_SSL_VERIFYHOST, 2); // Enforce hostname validation
// Force the use of TLS (SSL v2 and v3 are not secure).
// TODO: Use "CURL_SSLVERSION_TLSv1" instead of "1" once we can rely on PHP 5.5+.
$this->set(CURLOPT_SSLVERSION, 1);
// Limit the set of ciphersuites used.
global $sslCiphersuiteList;
if ($sslCiphersuiteList !== null) {
$this->set(CURLOPT_SSL_CIPHER_LIST, $sslCiphersuiteList);
}
// Certificate file.
$this->set(CURLOPT_CAINFO, __DIR__.'/certs/trusted-certs.crt');
// Certificate folder. If not specified, some PHP installations will use
// the system default, even when CURLOPT_CAINFO is specified.
$this->set(CURLOPT_CAPATH, __DIR__.'/certs/');
// 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
*/
@ -66,9 +69,7 @@ final class Curl
$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));
}
throw new Exception_NetworkIO("Error executing HTTP request: " . curl_error($this->handle));
}
$statusCode = curl_getinfo($this->handle, CURLINFO_HTTP_CODE);
@ -90,3 +91,35 @@ final class Curl
curl_close($this->handle);
}
}
// Different cURL SSL backends use different names for ciphersuites.
$curlVersion = \curl_version();
$curlSslBackend = $curlVersion['ssl_version'];
if (\substr_compare($curlSslBackend, "NSS/", 0, strlen("NSS/")) === 0) {
// Can't figure out how to reliably set ciphersuites for NSS.
$sslCiphersuiteList = null;
}
else {
// Use the OpenSSL names for all other backends. We may have to
// refine this if users report errors.
$sslCiphersuiteList =
'ECDHE-RSA-AES256-GCM-SHA384:'.
'ECDHE-RSA-AES128-GCM-SHA256:'.
'ECDHE-RSA-AES256-SHA384:'.
'ECDHE-RSA-AES128-SHA256:'.
'ECDHE-RSA-AES256-SHA:'.
'ECDHE-RSA-AES128-SHA:'.
'ECDHE-RSA-RC4-SHA:'.
'DHE-RSA-AES256-GCM-SHA384:'.
'DHE-RSA-AES128-GCM-SHA256:'.
'DHE-RSA-AES256-SHA256:'.
'DHE-RSA-AES128-SHA256:'.
'DHE-RSA-AES256-SHA:'.
'DHE-RSA-AES128-SHA:'.
'AES256-GCM-SHA384:'.
'AES128-GCM-SHA256:'.
'AES256-SHA256:'.
'AES128-SHA256:'.
'AES256-SHA:'.
'AES128-SHA';
}

View file

@ -33,7 +33,6 @@ class CurlStreamRelay
$this->errorData[] = $data;
} else {
fwrite($this->outStream, $data);
ob_flush();
}
return strlen($data);
@ -44,4 +43,3 @@ class CurlStreamRelay
return implode($this->errorData);
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Dropbox;
/**
* Use with {@link OAuth1Upgrader} to convert old OAuth 1 access tokens
* to OAuth 2 access tokens. This SDK doesn't support using OAuth 1
* access tokens for regular API calls.
*/
class OAuth1AccessToken
{
/**
* The OAuth 1 access token key.
*
* @return string
*/
function getKey() { return $this->key; }
/** @var string */
private $key;
/**
* The OAuth 1 access token secret.
*
* Make sure that this is kept a secret. Someone with your app secret can impesonate your
* application. People sometimes ask for help on the Dropbox API forums and
* copy/paste code that includes their app secret. Do not do that.
*
* @return string
*/
function getSecret() { return $this->secret; }
/** @var secret */
private $secret;
/**
* Constructor.
*
* @param string $key
* {@link getKey()}
* @param string $secret
* {@link getSecret()}
*/
function __construct($key, $secret)
{
AppInfo::checkKeyArg($key);
AppInfo::checkSecretArg($secret);
$this->key = $key;
$this->secret = $secret;
}
/**
* Use this to check that a function argument is of type <code>AppInfo</code>
*
* @internal
*/
static function checkArg($argName, $argValue)
{
if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Dropbox;
/**
* Lets you convert OAuth 1 access tokens to OAuth 2 access tokens. First call {@link
* OAuth1AccessTokenUpgrader::createOAuth2AccessToken()} to get an OAuth 2 access token.
* If that succeeds, call {@link OAuth1AccessTokenUpgrader::disableOAuth1AccessToken()}
* to disable the OAuth 1 access token.
*
* <code>
* use \Dropbox as dbx;
* $appInfo = dbx\AppInfo::loadFromJsonFile(...);
* $clientIdentifier = "my-app/1.0";
* $oauth1AccessToken = dbx\OAuth1AccessToken(...);
*
* $upgrader = new dbx\OAuth1AccessTokenUpgrader($appInfo, $clientIdentifier, ...);
* $oauth2AccessToken = $upgrader->getOAuth2AccessToken($oauth1AccessToken);
* $upgrader->disableOAuth1AccessToken($oauth1AccessToken);
* </code>
*/
class OAuth1Upgrader extends AuthBase
{
/**
* Given an existing active OAuth 1 access token, make a Dropbox API call to get a new OAuth 2
* access token that represents the same user and app.
*
* See <a href="https://www.dropbox.com/developers/core/docs#oa1-from-oa1">/oauth2/token_from_oauth1</a>.
*
* @param OAuth1AccessToken $oauth1AccessToken
*
* @return string
* The OAuth 2 access token.
*
* @throws Exception
*/
function createOAuth2AccessToken($oauth1AccessToken)
{
OAuth1AccessToken::checkArg("oauth1AccessToken", $oauth1AccessToken);
$response = self::doPost($oauth1AccessToken, "1/oauth2/token_from_oauth1");
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
$parts = RequestUtil::parseResponseJson($response->body);
if (!array_key_exists('token_type', $parts) || !is_string($parts['token_type'])) {
throw new Exception_BadResponse("Missing \"token_type\" field.");
}
$tokenType = $parts['token_type'];
if (!array_key_exists('access_token', $parts) || !is_string($parts['access_token'])) {
throw new Exception_BadResponse("Missing \"access_token\" field.");
}
$accessToken = $parts['access_token'];
if ($tokenType !== "Bearer" && $tokenType !== "bearer") {
throw new Exception_BadResponse("Unknown \"token_type\"; expecting \"Bearer\", got "
.Client::q($tokenType));
}
return $accessToken;
}
/**
* Make a Dropbox API call to disable the given OAuth 1 access token.
*
* See <a href="https://www.dropbox.com/developers/core/docs#disable-token">/disable_access_token</a>.
*
* @param OAuth1AccessToken $oauth1AccessToken
*
* @throws Exception
*/
function disableOAuth1AccessToken($oauth1AccessToken)
{
OAuth1AccessToken::checkArg("oauth1AccessToken", $oauth1AccessToken);
$response = self::doPost($oauth1AccessToken, "1/disable_access_token");
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
}
/**
* @param OAuth1AccessToken $oauth1AccessToken
* @param string $path
*
* @return HttpResponse
*
* @throws Exception
*/
private function doPost($oauth1AccessToken, $path)
{
// Construct the OAuth 1 header.
$signature = rawurlencode($this->appInfo->getSecret()) . "&" . rawurlencode($oauth1AccessToken->getSecret());
$authHeaderValue = "OAuth oauth_signature_method=\"PLAINTEXT\""
. ", oauth_consumer_key=\"" . rawurlencode($this->appInfo->getKey()) . "\""
. ", oauth_token=\"" . rawurlencode($oauth1AccessToken->getKey()) . "\""
. ", oauth_signature=\"" . $signature . "\"";
return RequestUtil::doPostWithSpecificAuth(
$this->clientIdentifier, $authHeaderValue, $this->userLocale,
$this->appInfo->getHost()->getApi(),
$path,
null);
}
}

View file

@ -141,6 +141,19 @@ final class Path
if ($error !== null) throw new \InvalidArgumentException("'$argName'': bad path: $error: ".var_export($value, true));
}
/**
* @internal
*
* @param string $argName
* @param mixed $value
* @throws \InvalidArgumentException
*/
static function checkArgOrNull($argName, $value)
{
if ($value === null) return;
self::checkArg($argName, $value);
}
/**
* @internal
*

View file

@ -9,10 +9,17 @@ if (!function_exists('json_decode')) {
throw new \Exception("The Dropbox SDK requires the JSON PHP extension, but it looks like you don't have it (couldn't find function \"json_decode\"). Library: \"" . __FILE__ . "\".");
}
// If mbstring.func_overload is set, it changes the behavior of the standard string functions in
// ways that makes this library break.
$mbstring_func_overload = ini_get("mbstring.func_overload");
if ($mbstring_func_overload & 2 == 2) {
throw new \Exception("The Dropbox SDK doesn't work when mbstring.func_overload is set to overload the standard string functions (value = ".var_export($mbstring_func_overload, true)."). Library: \"" . __FILE__ . "\".");
}
if (strlen((string) PHP_INT_MAX) < 19) {
// Looks like we're running on a 32-bit build of PHP. This could cause problems because some of the numbers
// we use (file sizes, quota, etc) can be larger than 32-bit ints can handle.
//throw new \Exception("The Dropbox SDK uses 64-bit integers, but it looks like we're running on a version of PHP that doesn't support 64-bit integers (PHP_INT_MAX=" . ((string) PHP_INT_MAX) . "). Library: \"" . __FILE__ . "\"");
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__ . "\"");
}
/**
@ -27,7 +34,7 @@ final class RequestUtil
* @param array $params
* @return string
*/
static function buildUrl($userLocale, $host, $path, $params = null)
static function buildUrlForGetOrPut($userLocale, $host, $path, $params = null)
{
$url = self::buildUri($host, $path);
$url .= "?locale=" . rawurlencode($userLocale);
@ -69,9 +76,9 @@ final class RequestUtil
* @param string $url
* @return Curl
*/
static function mkCurlWithoutAuth($clientIdentifier, $url, $maxsize=0)
static function mkCurl($clientIdentifier, $url)
{
$curl = new Curl($url, $maxsize);
$curl = new Curl($url);
$curl->set(CURLOPT_CONNECTTIMEOUT, 10);
@ -86,17 +93,28 @@ final class RequestUtil
return $curl;
}
/**
* @param string $clientIdentifier
* @param string $url
* @param string $authHeaderValue
* @return Curl
*/
static function mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue)
{
$curl = self::mkCurl($clientIdentifier, $url);
$curl->addHeader("Authorization: $authHeaderValue");
return $curl;
}
/**
* @param string $clientIdentifier
* @param string $url
* @param string $accessToken
* @return Curl
*/
static function mkCurl($clientIdentifier, $url, $accessToken, $maxsize=0)
static function mkCurlWithOAuth($clientIdentifier, $url, $accessToken)
{
$curl = self::mkCurlWithoutAuth($clientIdentifier, $url, $maxsize);
$curl->addHeader("Authorization: Bearer $accessToken");
return $curl;
return self::mkCurlWithAuth($clientIdentifier, $url, "Bearer $accessToken");
}
static function buildPostBody($params)
@ -123,6 +141,7 @@ final class RequestUtil
}
/**
* @param string $clientIdentifier
* @param string $accessToken
* @param string $userLocale
* @param string $host
@ -142,7 +161,7 @@ final class RequestUtil
if ($params === null) $params = array();
$params['locale'] = $userLocale;
$curl = self::mkCurl($clientIdentifier, $url, $accessToken);
$curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
$curl->set(CURLOPT_POST, true);
$curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
@ -151,6 +170,36 @@ final class RequestUtil
}
/**
* @param string $clientIdentifier
* @param string $authHeaderValue
* @param string $userLocale
* @param string $host
* @param string $path
* @param array|null $params
*
* @return HttpResponse
*
* @throws Exception
*/
static function doPostWithSpecificAuth($clientIdentifier, $authHeaderValue, $userLocale, $host, $path, $params = null)
{
Checker::argStringNonEmpty("authHeaderValue", $authHeaderValue);
$url = self::buildUri($host, $path);
if ($params === null) $params = array();
$params['locale'] = $userLocale;
$curl = self::mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue);
$curl->set(CURLOPT_POST, true);
$curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));
$curl->set(CURLOPT_RETURNTRANSFER, true);
return $curl->exec();
}
/**
* @param string $clientIdentifier
* @param string $accessToken
* @param string $userLocale
* @param string $host
@ -165,9 +214,9 @@ final class RequestUtil
{
Checker::argStringNonEmpty("accessToken", $accessToken);
$url = self::buildUrl($userLocale, $host, $path, $params);
$url = self::buildUrlForGetOrPut($userLocale, $host, $path, $params);
$curl = self::mkCurl($clientIdentifier, $url, $accessToken);
$curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
$curl->set(CURLOPT_HTTPGET, true);
$curl->set(CURLOPT_RETURNTRANSFER, true);
@ -181,7 +230,7 @@ final class RequestUtil
*/
static function parseResponseJson($responseBody)
{
$obj = json_decode($responseBody, TRUE, 10);
$obj = json_decode($responseBody, true, 10);
if ($obj === null) {
throw new Exception_BadResponse("Got bad JSON from server: $responseBody");
}
@ -203,7 +252,7 @@ final class RequestUtil
if ($sc === 500 || $sc === 502) return new Exception_ServerError($message);
if ($sc === 503) return new Exception_RetryLater($message);
return new Exception_BadResponse("Unexpected $message");
return new Exception_BadResponseCode("Unexpected $message", $sc);
}
/**

View file

@ -0,0 +1,132 @@
<?php
namespace Dropbox;
/**
* Call the <code>test()</code> method.
*/
class SSLTester
{
/**
* Peforms a few basic tests of your PHP installation's SSL implementation to see
* if it insecure in an obvious way. Results are written with "echo" and the output
* is HTML-safe.
*
* @return bool
* Returns <code>true</code> if all the tests passed.
*/
static function test()
{
$hostOs = php_uname('s').' '.php_uname('r');
$phpVersion = phpversion();
$curlVersionInfo = \curl_version();
$curlVersion = $curlVersionInfo['version'];
$curlSslBackend = $curlVersionInfo['ssl_version'];
echo "-----------------------------------------------------------------------------\n";
echo "Testing your PHP installation's SSL implementation for a few obvious problems...\n";
echo "-----------------------------------------------------------------------------\n";
echo "- Host OS: $hostOs\n";
echo "- PHP version: $phpVersion\n";
echo "- cURL version: $curlVersion\n";
echo "- cURL SSL backend: $curlSslBackend\n";
echo "Basic SSL tests\n";
$basicFailures = self::testMulti(array(
array("www.dropbox.com", 'testAllowed'),
array("www.digicert.com", 'testAllowed'),
array("www.v.dropbox.com", 'testHostnameMismatch'),
array("testssl-expire.disig.sk", 'testUntrustedCert'),
));
echo "Pinned certificate tests\n";
$pinnedCertFailures = self::testMulti(array(
array("www.verisign.com", 'testUntrustedCert'),
array("www.globalsign.fr", 'testUntrustedCert'),
));
if ($basicFailures) {
echo "-----------------------------------------------------------------------------\n";
echo "WARNING: Your PHP installation's SSL support is COMPLETELY INSECURE.\n";
echo "Your app's communication with the Dropbox API servers can be viewed and\n";
echo "manipulated by others. Try upgrading your version of PHP.\n";
echo "-----------------------------------------------------------------------------\n";
return false;
}
else if ($pinnedCertFailures) {
echo "-----------------------------------------------------------------------------\n";
echo "WARNING: Your PHP installation's cURL module doesn't support SSL certificate\n";
echo "pinning, which is an important security feature of the Dropbox SDK.\n";
echo "\n";
echo "This SDK uses CURLOPT_CAINFO and CURLOPT_CAPATH to tell PHP cURL to only trust\n";
echo "our custom certificate list. But your PHP installation's cURL module seems to\n";
echo "trust certificates that aren't on that list.\n";
echo "\n";
echo "More information on SSL certificate pinning:\n";
echo "https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#What_Is_Pinning.3F\n";
echo "-----------------------------------------------------------------------------\n";
return false;
}
else {
return true;
}
}
private static function testMulti($tests)
{
$anyFailed = false;
foreach ($tests as $test) {
list($host, $testType) = $test;
echo " - ".str_pad("$testType ($host) ", 50, ".");
$url = "https://$host/";
$passed = self::$testType($url);
if ($passed) {
echo " ok\n";
} else {
echo " FAILED\n";
$anyFailed = true;
}
}
return $anyFailed;
}
private static function testPinnedCert()
{
}
private static function testAllowed($url)
{
$curl = RequestUtil::mkCurl("test-ssl", $url);
$curl->set(CURLOPT_RETURNTRANSFER, true);
$curl->exec();
return true;
}
private static function testUntrustedCert($url)
{
return self::testDisallowed($url, 'Error executing HTTP request: SSL certificate problem, verify that the CA cert is OK');
}
private static function testHostnameMismatch($url)
{
return self::testDisallowed($url, 'Error executing HTTP request: SSL certificate problem: Invalid certificate chain');
}
private static function testDisallowed($url, $expectedExceptionMessage)
{
$curl = RequestUtil::mkCurl("test-ssl", $url);
$curl->set(CURLOPT_RETURNTRANSFER, true);
try {
$curl->exec();
}
catch (Exception_NetworkIO $ex) {
if (strpos($ex->getMessage(), $expectedExceptionMessage) == 0) {
return true;
} else {
throw $ex;
}
}
return false;
}
}

View file

@ -22,14 +22,16 @@ class Security
{
// 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().".");
throw new \InvalidArgumentException("Expecting 2 args, got ".func_num_args().".");
}
Checker::argString("a", $a);
Checker::argString("b", $b);
if (strlen($a) !== strlen($b)) return false;
$len = strlen($a);
if (strlen($b) !== $len) return false;
$result = 0;
for ($i = 0; $i < strlen($a); $i++) {
for ($i = 0; $i < $len; $i++) {
$result |= ord($a[$i]) ^ ord($b[$i]);
}
return $result === 0;
@ -60,6 +62,6 @@ class Security
// 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");
throw new \Exception("no suitable random number source available");
}
}

View file

@ -130,6 +130,8 @@ class WebAuth extends WebAuthBase
* the constructor. This CSRF token will be checked on {@link finish()} to prevent
* request forgery.
*
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
*
* @param string|null $urlState
* Any data you would like to keep in the URL through the authorization process.
* This exact state will be returned to you by {@link finish()}.
@ -159,15 +161,12 @@ class WebAuth extends WebAuthBase
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.
*
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
*
* @param array $queryParams
* The query parameters on the GET request to your redirect URI.
*
@ -201,10 +200,10 @@ class WebAuth extends WebAuthBase
Checker::argString("queryParams['state']", $state);
$error = null;
$errorDescription = 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);
@ -230,8 +229,6 @@ class WebAuth extends WebAuthBase
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) {

View file

@ -4,78 +4,11 @@ namespace Dropbox;
/**
* The base class for the two auth options.
*/
class WebAuthBase
class WebAuthBase extends AuthBase
{
/**
* 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(
return RequestUtil::buildUrlForGetOrPut(
$this->userLocale,
$this->appInfo->getHost()->getWeb(),
"1/oauth2/authorize",
@ -89,39 +22,33 @@ class WebAuthBase
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,
);
// This endpoint requires "Basic" auth.
$clientCredentials = $this->appInfo->getKey().":".$this->appInfo->getSecret();
$authHeaderValue = "Basic ".base64_encode($clientCredentials);
$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();
$response = RequestUtil::doPostWithSpecificAuth(
$this->clientIdentifier, $authHeaderValue, $this->userLocale,
$this->appInfo->getHost()->getApi(),
"1/oauth2/token",
array(
"grant_type" => "authorization_code",
"code" => $code,
"redirect_uri" => $originalRedirectUri,
));
if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
$parts = RequestUtil::parseResponseJson($response->body);
if (!array_key_exists('token_type', $parts) or !is_string($parts['token_type'])) {
if (!array_key_exists('token_type', $parts) || !is_string($parts['token_type'])) {
throw new Exception_BadResponse("Missing \"token_type\" field.");
}
$tokenType = $parts['token_type'];
if (!array_key_exists('access_token', $parts) or !is_string($parts['access_token'])) {
if (!array_key_exists('access_token', $parts) || !is_string($parts['access_token'])) {
throw new Exception_BadResponse("Missing \"access_token\" field.");
}
$accessToken = $parts['access_token'];
if (!array_key_exists('uid', $parts) or !is_string($parts['uid'])) {
if (!array_key_exists('uid', $parts) || !is_string($parts['uid'])) {
throw new Exception_BadResponse("Missing \"uid\" string field.");
}
$userId = $parts['uid'];

View file

@ -10,8 +10,6 @@ namespace Dropbox;
class WebAuthException_BadState extends \Exception
{
/**
* @param string $message
*
* @internal
*/
function __construct()

View file

@ -23,7 +23,8 @@ namespace Dropbox;
* 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: "));
* print("Enter the authorization code here: ");
* $code = \trim(\fgets(STDIN));
*
* try {
* list($accessToken, $userId) = $webAuth->finish($code);
@ -43,8 +44,10 @@ class WebAuthNoRedirect extends WebAuthBase
* copy/paste that code into your application so your app can pass it to
* {@link finish}.
*
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
*
* @return string
* An authorization URL. Redirect the user's browser to this URL. After the user decides
* An authorization URL. Direct the user's browser to this URL. After the user decides
* whether to authorize your app or not, Dropbox will show the user an authorization code,
* which the user will need to give to your application (e.g. via copy/paste).
*/
@ -58,6 +61,8 @@ class WebAuthNoRedirect extends WebAuthBase
* approved your app, was presented with an authorization code by Dropbox, and has copy/paste'd
* that authorization code into your app.
*
* See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
*
* @param string $code
* The authorization code provided to the user by Dropbox.
*
@ -75,4 +80,3 @@ class WebAuthNoRedirect extends WebAuthBase
return $this->_finish($code, null);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
<?php
// Throw exceptions on all PHP errors/warnings/notices.
// We'd like to do this in all situations (and not just when running tests), but
// this is a global setting and other code might not be ready for it.
/** @internal */
function error_to_exception($errno, $errstr, $errfile, $errline, $context)
{
// If the error is being suppressed with '@', don't throw an exception.
if (error_reporting() === 0) return;
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('error_to_exception');

View file

@ -1,341 +0,0 @@
# 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-----

View file

@ -143,4 +143,11 @@ class Catalog_beets extends Beets\Catalog
return $this->beetsdb;
}
public function format()
{
parent::format();
$this->f_info = $this->beetsdb;
$this->f_full_info = $this->f_info;
}
}

View file

@ -128,6 +128,22 @@ class Catalog_dropbox extends Catalog
}
public function isReady()
{
return (!empty($this->authtoken));
}
public function show_ready_process()
{
$this->showAuthToken();
}
public function perform_ready()
{
$this->authcode = $_REQUEST['authcode'];
$this->completeAuthToken();
}
public $apikey;
public $secret;
public $path;
@ -205,10 +221,11 @@ class Catalog_dropbox extends Catalog
$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='" . get_current_path() . "' method='post' enctype='multipart/form-data'>";
if ($_POST['action']) {
echo "<input type='hidden' name='action' value='add_to_catalog' />";
if ($_REQUEST['action']) {
echo "<input type='hidden' name='action' value='" . scrub_in($_REQUEST['action']) . "' />";
echo "<input type='hidden' name='catalogs[]' value='". $this->id ."' />";
}
echo "<input type='hidden' name='perform_ready' value='true' />";
echo "<input type='text' name='authcode' />";
echo "<input type='submit' value='Ok' />";
echo "</form>";

View file

@ -117,6 +117,22 @@ class Catalog_soundcloud extends Catalog
}
public function isReady()
{
return (!empty($this->authtoken));
}
public function show_ready_process()
{
$this->showAuthToken();
}
public function perform_ready()
{
$this->authcode = $_REQUEST['authcode'];
$this->completeAuthToken();
}
public $userid;
public $secret;
public $authtoken;
@ -183,10 +199,11 @@ class Catalog_soundcloud extends Catalog
$authurl = $api->getAuthorizeUrl(array('scope' => 'non-expiring'));
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='" . get_current_path() . "' method='post' enctype='multipart/form-data'>";
if ($_POST['action']) {
echo "<input type='hidden' name='action' value='add_to_catalog' />";
if ($_REQUEST['action']) {
echo "<input type='hidden' name='action' value='" . scrub_in($_REQUEST['action']) . "' />";
echo "<input type='hidden' name='catalogs[]' value='". $this->id ."' />";
}
echo "<input type='hidden' name='perform_ready' value='true' />";
echo "<input type='text' name='authcode' />";
echo "<input type='submit' value='Ok' />";
echo "</form>";

View file

@ -58,7 +58,9 @@ switch ($worker) {
if ($_REQUEST['catalogs']) {
foreach ($_REQUEST['catalogs'] as $catalog_id) {
$catalog = Catalog::create_from_id($catalog_id);
$catalog->add_to_catalog($_POST);
if ($catalog !== null) {
$catalog->add_to_catalog($_POST);
}
}
}
break;
@ -68,7 +70,9 @@ switch ($worker) {
if (isset($_REQUEST['catalogs'])) {
foreach ($_REQUEST['catalogs'] as $catalog_id) {
$catalog = Catalog::create_from_id($catalog_id);
$catalog->verify_catalog();
if ($catalog !== null) {
$catalog->verify_catalog();
}
}
}
break;
@ -80,9 +84,11 @@ switch ($worker) {
/* This runs the clean/verify/add in that order */
foreach ($_REQUEST['catalogs'] as $catalog_id) {
$catalog = Catalog::create_from_id($catalog_id);
$catalog->clean_catalog();
$catalog->verify_catalog();
$catalog->add_to_catalog();
if ($catalog !== null) {
$catalog->clean_catalog();
$catalog->verify_catalog();
$catalog->add_to_catalog();
}
}
Dba::optimize_tables();
break;
@ -93,7 +99,9 @@ switch ($worker) {
if (isset($_REQUEST['catalogs'])) {
foreach ($_REQUEST['catalogs'] as $catalog_id) {
$catalog = Catalog::create_from_id($catalog_id);
$catalog->clean_catalog();
if ($catalog !== null) {
$catalog->clean_catalog();
}
} // end foreach catalogs
Dba::optimize_tables();
}
@ -104,7 +112,9 @@ switch ($worker) {
if ($_REQUEST['add_path'] != '/' AND strlen($_REQUEST['add_path'])) {
if ($catalog_id = Catalog_local::get_from_path($_REQUEST['add_path'])) {
$catalog = Catalog::create_from_id($catalog_id);
$catalog->add_to_catalog(array('subdirectory'=>$_REQUEST['add_path']));
if ($catalog !== null) {
$catalog->add_to_catalog(array('subdirectory'=>$_REQUEST['add_path']));
}
}
} // end if add
@ -123,11 +133,13 @@ switch ($worker) {
case 'add_catalog':
$catalog_id = intval($_REQUEST['catalog_id']);
$catalog = Catalog::create_from_id($catalog_id);
// Run our initial add
$catalog->add_to_catalog($options);
if ($catalog !== null) {
// Run our initial add
$catalog->add_to_catalog($options);
if (!defined('SSE_OUTPUT')) {
Error::display('catalog_add');
if (!defined('SSE_OUTPUT')) {
Error::display('catalog_add');
}
}
break;
case 'gather_media_art':
@ -136,9 +148,11 @@ switch ($worker) {
// Iterate throught the catalogs and gather as needed
foreach ($catalogs as $catalog_id) {
$catalog = Catalog::create_from_id($catalog_id);
require AmpConfig::get('prefix') . '/templates/show_gather_art.inc.php';
flush();
$catalog->gather_art();
if ($catalog !== null) {
require AmpConfig::get('prefix') . '/templates/show_gather_art.inc.php';
flush();
$catalog->gather_art();
}
}
break;
}