<?php
defined("ABSPATH") or die("");
/**
 * DropPHP - A simple Dropbox client that works without cURL.
 *
 * http://fabi.me/en/php-projects/dropphp-dropbox-api-client/
 * 
 * 
 * @author     Fabian Schlieper <fabian@fabi.me>
 * @copyright  Fabian Schlieper 2014
 * @version    1.7.1
 * @license    See LICENSE
 *
 */

require_once(dirname(__FILE__) . "/OAuthSimple.php");

if (!class_exists('DUP_PRO_DropboxClient_UploadInfo'))
{

    class DUP_PRO_DropboxClient_UploadInfo
    {
        public $upload_id;
        public $next_offset;
        public $error_details = null;
        public $file_meta = null;       // Is non null if upload complete

    }

}

if (!class_exists('DUP_PRO_DropboxClient'))
{

    class DUP_PRO_DropboxClient
    {
        const API_URL = "https://api.dropbox.com/1/";
        const API_CONTENT_URL = "https://api-content.dropbox.com/1/";
        const BUFFER_SIZE = 4096;
        const MAX_UPLOAD_CHUNK_SIZE = 150000000; // 150MB
        const UPLOAD_CHUNK_SIZE = 4000000; // 4MB

        private $appParams;
        private $consumerToken;
        private $requestToken;
        private $accessToken;
        private $locale;
        private $rootPath;
        private $useCurl;

        function __construct($app_params, $locale = "en", $use_curl = false)
        {
            $this->appParams = $app_params;
            if (empty($app_params['app_key']))
                throw new DropboxException("App Key is empty!");

            $this->consumerToken = array('t' => $this->appParams['app_key'], 's' => $this->appParams['app_secret']);
            $this->locale = $locale;
            $this->rootPath = empty($app_params['app_full_access']) ? "sandbox" : "dropbox";

            $this->requestToken = null;
            $this->accessToken = null;

            //$this->useCurl = function_exists('curl_init');
            $this->useCurl = $use_curl;
            
            if($this->useCurl)
            {
                DUP_PRO_Log::trace("Using cURL for Dropbox transfers");    
            }
            else
            {
                DUP_PRO_Log::trace("Using FOpen URL for Dropbox transfers");    
            }
            
        }

        // ##################################################
        // Authorization
        /**
         * Step 1 of authentication process. Retrieves a request token or returns a previously retrieved one.
         * 
         * @access public
         * @param boolean $get_new_token Optional (default false). Wether to retrieve a new request token.
         * @return array Request Token array.
         */
        public function GetRequestToken($get_new_token = false)
        {
            if (!empty($this->requestToken) && !$get_new_token)
                return $this->requestToken;

            $rt = $this->authCall("oauth/request_token");
            if (empty($rt) || empty($rt['oauth_token']))
                throw new DropboxException('Could not get request token!');

            return ($this->requestToken = array('t' => $rt['oauth_token'], 's' => $rt['oauth_token_secret']));
        }

        /**
         * Step 2. Returns a URL the user must be redirected to in order to connect the app to their Dropbox account
         * 
         * @access public
         * @param string $return_url URL users are redirected after authorization
         * @return string URL
         */
        public function BuildAuthorizeUrl($return_url = null)
        {
            $rt = $this->GetRequestToken();
            if (empty($rt) || empty($rt['t']))
                throw new DropboxException('Request Token Invalid (' . print_r($rt, true) . ').');

            $url = "https://www.dropbox.com/1/oauth/authorize?oauth_token=" . $rt['t'];

            if ($return_url != null)
            {
                $url .= "&oauth_callback=" . urlencode($return_url);
            }

            return $url;
        }

        /**
         * Step 3. Acquires an access token. This is the final step of authentication.
         * 
         * @access public
         * @param array $request_token Optional. The previously retrieved request token. This parameter can only be skipped if the DropboxClient object has been (de)serialized.
         * @return array Access Token array.
         */
        public function GetAccessToken($request_token = null)
        {
            if (!empty($this->accessToken))
                return $this->accessToken;

            if (empty($request_token))
                $request_token = $this->requestToken;
            if (empty($request_token))
                throw new DropboxException('Request token required!');

            $at = $this->authCall("oauth/access_token", $request_token);
            if (empty($at))
            {
                throw new DropboxException(sprintf('Could not get access token! (request token: %s)', $request_token['t']));
            }

            if (!isset($at['oauth_token']) || !isset($at['oauth_token_secret']))
            {
                throw new DropboxException(sprintf('Could not get access token! (request token: %s)', $request_token['t']));
            }
            return ($this->accessToken = array('t' => $at['oauth_token'], 's' => $at['oauth_token_secret']));
        }

        /**
         * Sets a previously retrieved (and stored) access token.
         * 
         * @access public
         * @param array $token The Access Token
         *
         * @return void
         */
        public function SetAccessToken($token)
        {
            if (empty($token['t']) || empty($token['s']))
                throw new DropboxException('Passed invalid access token.');
            $this->accessToken = $token;
        }

        /**
         * Checks if an access token has been set.
         * 
         * @access public
         * @return boolean Authorized or not
         */
        public function IsAuthorized()
        {
            if (empty($this->accessToken))
                return false;
            return true;
        }

        // ##################################################
        // API Functions
        /**
         * Retrieves information about the user's account.
         * 
         * @access public
         * @return object Account info object. See https://www.dropbox.com/developers/reference/api#account-info
         */
        public function GetAccountInfo()
        {
            return $this->apiCall("account/info", "GET");
        }

        /**
         * Get file list of a dropbox folder.
         * 
         * @access public
         * @param string|object $dropbox_path Dropbox path of the folder
         * @return array An array with metadata of files/folders keyed by paths 
         */
        public function GetFiles($dropbox_path = '', $recursive = false, $include_deleted = false)
        {
            if (is_object($dropbox_path) && !empty($dropbox_path->path))
                $dropbox_path = $dropbox_path->path;
            return $this->getFileTree($dropbox_path, $include_deleted, $recursive ? 1000 : 0);
        }

        /**
         * Get file or folder metadata
         * 
         * @access public
         * @param $dropbox_path string Dropbox path of the file or folder
         */
        public function GetMetadata($dropbox_path, $include_deleted = false, $rev = null)
        {
            if (is_object($dropbox_path) && !empty($dropbox_path->path))
                $dropbox_path = $dropbox_path->path;
            return $this->apiCall("metadata/$this->rootPath/$dropbox_path", "GET", compact('include_deleted', 'rev'));
        }

        /**
         * Download a file to the webserver
         * 
         * @access public
         * @param string|object $dropbox_file Dropbox path or metadata object of the file to download. 
         * @param string $dest_path Local path for destination
         * @param string $rev Optional. The revision of the file to retrieve. This defaults to the most recent revision.
         * @param callback $progress_changed_callback Optional. Callback that will be called during download with 2 args: 1. bytes loaded, 2. file size
         * @return object Dropbox file metadata
         */
        public function DownloadFile($dropbox_file, $dest_path = '', $rev = null, $progress_changed_callback = null)
        {
            if (is_object($dropbox_file) && !empty($dropbox_file->path))
                $dropbox_file = $dropbox_file->path;

            if (empty($dest_path))
                $dest_path = basename($dropbox_file);

            $url = $this->cleanUrl(self::API_CONTENT_URL . "/files/$this->rootPath/$dropbox_file")
                    . (!empty($rev) ? ('?' . http_build_query(array('rev' => $rev), '', '&')) : '');
            $context = $this->createRequestContext($url, "GET");

            $fh = @fopen($dest_path, 'wb'); // write binary
            if ($fh === false)
            {
                @fclose($rh);
                throw new DropboxException("Could not create file $dest_path !");
            }

            if ($this->useCurl)
            {
                curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
                curl_setopt($context, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($context, CURLOPT_FILE, $fh);
                $response_headers = array();
                self::execCurlAndClose($context, $response_headers);
                fclose($fh);
                $meta = self::getMetaFromHeaders($response_headers, true);
                $bytes_loaded = filesize($dest_path);
            }
            else
            {
                $rh = @fopen($url, 'rb', false, $context); // read binary
                if ($rh === false)
                    throw new DropboxException("HTTP request to $url failed!");


                // get file meta from HTTP header
                $s_meta = stream_get_meta_data($rh);
                $meta = self::getMetaFromHeaders($s_meta['wrapper_data'], true);
                $bytes_loaded = 0;
                while (!feof($rh))
                {
                    if (($s = fwrite($fh, fread($rh, self::BUFFER_SIZE))) === false)
                    {
                        @fclose($rh);
                        @fclose($fh);
                        throw new DropboxException("Writing to file $dest_path failed!'");
                    }
                    $bytes_loaded += $s;
                    if (!empty($progress_changed_callback))
                    {
                        call_user_func($progress_changed_callback, $bytes_loaded, $meta->bytes);
                    }
                }

                fclose($rh);
                fclose($fh);
            }

            if ($meta->bytes != $bytes_loaded)
                throw new DropboxException("Download size mismatch! (header:{$meta->bytes} vs actual:{$bytes_loaded}; curl:{$this->useCurl})");

            return $meta;
        }

        // @returns DUP_PRO_DropboxClient_UploadInfo
        public function upload_file_chunk($src_file, $dropbox_path = '', $upload_chunk_size = self::UPLOAD_CHUNK_SIZE, $max_upload_time_in_sec = 15, $offset = 0, $upload_id = null, $server_load_delay = 0)
        {
            DUP_PRO_Log::trace("start");
            $dropbox_client_upload_info = new DUP_PRO_DropboxClient_UploadInfo();
            $dropbox_client_upload_info->next_offset = $offset;
            $dropbox_client_upload_info->upload_id = $upload_id;

            DUP_PRO_Log::trace("offset coming in=$offset");
            DUP_PRO_Log::trace("chunk size=$upload_chunk_size");
            if (empty($dropbox_path))
            {
                $dropbox_path = basename($src_file);
            }
            elseif (is_object($dropbox_path) && !empty($dropbox_path->path))
            {
                $dropbox_path = $dropbox_path->path;
            }

            // make sure the dropbox_path is not a dir. if it is, append baseneme of $src_file
            $dropbox_bn = basename($dropbox_path);
            if (strpos($dropbox_bn, '.') === false)
            {
                // check if ext. is missing -> could be a directory!
                try
                {
                    $meta = $this->GetMetadata($dropbox_path);
                    if ($meta && $meta->is_dir)
                        $dropbox_path = $dropbox_path . '/' . basename($src_file);
                }
                catch (Exception $e)
                {
                    DUP_PRO_Log::trace("problem getting metadata");
                }
            }

            DUP_PRO_Log::trace("dropbox path=$dropbox_path");
            $file_size = filesize($src_file);


            $fh = fopen($src_file, 'rb');

            if ($fh === false)
            {
                //throw new DropboxException();
                DUP_PRO_Log::traceError("problem opening $src_file");
            }

            fseek($fh, $offset);

            DUP_PRO_Log::trace("Just did fseek now tell says " . ftell($fh));

            $start_time = time();
            $time_passed = 0;
            $end_of_file = feof($fh);

            $eof_string = $end_of_file ? 'true' : 'false';

            DUP_PRO_Log::trace("upload_id=$upload_id filesize = $file_size end_of_file=$eof_string max_upload_time=$max_upload_time_in_sec");

            $error_present = false;

            while (($end_of_file == false) && ($time_passed < $max_upload_time_in_sec) & ($error_present == false))
            {
                if($server_load_delay !== 0) {
                    usleep($server_load_delay);
                }
                
                DUP_PRO_Log::trace("Uploading offset $offset since timepassed=$time_passed");
                $url = $this->cleanUrl(self::API_CONTENT_URL . "/chunked_upload") . '?' . http_build_query(compact('upload_id', 'offset'), '', '&');

                DUP_PRO_Log::trace("3");
                $content = fread($fh, $upload_chunk_size);

                $context = $this->createRequestContext($url, "PUT", $content);
                $old_offset = $offset;
                $offset += strlen($content);
                
                
                if($this->useCurl)
                {
                    DUP_PRO_Log::trace("Using curl from url = $url");
                    curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
					$response = json_decode(self::execCurlAndClose($context));
                }
                else
                {
                    DUP_PRO_Log::trace("Using file_get_contents  from url = $url");
                    $file_contents = file_get_contents($url, false, $context);
                    $response = json_decode($file_contents);
                }
                
                unset($content);                
                unset($context);
                
                // NOTE: it returns 400 if dropbox already has that chunk

                if (empty($response) == false)
                {
                    if (!empty($response->error))
                    {
                        $error_message = DUP_PRO_U::__('Response error present:') . $response->error;

                        DUP_PRO_Log::trace("rolling back dropbox due to $error_message");
						if(strpos($response->error, 'expected [') !== false)
						{							
							$matches = array();
							$preg_retval = preg_match("/.*expected\\s+\\[(.+)\\]/", $response->error, $matches);
							
							if(count($matches) == 2)
							{								
								$old_offset = (int)$matches[1];
								DUP_PRO_Log::trace("Forcing old offset to be $old_offset");
							}
							else
							{
								DUP_PRO_Log::traceObject("Something wrong with parsing matches, retval=$preg_retval", $matches);
							}
						}
						else
						{
							DUP_PRO_Log::trace("Response error doens't contain expected");
						}
						
                        $dropbox_client_upload_info->error_details = $error_message;
                        $error_present = true;
                        $offset = $old_offset;
                        DUP_PRO_Log::trace("rolling back dropbox to offset $offset due to $error_message");
                    }
                    else if (empty($upload_id))
                    {

                        $upload_id = $response->upload_id;
                        DUP_PRO_Log::trace("upload id set to $upload_id");
                    }
                }
                else
                {
                    $error_message = "Response to {$url} is missing and file content is $file_contents.  Make sure 'extension=php_openssl.dll' is in the php.ini for windows.  ";
                    $error_message .= 'On Linux based systems check for the --with-openssl[=DIR] flag.';

                    $dropbox_client_upload_info->error_details = $error_message;
                    $error_present = true;
                    $offset = $old_offset;
                    
                    DUP_PRO_Log::trace("rolling back dropbox to offset $offset due to $error_message");
                }

                DUP_PRO_Log::trace("4");

                

                DUP_PRO_Log::trace("5");
                $time_passed = time() - $start_time;
                $end_of_file = feof($fh);
            }

            @fclose($fh);

            DUP_PRO_Log::trace("Time passed=$time_passed");

            if ($end_of_file)
            {
                DUP_PRO_Log::trace("end of file");
                $dropbox_client_upload_info->file_meta = $this->apiCall("commit_chunked_upload/$this->rootPath/$dropbox_path", "POST", compact('overwrite', 'parent_rev', 'upload_id'), true);
            }

            $dropbox_client_upload_info->upload_id = $upload_id;
            $dropbox_client_upload_info->next_offset = $offset;

            return $dropbox_client_upload_info;
        }

        /**
         * Upload a file to dropbox
         * 
         * @access public
         * @param $src_file string Local file to upload
         * @param $dropbox_path string Dropbox path for destination
         * @return object Dropbox file metadata
         */
        public function UploadFile($src_file, $dropbox_path = '', $overwrite = true, $parent_rev = null)
        {
            if (empty($dropbox_path))
                $dropbox_path = basename($src_file);
            elseif (is_object($dropbox_path) && !empty($dropbox_path->path))
                $dropbox_path = $dropbox_path->path;

            // make sure the dropbox_path is not a dir. if it is, append baseneme of $src_file
            $dropbox_bn = basename($dropbox_path);
            if (strpos($dropbox_bn, '.') === false)
            { // check if ext. is missing -> could be a directory!
                try
                {
                    $meta = $this->GetMetadata($dropbox_path);
                    if ($meta && $meta->is_dir)
                        $dropbox_path = $dropbox_path . '/' . basename($src_file);
                }
                catch (Exception $e)
                {
                    
                }
            }

			// Delete any file that may be there ahead of time
			try
			{
				DUP_PRO_Log::trace("Deleting dropbox files $dropbox_path");
				$this->Delete($dropbox_path);
			}
			catch(Exception $ex)
			{	
				// Bury any exceptions
			}
			
            $file_size = filesize($src_file);

            if ($file_size > self::MAX_UPLOAD_CHUNK_SIZE)
            {
                $fh = fopen($src_file, 'rb');
                if ($fh === false)
                    throw new DropboxException();

                $upload_id = null;
                $offset = 0;


                while (!feof($fh))
                {
                    $url = $this->cleanUrl(self::API_CONTENT_URL . "/chunked_upload") . '?' . http_build_query(compact('upload_id', 'offset'), '', '&');

                    if ($this->useCurl)
                    {
                        $context = $this->createRequestContext($url, "PUT");
                        curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
                        curl_setopt($context, CURLOPT_PUT, 1);
                        curl_setopt($context, CURLOPT_INFILE, $fh);
                        $chunk_size = min(self::UPLOAD_CHUNK_SIZE, $file_size - $offset);
                        $offset += $chunk_size;
                        curl_setopt($context, CURLOPT_INFILESIZE, $chunk_size);
                        $response = json_decode(self::execCurlAndClose($context));

                        fseek($fh, $offset);
                        if ($offset >= $file_size)
                            break;
                    } else
                    {
                        $content = fread($fh, self::UPLOAD_CHUNK_SIZE);

                        $context = $this->createRequestContext($url, "PUT", $content);
                        $offset += strlen($content);

                        $response = json_decode(file_get_contents($url, false, $context));

                        unset($content);
                    }
                    unset($context);

                    self::checkForError($response);

                    if (empty($upload_id))
                    {
                        $upload_id = $response->upload_id;
                        if (empty($upload_id))
                            throw new DropboxException("Upload ID empty!");
                    }
                }

                @fclose($fh);

                return $this->apiCall("commit_chunked_upload/$this->rootPath/$dropbox_path", "POST", compact('overwrite', 'parent_rev', 'upload_id'), true);
            }

            $query = http_build_query(array_merge(compact('overwrite', 'parent_rev'), array('locale' => $this->locale)), '', '&');
            $url = $this->cleanUrl(self::API_CONTENT_URL . "/files_put/$this->rootPath/$dropbox_path") . "?$query";

            if ($this->useCurl)
            {
                $context = $this->createRequestContext($url, "PUT");
                curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
                $fh = fopen($src_file, 'rb');
                curl_setopt($context, CURLOPT_PUT, 1);
                curl_setopt($context, CURLOPT_INFILE, $fh); // file pointer
                curl_setopt($context, CURLOPT_INFILESIZE, filesize($src_file));
                $meta = json_decode(self::execCurlAndClose($context));
                fclose($fh);
                return self::checkForError($meta);
            }
            else
            {
                $content = file_get_contents($src_file);
                if (strlen($content) == 0)
                    throw new DropboxException("Could not read file $src_file or file is empty!");

                $context = $this->createRequestContext($url, "PUT", $content);

                return self::checkForError(json_decode(file_get_contents($url, false, $context)));
            }
        }

        /**
         * Get thumbnail for a specified image
         *
         * @access public
         * @param $dropbox_file string Path to the image
         * @param $format string Image format of the thumbnail (jpeg or png)
         * @param $size string Thumbnail size (xs, s, m, l, xl)
         * @return mime/* Returns the thumbnail as binary image data
         */
        public function GetThumbnail($dropbox_file, $size = 's', $format = 'jpeg', $echo = false)
        {
            if (is_object($dropbox_file) && !empty($dropbox_file->path))
                $dropbox_file = $dropbox_file->path;
            $url = $this->cleanUrl(self::API_CONTENT_URL . "thumbnails/$this->rootPath/$dropbox_file")
                    . '?' . http_build_query(array('format' => $format, 'size' => $size), '', '&');
            $context = $this->createRequestContext($url, "GET");

            if ($this->useCurl)
            {
                curl_setopt($context, CURLOPT_BINARYTRANSFER, true);
                curl_setopt($context, CURLOPT_RETURNTRANSFER, true);
            }

            $thumb = $this->useCurl ? self::execCurlAndClose($context) : file_get_contents($url, NULL, $context);

            if ($echo)
            {
                header('Content-type: image/' . $format);
                echo $thumb;
                unset($thumb);
                return;
            }

            return $thumb;
        }

        function GetLink($dropbox_file, $preview = true, $short = true, &$expires = null)
        {
            if (is_object($dropbox_file) && !empty($dropbox_file->path))
                $dropbox_file = $dropbox_file->path;
            $url = $this->apiCall(($preview ? "shares" : "media") . "/$this->rootPath/$dropbox_file", "POST", array('locale' => null, 'short_url' => $preview ? $short : null));
            $expires = strtotime($url->expires);
            return $url->url;
        }

        function Delta($cursor)
        {
            return $this->apiCall("delta", "POST", compact('cursor'));
        }

        function token_from_oauth1()
        {
            /*
            https://www.dropbox.com/developers-v1/core/docs#oa2-from-oa1
            */
            return $this->apiCall("oauth2/token_from_oauth1", "POST");
        }

        function GetRevisions($dropbox_file, $rev_limit = 10)
        {
            if (is_object($dropbox_file) && !empty($dropbox_file->path))
                $dropbox_file = $dropbox_file->path;
            return $this->apiCall("revisions/$this->rootPath/$dropbox_file", "GET", compact('rev_limit'));
        }

        function Restore($dropbox_file, $rev)
        {
            if (is_object($dropbox_file) && !empty($dropbox_file->path))
                $dropbox_file = $dropbox_file->path;
            return $this->apiCall("restore/$this->rootPath/$dropbox_file", "POST", compact('rev'));
        }

        function Search($path, $query, $file_limit = 1000, $include_deleted = false)
        {
            return $this->apiCall("search/$this->rootPath/$path", "POST", compact('query', 'file_limit', 'include_deleted'));
        }

        function GetCopyRef($dropbox_file, &$expires = null)
        {
            if (is_object($dropbox_file) && !empty($dropbox_file->path))
                $dropbox_file = $dropbox_file->path;
            $ref = $this->apiCall("copy_ref/$this->rootPath/$dropbox_file", "GET", array('locale' => null));
            $expires = strtotime($ref->expires);
            return $ref->copy_ref;
        }

        function Copy($from_path, $to_path, $copy_ref = false)
        {
            if (is_object($from_path) && !empty($from_path->path))
                $from_path = $from_path->path;
            return $this->apiCall("fileops/copy", "POST", array('root' => $this->rootPath, ($copy_ref ? 'from_copy_ref' : 'from_path') => $from_path, 'to_path' => $to_path));
        }

        /**
         * Creates a new folder in the DropBox
         * 
         * @access public
         * @param $path string The path to the new folder to create
         * @return object Dropbox folder metadata
         */
        function CreateFolder($path)
        {
            return $this->apiCall("fileops/create_folder", "POST", array('root' => $this->rootPath, 'path' => $path));
        }

        /**
         * Delete file or folder
         * 
         * @access public
         * @param $path mixed The path or metadata of the file/folder to be deleted.
         * @return object Dropbox metadata of deleted file or folder
         */
        function Delete($path)
        {
            if (is_object($path) && !empty($path->path))
                $path = $path->path;
            return $this->apiCall("fileops/delete", "POST", array('locale' => null, 'root' => $this->rootPath, 'path' => $path));
        }

        function Move($from_path, $to_path)
        {
            if (is_object($from_path) && !empty($from_path->path))
                $from_path = $from_path->path;
            return $this->apiCall("fileops/move", "POST", array('root' => $this->rootPath, 'from_path' => $from_path, 'to_path' => $to_path));
        }

        function getFileTree($path = "", $include_deleted = false, $max_depth = 0, $depth = 0)
        {
            static $files;
            if ($depth == 0)
                $files = array();

            $dir = $this->apiCall("metadata/$this->rootPath/$path", "GET", compact('include_deleted'));

            if (empty($dir) || !is_object($dir))
                return false;

            if (!empty($dir->error))
                throw new DropboxException($dir->error);

            foreach ($dir->contents as $item)
            {
                $files[trim($item->path, '/')] = $item;
                if ($item->is_dir && $depth < $max_depth)
                {
                    $this->getFileTree($item->path, $include_deleted, $max_depth, $depth + 1);
                }
            }

            return $files;
        }

        function createCurl($url, $http_context)
        {
            $ch = curl_init($url);

            $curl_opts = array(
                CURLOPT_HEADER => false, // exclude header from output
                //CURLOPT_MUTE => true, // no output!
                CURLOPT_RETURNTRANSFER => true, // but return!
                CURLOPT_SSL_VERIFYPEER => false,
            );

            $curl_opts[CURLOPT_CUSTOMREQUEST] = $http_context['method'];

            if (!empty($http_context['content']))
            {
                $curl_opts[CURLOPT_POSTFIELDS] = & $http_context['content'];
                if (defined("CURLOPT_POSTFIELDSIZE"))
                    $curl_opts[CURLOPT_POSTFIELDSIZE] = strlen($http_context['content']);
            }

            $curl_opts[CURLOPT_HTTPHEADER] = array_map('trim', explode("\n", $http_context['header']));

            curl_setopt_array($ch, $curl_opts);
            return $ch;
        }

        static private $_curlHeadersRef;

        static function _curlHeaderCallback($ch, $header)
        {
            self::$_curlHeadersRef[] = trim($header);
            return strlen($header);
        }

        static function &execCurlAndClose($ch, &$out_response_headers = null)
        {
            if (is_array($out_response_headers))
            {
                self::$_curlHeadersRef = & $out_response_headers;
                curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(__CLASS__, '_curlHeaderCallback'));
            }
            $res = curl_exec($ch);
            $err_no = curl_errno($ch);
            $err_str = curl_error($ch);
            curl_close($ch);
            if ($err_no || $res === false)
            {
                throw new DropboxException("cURL-Error ($err_no): $err_str");
            }

            return $res;
        }

        private function createRequestContext($url, $method, &$content = null, $oauth_token = -1)
        {
            if ($oauth_token === -1)
                $oauth_token = $this->accessToken;

            $method = strtoupper($method);
            $http_context = array('method' => $method, 'header' => '');

            $oauth = new DUP_PRO_OAuthSimple($this->consumerToken['t'], $this->consumerToken['s']);

            if (empty($oauth_token) && !empty($this->accessToken))
                $oauth_token = $this->accessToken;

            if (!empty($oauth_token))
            {
                $oauth->setParameters(array('oauth_token' => $oauth_token['t']));
                $oauth->signatures(array('oauth_secret' => $oauth_token['s']));
            }

            if (!empty($content))
            {
                $post_vars = ($method != "PUT" && preg_match("/^[a-z][a-z0-9_]*=/i", substr($content, 0, 32)));
                $http_context['header'] .= "Content-Length: " . strlen($content) . "\r\n";
                $http_context['header'] .= "Content-Type: application/" . ($post_vars ? "x-www-form-urlencoded" : "octet-stream") . "\r\n";
                $http_context['content'] = & $content;
                if ($method == "POST" && $post_vars)
                    $oauth->setParameters($content);
            } elseif ($method == "POST")
            {
                // make sure that content-length is always set when post request (otherwise some wrappers fail!)
                $http_context['content'] = "";
                $http_context['header'] .= "Content-Length: 0\r\n";
            }


            // check for query vars in url and add them to oauth parameters (and remove from path)
            $path = $url;
            $query = strrchr($url, '?');
            if (!empty($query))
            {
                $oauth->setParameters(substr($query, 1));
                $path = substr($url, 0, -strlen($query));
            }


            $signed = $oauth->sign(array(
                'action' => $method,
                'path' => $path));
            //print_r($signed);	

            $http_context['header'] .= "Authorization: " . $signed['header'] . "\r\n";

            return $this->useCurl ? $this->createCurl($url, $http_context) : stream_context_create(array('http' => $http_context));
        }

        private function authCall($path, $request_token = null)
        {
            $url = $this->cleanUrl(self::API_URL . $path);
            $dummy = null;
            $context = $this->createRequestContext($url, "POST", $dummy, $request_token);

            $contents = $this->useCurl ? self::execCurlAndClose($context) : file_get_contents($url, false, $context);
            $data = array();
            parse_str($contents, $data);
            return $data;
        }

        private static function checkForError($resp)
        {
            if (!empty($resp->error))
                throw new DropboxException($resp->error);
            return $resp;
        }

        private function apiCall($path, $method, $params = array(), $content_call = false)
        {
            $url = $this->cleanUrl(($content_call ? self::API_CONTENT_URL : self::API_URL) . $path);
            $content = http_build_query(array_merge(array('locale' => $this->locale), $params), '', '&');

            if ($method == "GET")
            {
                $url .= "?" . $content;
                $content = null;
            }

            $context = $this->createRequestContext($url, $method, $content);
            $json = $this->useCurl ? self::execCurlAndClose($context) : file_get_contents($url, false, $context);
            //if($json === false)
//			throw new DropboxException();
            $resp = json_decode($json);
            return self::checkForError($resp);
        }

        private static function getMetaFromHeaders(&$header_array, $throw_on_error = false)
        {
            $obj = json_decode(substr(@array_shift(array_filter($header_array, create_function('$s', 'return stripos($s, "x-dropbox-metadata:") === 0;'))), 20));
           
            if ($throw_on_error && (empty($obj) || !is_object($obj)))
                throw new DropboxException("Could not retrieve meta data from header data: " . print_r($header_array, true));
            if ($throw_on_error)
                self::checkForError($obj);
            return $obj;
        }

        function cleanUrl($url)
        {
            $p = substr($url, 0, 8);
            $url = str_replace('//', '/', str_replace('\\', '/', substr($url, 8)));
            $url = rawurlencode($url);
            $url = str_replace('%2F', '/', $url);
            return $p . $url;
        }

    }

if (!class_exists('DropboxException'))
{
    class DropboxException extends Exception
    {
        public function __construct($err = null, $isDebug = FALSE)
        {
            if (is_null($err))
            {
                $el = error_get_last();
                $this->message = $el['message'];
                $this->file = $el['file'];
                $this->line = $el['line'];
            }
            else
                $this->message = $err;
            self::log_error($err);
            if ($isDebug)
            {
                self::display_error($err, TRUE);
            }
        }

        public static function log_error($err)
        {
            error_log($err, 0);
        }

        public static function display_error($err, $kill = FALSE)
        {
            print_r($err);
            if ($kill === FALSE)
            {
                die();
            }
        }

    }
}
}