Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 30b1e89f8ee6a2f399737246f45922b5f5c8bf11 0 parents
@andreoav authored
20 bootstrap.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Opauth Multi-provider authentication framework for PHP package for FuelPHP Framework
+ *
+ * @package Fuel-Opauth
+ * @version 1.0
+ * @author Andreo Vieira <andreoav@gmail.com>
+ * @license MIT License
+ * @copyright 2012 Andreo Vieira
+ * @link http://www.inf.ufsm.br/~andreoav
+ */
+
+Autoloader::add_core_namespace('Opauth');
+
+Autoloader::add_classes(array(
+ 'Opauth\\Opauth' => __DIR__ . '/classes/Opauth.php',
+ 'Opauth\\OpauthStrategy' => __DIR__ . '/classes/OpauthStrategy.php',
+ 'Opauth\\FacebookStrategy' => __DIR__ . '/classes/Strategy/FacebookStrategy.php',
+ 'Opauth\\TwitterStrategy' => __DIR__ . '/classes/Strategy/TwitterStrategy.php',
+));
326 classes/Opauth.php
@@ -0,0 +1,326 @@
+<?php
+/**
+ * Opauth
+ * Multi-provider authentication framework for PHP
+ *
+ * @copyright Copyright © 2012 U-Zyn Chua (http://uzyn.com)
+ * @link http://opauth.org
+ * @package Opauth
+ * @license MIT License
+ */
+
+namespace Opauth;
+
+/**
+ * Opauth
+ * Multi-provider authentication framework for PHP
+ *
+ * @package Opauth
+ */
+class Opauth{
+ /**
+ * User configuraable settings
+ * Refer to example/opauth.conf.php.default or example/opauth.conf.php.advanced for sample
+ * More info: https://github.com/uzyn/opauth/wiki/Opauth-configuration
+ */
+ public $config;
+
+ /**
+ * Environment variables
+ */
+ public $env;
+
+ /**
+ * Strategy map: for mapping URL-friendly name to Class name
+ */
+ public $strategyMap;
+
+ /**
+ * Constructor
+ * Loads user configuration and strategies.
+ *
+ * @param array $config User configuration
+ * @param boolean $run Whether Opauth should auto run after initialization.
+ */
+ public function __construct($config = array(), $run = true){
+ /**
+ * Configurable settings
+ */
+ $this->config = array_merge(array(
+ 'host' => ((array_key_exists('HTTPS', $_SERVER) && $_SERVER['HTTPS'])?'https':'http').'://'.$_SERVER['HTTP_HOST'],
+ 'path' => '/',
+ 'callback_url' => '{path}callback',
+ 'callback_transport' => 'session',
+ 'debug' => false,
+
+ /**
+ * Security settings
+ */
+ 'security_salt' => 'LDFmiilYf8Fyw5W10rx4W1KsVrieQCnpBzzpTBWA5vJidQKDx8pMJbmw28R1C4m',
+ 'security_iteration' => 300,
+ 'security_timeout' => '2 minutes'
+
+ ), $config);
+
+ /**
+ * Environment variables, including config
+ * Used mainly as accessors
+ */
+ $this->env = array_merge(array(
+ 'request_uri' => $_SERVER['REQUEST_URI'],
+ 'complete_path' => $this->config['host'].$this->config['path'],
+ 'lib_dir' => dirname(__FILE__).'/',
+ 'strategy_dir' => dirname(__FILE__).'/Strategy/'
+ ), $this->config);
+
+ if (!class_exists('OpauthStrategy')){
+ require $this->env['lib_dir'].'OpauthStrategy.php';
+ }
+
+ foreach ($this->env as $key => $value){
+ $this->env[$key] = OpauthStrategy::envReplace($value, $this->env);
+ }
+
+ if ($this->env['security_salt'] == 'LDFmiilYf8Fyw5W10rx4W1KsVrieQCnpBzzpTBWA5vJidQKDx8pMJbmw28R1C4m'){
+ trigger_error('Please change the value of \'security_salt\' to a salt value specific to your application', E_USER_NOTICE);
+ }
+
+ $this->loadStrategies();
+
+ if ($run) $this->run();
+ }
+
+ /**
+ * Run Opauth:
+ * Parses request URI and perform defined authentication actions based based on it.
+ */
+ public function run(){
+ $this->parseUri();
+
+ if (!empty($this->env['params']['strategy'])){
+ if (strtolower($this->env['params']['strategy']) == 'callback'){
+ $this->callback();
+ }
+ elseif (array_key_exists($this->env['params']['strategy'], $this->strategyMap)){
+ $name = $this->strategyMap[$this->env['params']['strategy']]['name'];
+ $class = $this->strategyMap[$this->env['params']['strategy']]['class'];
+ $strategy = $this->env['Strategy'][$name];
+
+ // Strip out critical parameters
+ $safeEnv = $this->env;
+ unset($safeEnv['Strategy']);
+
+ $actualClass = $this->requireStrategy($class);
+ $this->Strategy = new $actualClass($strategy, $safeEnv);
+
+ if (empty($this->env['params']['action'])){
+ $this->env['params']['action'] = 'request';
+ }
+ $this->Strategy->callAction($this->env['params']['action']);
+ }
+ else{
+ trigger_error('Unsupported or undefined Opauth strategy - '.$this->env['strategy'], E_USER_ERROR);
+ }
+ }
+ else{
+ $sampleStrategy = array_pop($this->env['Strategy']);
+ trigger_error('No strategy is requested. Try going to '.$this->env['complete_path'].$sampleStrategy['strategy_url_name'].' to authenticate with '.$sampleStrategy['strategy_name'], E_USER_NOTICE);
+ }
+ }
+
+ /**
+ * Parses Request URI
+ */
+ private function parseUri(){
+ $this->env['request'] = substr($this->env['request_uri'], strlen($this->env['path']) - 1);
+
+ if (preg_match_all('/\/([A-Za-z0-9-_]+)/', $this->env['request'], $matches)){
+ foreach ($matches[1] as $match){
+ $this->env['params'][] = $match;
+ }
+ }
+
+ if (!empty($this->env['params'][0])) $this->env['params']['strategy'] = $this->env['params'][0];
+ if (!empty($this->env['params'][1])) $this->env['params']['action'] = $this->env['params'][1];
+ }
+
+ /**
+ * Load strategies from user-input $config
+ */
+ private function loadStrategies(){
+ if (isset($this->env['Strategy']) && is_array($this->env['Strategy']) && count($this->env['Strategy']) > 0){
+ foreach ($this->env['Strategy'] as $key => $strategy){
+ if (!is_array($strategy)){
+ $key = $strategy;
+ $strategy = array();
+ }
+
+ $strategyClass = $key;
+ if (array_key_exists('strategy_class', $strategy)) $strategyClass = $strategy['strategy_class'];
+ else $strategy['strategy_class'] = $strategyClass;
+
+ $strategy['strategy_name'] = $key;
+
+ // Define a URL-friendly name
+ if (empty($strategy['strategy_url_name'])) $strategy['strategy_url_name'] = strtolower($key);
+ $this->strategyMap[$strategy['strategy_url_name']] = array(
+ 'name' => $key,
+ 'class' => $strategyClass
+ );
+
+ $this->env['Strategy'][$key] = $strategy;
+ }
+ }
+ else{
+ trigger_error('No Opauth strategies defined', E_USER_ERROR);
+ }
+ }
+
+ /**
+ * Validate $auth response
+ * Accepts either function call or HTTP-based call
+ *
+ * @param string $input = sha1(print_r($auth, true))
+ * @param string $timestamp = $_REQUEST['timestamp'])
+ * @param string $signature = $_REQUEST['signature']
+ * @param string $reason Sets reason for failure if validation fails
+ * @return boolean true: valid; false: not valid.
+ */
+ public function validate($input = null, $timestamp = null, $signature = null, &$reason = null){
+ $functionCall = true;
+ if (!empty($_REQUEST['input']) && !empty($_REQUEST['timestamp']) && !empty($_REQUEST['signature'])){
+ $functionCall = false;
+ $provider = $_REQUEST['input'];
+ $timestamp = $_REQUEST['timestamp'];
+ $signature = $_REQUEST['signature'];
+ }
+
+ $timestamp_int = strtotime($timestamp);
+ if ($timestamp_int < strtotime('-'.$this->env['security_timeout']) || $timestamp_int > time()){
+ $reason = "Auth response expired";
+ return false;
+ }
+
+ $hash = OpauthStrategy::hash($input, $timestamp, $this->env['security_iteration'], $this->env['security_salt']);
+
+ if (strcasecmp($hash, $signature) !== 0){
+ $reason = "Signature does not validate";
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Callback: prints out $auth values, and acts as a guide on Opauth security
+ * Application should redirect callback URL to application-side.
+ * Refer to example/callback.php on how to handle auth callback.
+ */
+ public function callback(){
+ echo "<strong>Note: </strong>Application should set callback URL to application-side for further specific authentication process.\n<br>";
+
+ $response = null;
+ switch($this->env['callback_transport']){
+ case 'session':
+ session_start();
+ $response = $_SESSION['opauth'];
+ unset($_SESSION['opauth']);
+ break;
+ case 'post':
+ $response = unserialize(base64_decode( $_POST['opauth'] ));
+ break;
+ case 'get':
+ $response = unserialize(base64_decode( $_GET['opauth'] ));
+ break;
+ default:
+ echo '<strong style="color: red;">Error: </strong>Unsupported callback_transport.'."<br>\n";
+ break;
+ }
+
+ /**
+ * Check if it's an error callback
+ */
+ if (array_key_exists('error', $response)){
+ echo '<strong style="color: red;">Authentication error: </strong> Opauth returns error auth response.'."<br>\n";
+ }
+
+ /**
+ * No it isn't. Proceed with auth validation
+ */
+ else{
+ if (empty($response['auth']) || empty($response['timestamp']) || empty($response['signature']) || empty($response['auth']['provider']) || empty($response['auth']['uid'])){
+ echo '<strong style="color: red;">Invalid auth response: </strong>Missing key auth response components.'."<br>\n";
+ }
+ elseif (!$this->validate(sha1(print_r($response['auth'], true)), $response['timestamp'], $response['signature'], $reason)){
+ echo '<strong style="color: red;">Invalid auth response: </strong>'.$reason.".<br>\n";
+ }
+ else{
+ echo '<strong style="color: green;">OK: </strong>Auth response is validated.'."<br>\n";
+ }
+ }
+
+ /**
+ * Auth response dump
+ */
+ echo "<pre>";
+ print_r($response);
+ echo "</pre>";
+ }
+
+ /**
+ * Loads a strategy, firstly check if the
+ * strategy's class already exists, especially for users of Composer;
+ * If it isn't, attempts to load it from $this->env['strategy_dir']
+ *
+ * @param string $strategy Name of a strategy
+ * @return string Class name of the strategy, usually StrategyStrategy
+ */
+ private function requireStrategy($strategy){
+ if (!class_exists($strategy.'Strategy')){
+ // Include dir where Git repository for strategy is cloned directly without
+ // specifying a dir name, eg. opauth-facebook
+ $directories = array(
+ $this->env['strategy_dir'].$strategy.'/',
+ $this->env['strategy_dir'].'opauth-'.strtolower($strategy).'/',
+ $this->env['strategy_dir'].strtolower($strategy).'/',
+ $this->env['strategy_dir'].'Opauth-'.$strategy.'/'
+ );
+
+ // Include deprecated support for strategies without Strategy postfix as class name or filename
+ $classNames = array(
+ $strategy.'Strategy',
+ $strategy
+ );
+
+ $found = false;
+ foreach ($directories as $dir){
+ foreach ($classNames as $name){
+ if (file_exists($dir.$name.'.php')){
+ $found = true;
+ require $dir.$name.'.php';
+ return $name;
+ }
+ }
+ }
+
+ if (!$found){
+ trigger_error('Strategy class file ('.$this->env['strategy_dir'].$strategy.'/'.$strategy.'Strategy.php'.') is missing', E_USER_ERROR);
+ }
+ }
+ return $strategy.'Strategy';
+ }
+
+ /**
+ * Prints out variable with <pre> tags
+ * Silence if Opauth is not in debug mode
+ *
+ * @param mixed $var Object or variable to be printed
+ */
+ public function debug($var){
+ if ($this->env['debug'] !== false){
+ echo "<pre>";
+ print_r($var);
+ echo "</pre>";
+ }
+ }
+}
476 classes/OpauthStrategy.php
@@ -0,0 +1,476 @@
+<?php
+/**
+ * Opauth Strategy
+ * Individual strategies are to be extended from this class
+ *
+ * @copyright Copyright © 2012 U-Zyn Chua (http://uzyn.com)
+ * @link http://opauth.org
+ * @package Opauth.Strategy
+ * @license MIT License
+ */
+
+namespace Opauth;
+
+/**
+ * Opauth Strategy
+ * Individual strategies are to be extended from this class
+ *
+ * @package Opauth.Strategy
+ */
+class OpauthStrategy{
+
+ /**
+ * Compulsory config keys, listed as unassociative arrays
+ * eg. array('app_id', 'app_secret');
+ */
+ public $expects;
+
+ /**
+ * Optional config keys with respective default values, listed as associative arrays
+ * eg. array('scope' => 'email');
+ */
+ public $defaults;
+
+ /**
+ * Auth response array, containing results after successful authentication
+ */
+ public $auth;
+
+ /**
+ * Name of strategy
+ */
+ public $name = null;
+
+ /**
+ * Configurations and settings unique to a particular strategy
+ */
+ protected $strategy;
+
+ /**
+ * Safe env values from Opauth, with critical parameters stripped out
+ */
+ protected $env;
+
+ /**
+ * Constructor
+ *
+ * @param array $strategy Strategy-specific configuration
+ * @param array $env Safe env values from Opauth, with critical parameters stripped out
+ */
+ public function __construct($strategy, $env){
+ $this->strategy = $strategy;
+ $this->env = $env;
+
+ // Include some useful values from Opauth's env
+ $this->strategy['strategy_callback_url'] = $this->env['host'].$this->env['callback_url'];
+
+ if ($this->name === null){
+ $this->name = (isset($name) ? $name : get_class($this));
+ }
+
+ if (is_array($this->expects)){
+ foreach ($this->expects as $key){
+ $this->expects($key);
+ }
+ }
+
+ if (is_array($this->defaults)){
+ foreach ($this->defaults as $key => $value){
+ $this->optional($key, $value);
+ }
+ }
+
+ /**
+ * Additional helpful values
+ */
+ $this->strategy['path_to_strategy'] = $this->env['path'].$this->strategy['strategy_url_name'].'/';
+ $this->strategy['complete_url_to_strategy'] = $this->env['host'].$this->strategy['path_to_strategy'];
+
+
+ $dictionary = array_merge($this->env, $this->strategy);
+ foreach ($this->strategy as $key=>$value){
+ $this->strategy[$key] = $this->envReplace($value, $dictionary);
+ }
+ }
+
+ /**
+ * Auth request
+ * aka Log in or Register
+ */
+ public function request(){
+ }
+
+ /**
+ * Packs $auth nicely and send to callback_url, ships $auth either via GET, POST or session.
+ * Set shipping transport via callback_transport config, default being session.
+ */
+ public function callback(){
+ $timestamp = date('c');
+
+ // To standardize the way of accessing data, objects are translated to arrays
+ $this->auth = $this->recursiveGetObjectVars($this->auth);
+
+ $this->auth['provider'] = $this->strategy['strategy_name'];
+
+ $params = array(
+ 'auth' => $this->auth,
+ 'timestamp' => $timestamp,
+ 'signature' => $this->sign($timestamp)
+ );
+
+ $this->shipToCallback($params);
+ }
+
+ /**
+ * Error callback
+ *
+ * More info: https://github.com/uzyn/opauth/wiki/Auth-response#wiki-error-response
+ *
+ * @param array $error Data on error to be sent back along with the callback
+ * $error = array(
+ * 'provider' // Provider name
+ * 'code' // Error code, can be int (HTTP status) or string (eg. access_denied)
+ * 'message' // User-friendly error message
+ * 'raw' // Actual detail on the error, as returned by the provider
+ * )
+ *
+ */
+ public function errorCallback($error){
+ $timestamp = date('c');
+
+ $error = $this->recursiveGetObjectVars($error);
+ $error['provider'] = $this->strategy['strategy_name'];
+
+ $params = array(
+ 'error' => $error,
+ 'timestamp' => $timestamp
+ );
+
+ $this->shipToCallback($params);
+ }
+
+ /**
+ * Send $data to callback_url using specified transport method
+ *
+ * @param array $data Data to be sent
+ * @param string $transport Callback method, either 'get', 'post' or 'session'
+ * 'session': Default. Works best unless callback_url is on a different domain than Opauth
+ * 'post': Works cross-domain, but relies on availability of client-side JavaScript.
+ * 'get': Works cross-domain, but may be limited or corrupted by browser URL length limit
+ * (eg. IE8/IE9 has 2083-char limit)
+ *
+ */
+ private function shipToCallback($data, $transport = null){
+ if (empty($transport)) $transport = $this->env['callback_transport'];
+
+ switch($transport){
+ case 'get':
+ $this->redirect($this->env['callback_url'].'?'.http_build_query(array('opauth' => base64_encode(serialize($data))), '', '&'));
+ break;
+ case 'post':
+ $this->clientPost($this->env['callback_url'], array('opauth' => base64_encode(serialize($data))));
+ break;
+ case 'session':
+ default:
+ session_start();
+ $_SESSION['opauth'] = $data;
+ $this->redirect($this->env['callback_url']);
+ }
+ }
+
+ /**
+ * Call an action from a defined strategy
+ *
+ * @param string $action Action name to call
+ * @param string $defaultAction If an action is not defined in a strategy, calls $defaultAction
+ */
+ public function callAction($action, $defaultAction = 'request'){
+ if (method_exists($this, $action)) return $this->{$action}();
+ else return $this->{$defaultAction}();
+ }
+
+ /**
+ * Ensures that a compulsory value is set, throws an error if it's not set
+ *
+ * @param string $key Expected configuration key
+ * @param string $not If value is set as $not, trigger E_USER_ERROR
+ * @return mixed The loaded value
+ */
+ protected function expects($key, $not = null){
+ if (!array_key_exists($key, $this->strategy)){
+ trigger_error($this->name." config parameter for \"$key\" expected.", E_USER_ERROR);
+ exit();
+ }
+
+ $value = $this->strategy[$key];
+ if (empty($value) || $value == $not){
+ trigger_error($this->name." config parameter for \"$key\" expected.", E_USER_ERROR);
+ exit();
+ }
+
+ return $value;
+ }
+
+ /**
+ * Loads a default value into $strategy if the associated key is not found
+ *
+ * @param string $key Configuration key to be loaded
+ * @param string $default Default value for the configuration key if none is set by the user
+ * @return mixed The loaded value
+ */
+ protected function optional($key, $default = null){
+ if (!array_key_exists($key, $this->strategy)){
+ $this->strategy[$key] = $default;
+ return $default;
+ }
+
+ else return $this->strategy[$key];
+ }
+
+ /**
+ * Security: Sign $auth before redirecting to callback_url
+ *
+ * @param string $timestamp ISO 8601 formatted date
+ * @return string Resulting signature
+ */
+ protected function sign($timestamp = null){
+ if (is_null($timestamp)) $timestamp = date('c');
+
+ $input = sha1(print_r($this->auth, true));
+ $hash = $this->hash($input, $timestamp, $this->env['security_iteration'], $this->env['security_salt']);
+
+ return $hash;
+ }
+
+ /**
+ * Maps user profile to auth response
+ *
+ * @param array $profile User profile obtained from provider
+ * @param string $profile_path Path to a $profile property. Use dot(.) to separate levels.
+ * eg. Path to $profile['a']['b']['c'] would be 'a.b.c'
+ * @param string $auth_path Path to $this->auth that is to be set.
+ */
+ protected function mapProfile($profile, $profile_path, $auth_path){
+ $from = explode('.', $profile_path);
+
+ $base = $profile;
+ foreach ($from as $element){
+ if (is_array($base) && array_key_exists($element, $base)) $base = $base[$element];
+ else return false;
+ }
+ $value = $base;
+
+ $to = explode('.', $auth_path);
+
+ $auth = &$this->auth;
+ foreach ($to as $element){
+ $auth = &$auth[$element];
+ }
+ $auth = $value;
+ return true;
+
+ }
+
+
+ /**
+ * *****************************************************
+ * Utilities
+ * A collection of static functions for strategy's use
+ * *****************************************************
+ */
+
+ /**
+ * Static hashing funciton
+ *
+ * @param string $input Input string
+ * @param string $timestamp ISO 8601 formatted date
+ * @param int $iteration Number of hash interations
+ * @param string $salt
+ * @return string Resulting hash
+ */
+ public static function hash($input, $timestamp, $iteration, $salt){
+ $iteration = intval($iteration);
+ if ($iteration <= 0) return false;
+
+ for ($i = 0; $i < $iteration; ++$i) $input = base_convert(sha1($input.$salt.$timestamp), 16, 36);
+ return $input;
+ }
+
+ /**
+ * Redirect to $url with HTTP header (Location: )
+ *
+ * @param string $url URL to redirect user to
+ * @param boolean $exit Whether to call exit() right after redirection
+ */
+ public static function redirect($url, $exit = true){
+ header("Location: $url");
+ if ($exit) exit();
+ }
+
+ /**
+ * Client-side GET: This function builds the full HTTP URL with parameters and redirects via Location header.
+ *
+ * @param string $url Destination URL
+ * @param array $data Data
+ * @param boolean $exit Whether to call exit() right after redirection
+ */
+ public static function clientGet($url, $data = array(), $exit = true){
+ self::redirect($url.'?'.http_build_query($data, '', '&'), $exit);
+ }
+
+ /**
+ * Generates a simple HTML form with $data initialized and post results via JavaScript
+ *
+ * @param string $url URL to be POSTed
+ * @param array $data Data to be POSTed
+ */
+ public static function clientPost($url, $data = array()){
+ $html = '<html><body onload="postit();"><form name="auth" method="post" action="'.$url.'">';
+
+ if (!empty($data) && is_array($data)){
+ $flat = self::flattenArray($data);
+ foreach ($flat as $key => $value){
+ $html .= '<input type="hidden" name="'.$key.'" value="'.$value.'">';
+ }
+ }
+
+ $html .= '</form>';
+ $html .= '<script type="text/javascript">function postit(){ document.auth.submit(); }</script>';
+ $html .= '</body></html>';
+ echo $html;
+ }
+
+ /**
+ * Basic server-side HTTP GET request via self::httpRequest(), wrapper of file_get_contents
+ *
+ * @param string $url Destination URL
+ * @param array $data Data to be submitted via GET
+ * @param array $options Additional stream context options, if any
+ * @param string $responseHeaders Response headers after HTTP call. Useful for error debugging.
+ * @return string Content resulted from request, without headers
+ */
+ public static function serverGet($url, $data, $options = null, &$responseHeaders = null){
+ return self::httpRequest($url.'?'.http_build_query($data, '', '&'), $options, $responseHeaders);
+ }
+
+ /**
+ * Basic server-side HTTP POST request via self::httpRequest(), wrapper of file_get_contents
+ *
+ * @param string $url Destination URL
+ * @param array $data Data to be POSTed
+ * @param array $options Additional stream context options, if any
+ * @param string $responseHeaders Response headers after HTTP call. Useful for error debugging.
+ * @return string Content resulted from request, without headers
+ */
+ public static function serverPost($url, $data, $options = array(), &$responseHeaders = null){
+ if (!is_array($options)) $options = array();
+
+ $query = http_build_query($data, '', '&');
+
+ $stream = array('http' => array(
+ 'method' => 'POST',
+ 'header' => "Content-type: application/x-www-form-urlencoded",
+ 'content' => $query
+ ));
+
+ $stream = array_merge($options, $stream);
+
+ return self::httpRequest($url, $stream, $responseHeaders);
+ }
+
+ /**
+ * Simple server-side HTTP request with file_get_contents
+ * Provides basic HTTP calls.
+ * See serverGet() and serverPost() for wrapper functions of httpRequest()
+ *
+ * Notes:
+ * Reluctant to use any more advanced transport like cURL for the time being to not
+ * having to set cURL as being a requirement.
+ * Strategy is to provide own HTTP transport handler if requiring more advanced support.
+ *
+ * @param string $url Full URL to load
+ * @param array $options Stream context options (http://php.net/stream-context-create)
+ * @param string $responseHeaders Response headers after HTTP call. Useful for error debugging.
+ * @return string Content resulted from request, without headers
+ */
+ public static function httpRequest($url, $options = null, &$responseHeaders = null){
+ $context = null;
+ if (!empty($options) && is_array($options)){
+ $context = stream_context_create($options);
+ }
+
+ $content = @file_get_contents($url, false, $context);
+ $responseHeaders = implode("\r\n", $http_response_header);
+
+ return $content;
+ }
+
+ /**
+ * Recursively converts object into array
+ * Basically get_object_vars, but recursive.
+ *
+ * @param mixed $obj Object
+ * @return array Array of object properties
+ */
+ public static function recursiveGetObjectVars($obj){
+ $arr = array();
+ $_arr = is_object($obj) ? get_object_vars($obj) : $obj;
+
+ foreach ($_arr as $key => $val){
+ $val = (is_array($val) || is_object($val)) ? self::recursiveGetObjectVars($val) : $val;
+
+ // Transform boolean into 1 or 0 to make it safe across all Opauth HTTP transports
+ if (is_bool($val)) $val = ($val) ? 1 : 0;
+
+ $arr[$key] = $val;
+ }
+
+ return $arr;
+ }
+
+ /**
+ * Recursively converts multidimensional array into POST-friendly single dimensional array
+ *
+ * @param array $array Array to be flatten
+ * @param string $prefix String to be prefixed to flatenned variable name
+ * @param array $results Existing array of flattened inputs to be merged upon
+ *
+ * @return array A single dimensional array with POST-friendly name
+ */
+ public static function flattenArray($array, $prefix = null, $results = array()){
+ //if (is_null($prefix)) $prefix = 'array';
+
+ foreach ($array as $key => $val){
+ $name = (empty($prefix)) ? $key : $prefix."[$key]";
+
+ if (is_array($val)){
+ $results = array_merge($results, self::flattenArray($val, $name));
+ }
+ else{
+ $results[$name] = $val;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Replace defined env values enclused in {} with values from $dictionary
+ *
+ * @param string $value Input string
+ * @param array $dictionary Dictionary to lookup values from
+ * @return string String substitued with value from dictionary, if applicable
+ */
+ public static function envReplace($value, $dictionary){
+ if (is_string($value) && preg_match_all('/{([A-Za-z0-9-_]+)}/', $value, $matches)){
+ foreach ($matches[1] as $key){
+ if (array_key_exists($key, $dictionary)){
+ $value = str_replace('{'.$key.'}', $dictionary[$key], $value);
+ }
+ }
+ return $value;
+ }
+ return $value;
+ }
+
+}
147 classes/Strategy/FacebookStrategy.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Facebook strategy for Opauth
+ * based on https://developers.facebook.com/docs/authentication/server-side/
+ *
+ * More information on Opauth: http://opauth.org
+ *
+ * @copyright Copyright © 2012 U-Zyn Chua (http://uzyn.com)
+ * @link http://opauth.org
+ * @package Opauth.FacebookStrategy
+ * @license MIT License
+ */
+
+namespace Opauth;
+
+class FacebookStrategy extends OpauthStrategy{
+
+ /**
+ * Compulsory config keys, listed as unassociative arrays
+ * eg. array('app_id', 'app_secret');
+ */
+ public $expects = array('app_id', 'app_secret');
+
+ /**
+ * Optional config keys with respective default values, listed as associative arrays
+ * eg. array('scope' => 'email');
+ */
+ public $defaults = array(
+ 'redirect_uri' => '{complete_url_to_strategy}int_callback'
+ );
+
+ /**
+ * Auth request
+ */
+ public function request(){
+ $url = 'https://www.facebook.com/dialog/oauth';
+ $params = array(
+ 'client_id' => $this->strategy['app_id'],
+ 'redirect_uri' => $this->strategy['redirect_uri']
+ );
+
+ if (!empty($this->strategy['scope'])) $params['scope'] = $this->strategy['scope'];
+ if (!empty($this->strategy['state'])) $params['state'] = $this->strategy['state'];
+ if (!empty($this->strategy['response_type'])) $params['response_type'] = $this->strategy['response_type'];
+ if (!empty($this->strategy['display'])) $params['display'] = $this->strategy['display'];
+
+ $this->clientGet($url, $params);
+ }
+
+ /**
+ * Internal callback, after Facebook's OAuth
+ */
+ public function int_callback(){
+ if (array_key_exists('code', $_GET) && !empty($_GET['code'])){
+ $url = 'https://graph.facebook.com/oauth/access_token';
+ $params = array(
+ 'client_id' =>$this->strategy['app_id'],
+ 'client_secret' => $this->strategy['app_secret'],
+ 'redirect_uri'=> $this->strategy['redirect_uri'],
+ 'code' => trim($_GET['code'])
+ );
+ $response = $this->serverGet($url, $params, null, $headers);
+
+ parse_str($response, $results);
+
+ if (!empty($results) && !empty($results['access_token'])){
+ $me = $this->me($results['access_token']);
+
+ $this->auth = array(
+ 'provider' => 'Facebook',
+ 'uid' => $me->id,
+ 'info' => array(
+ 'name' => $me->name,
+ 'image' => 'https://graph.facebook.com/'.$me->id.'/picture?type=square'
+ ),
+ 'credentials' => array(
+ 'token' => $results['access_token'],
+ 'expires' => date('c', time() + $results['expires'])
+ ),
+ 'raw' => $me
+ );
+
+ if (!empty($me->email)) $this->auth['info']['email'] = $me->email;
+ if (!empty($me->username)) $this->auth['info']['nickname'] = $me->username;
+ if (!empty($me->first_name)) $this->auth['info']['first_name'] = $me->first_name;
+ if (!empty($me->last_name)) $this->auth['info']['last_name'] = $me->last_name;
+ if (!empty($me->location)) $this->auth['info']['location'] = $me->location->name;
+ if (!empty($me->link)) $this->auth['info']['urls']['facebook'] = $me->link;
+ if (!empty($me->website)) $this->auth['info']['urls']['website'] = $me->website;
+
+ /**
+ * Missing optional info values
+ * - description
+ * - phone: not accessible via Facebook Graph API
+ */
+
+ $this->callback();
+ }
+ else{
+ $error = array(
+ 'provider' => 'Facebook',
+ 'code' => 'access_token_error',
+ 'message' => 'Failed when attempting to obtain access token',
+ 'raw' => $headers
+ );
+
+ $this->errorCallback($error);
+ }
+ }
+ else{
+ $error = array(
+ 'provider' => 'Facebook',
+ 'code' => $_GET['error'],
+ 'message' => $_GET['error_description'],
+ 'raw' => $_GET
+ );
+
+ $this->errorCallback($error);
+ }
+ }
+
+ /**
+ * Queries Facebook Graph API for user info
+ *
+ * @param string $access_token
+ * @return array Parsed JSON results
+ */
+ private function me($access_token){
+ $me = $this->serverGet('https://graph.facebook.com/me', array('access_token' => $access_token), null, $headers);
+ if (!empty($me)){
+ return json_decode($me);
+ }
+ else{
+ $error = array(
+ 'provider' => 'Facebook',
+ 'code' => 'me_error',
+ 'message' => 'Failed when attempting to query for user information',
+ 'raw' => array(
+ 'response' => $me,
+ 'headers' => $headers
+ )
+ );
+
+ $this->errorCallback($error);
+ }
+ }
+}
208 classes/Strategy/TwitterStrategy.php
@@ -0,0 +1,208 @@
+<?php
+/**
+ * Twitter strategy for Opauth
+ * Based on https://dev.twitter.com/docs/auth/obtaining-access-tokens
+ *
+ * More information on Opauth: http://opauth.org
+ *
+ * @copyright Copyright © 2012 U-Zyn Chua (http://uzyn.com)
+ * @link http://opauth.org
+ * @package Opauth.TwitterStrategy
+ * @license MIT License
+ */
+
+class TwitterStrategy extends OpauthStrategy{
+
+ /**
+ * Compulsory parameters
+ */
+ public $expects = array('key', 'secret');
+
+ /**
+ * Optional parameters
+ */
+ public $defaults = array(
+ 'method' => 'POST', // The HTTP method being used. e.g. POST, GET, HEAD etc
+ 'oauth_callback' => '{complete_url_to_strategy}oauth_callback',
+
+ // For Twitter
+ 'request_token_url' => 'https://api.twitter.com/oauth/request_token',
+ 'authorize_url' => 'https://api.twitter.com/oauth/authenticate', // or 'https://api.twitter.com/oauth/authorize'
+ 'access_token_url' => 'https://api.twitter.com/oauth/access_token',
+ 'verify_credentials_json_url' => 'https://api.twitter.com/1/account/verify_credentials.json',
+ 'verify_credentials_skip_status' => true,
+ 'twitter_profile_url' => 'http://twitter.com/{screen_name}',
+
+ // From tmhOAuth
+ 'user_token' => '',
+ 'user_secret' => '',
+ 'use_ssl' => true,
+ 'debug' => false,
+ 'force_nonce' => false,
+ 'nonce' => false, // used for checking signatures. leave as false for auto
+ 'force_timestamp' => false,
+ 'timestamp' => false, // used for checking signatures. leave as false for auto
+ 'oauth_version' => '1.0',
+ 'curl_connecttimeout' => 30,
+ 'curl_timeout' => 10,
+ 'curl_ssl_verifypeer' => false,
+ 'curl_followlocation' => false, // whether to follow redirects or not
+ 'curl_proxy' => false, // really you don't want to use this if you are using streaming
+ 'curl_proxyuserpwd' => false, // format username:password for proxy, if required
+ 'is_streaming' => false,
+ 'streaming_eol' => "\r\n",
+ 'streaming_metrics_interval' => 60,
+ 'as_header' => true,
+ );
+
+ public function __construct($strategy, $env){
+ parent::__construct($strategy, $env);
+
+ $this->strategy['consumer_key'] = $this->strategy['key'];
+ $this->strategy['consumer_secret'] = $this->strategy['secret'];
+
+ require dirname(__FILE__).'/Vendor/tmhOAuth/tmhOAuth.php';
+ $this->tmhOAuth = new tmhOAuth($this->strategy);
+ }
+
+ /**
+ * Auth request
+ */
+ public function request(){
+ $params = array(
+ 'oauth_callback' => $this->strategy['oauth_callback']
+ );
+
+ $results = $this->_request('POST', $this->strategy['request_token_url'], $params);
+
+ if ($results !== false && !empty($results['oauth_token']) && !empty($results['oauth_token_secret'])){
+ session_start();
+ $_SESSION['_opauth_twitter'] = $results;
+
+ $this->_authorize($results['oauth_token']);
+ }
+ }
+
+ /**
+ * Receives oauth_verifier, requests for access_token and redirect to callback
+ */
+ public function oauth_callback(){
+ session_start();
+ $session = $_SESSION['_opauth_twitter'];
+ unset($_SESSION['_opauth_twitter']);
+
+ if ($_REQUEST['oauth_token'] == $session['oauth_token']){
+ $this->tmhOAuth->config['user_token'] = $session['oauth_token'];
+ $this->tmhOAuth->config['user_secret'] = $session['oauth_token_secret'];
+
+ $params = array(
+ 'oauth_verifier' => $_REQUEST['oauth_verifier']
+ );
+
+ $results = $this->_request('POST', $this->strategy['access_token_url'], $params);
+
+ if ($results !== false && !empty($results['oauth_token']) && !empty($results['oauth_token_secret'])){
+ $credentials = $this->_verify_credentials($results['oauth_token'], $results['oauth_token_secret']);
+
+ if (!empty($credentials['id'])){
+
+ $this->auth = array(
+ 'provider' => 'Twitter',
+ 'uid' => $credentials['id'],
+ 'info' => array(
+ 'name' => $credentials['name'],
+ 'nickname' => $credentials['screen_name'],
+ 'location' => $credentials['location'],
+ 'description' => $credentials['description'],
+ 'image' => $credentials['profile_image_url'],
+ 'urls' => array(
+ 'twitter' => str_replace('{screen_name}', $credentials['screen_name'], $this->strategy['twitter_profile_url']),
+ 'website' => $credentials['url']
+ )
+ ),
+ 'credentials' => array(
+ 'token' => $results['oauth_token'],
+ 'secret' => $results['oauth_token_secret']
+ ),
+ 'raw' => $credentials
+ );
+
+ $this->callback();
+ }
+ }
+ }
+ else{
+ $error = array(
+ 'provider' => 'Twitter',
+ 'code' => 'access_denied',
+ 'message' => 'User denied access.',
+ 'raw' => $_GET
+ );
+
+ $this->errorCallback($error);
+ }
+
+
+ }
+
+ private function _authorize($oauth_token){
+ $params = array(
+ 'oauth_token' => $oauth_token
+ );
+
+ $this->clientGet($this->strategy['authorize_url'], $params);
+ }
+
+ private function _verify_credentials($user_token, $user_token_secret){
+ $this->tmhOAuth->config['user_token'] = $user_token;
+ $this->tmhOAuth->config['user_secret'] = $user_token_secret;
+
+ $params = array( 'skip_status' => $this->strategy['verify_credentials_skip_status'] );
+
+ $response = $this->_request('GET', $this->strategy['verify_credentials_json_url'], $params);
+
+ return $this->recursiveGetObjectVars($response);
+ }
+
+
+
+ /**
+ * Wrapper of tmhOAuth's request() with Opauth's error handling.
+ *
+ * request():
+ * Make an HTTP request using this library. This method doesn't return anything.
+ * Instead the response should be inspected directly.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $useauth whether to use authentication when making the request. Default true.
+ * @param string $multipart whether this request contains multipart data. Default false
+ */
+ private function _request($method, $url, $params = array(), $useauth = true, $multipart = false){
+ $code = $this->tmhOAuth->request($method, $url, $params, $useauth, $multipart);
+
+ if ($code == 200){
+ if (strpos($url, '.json') !== false)
+ $response = json_decode($this->tmhOAuth->response['response']);
+ else
+ $response = $this->tmhOAuth->extract_params($this->tmhOAuth->response['response']);
+
+ return $response;
+ }
+ else {
+ $error = array(
+ 'provider' => 'Twitter',
+ 'code' => $code,
+ 'raw' => $this->tmhOAuth->response['response']
+ );
+
+ $this->errorCallback($error);
+
+ return false;
+ }
+
+
+ }
+
+}
202 classes/Strategy/Vendor/tmhOAuth/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
167 classes/Strategy/Vendor/tmhOAuth/README.md
@@ -0,0 +1,167 @@
+# tmhOAuth
+
+An OAuth 1.0A library written in PHP by @themattharris, specifically for use
+with the Twitter API.
+
+**Disclaimer**: This project is a work in progress. Please use the issue tracker
+to report any enhancements or issues you encounter.
+
+## Goals
+
+- Support OAuth 1.0A
+- Use Authorisation headers instead of query string or POST parameters
+- Allow uploading of images
+- Provide enough information to assist with debugging
+
+## Dependencies
+
+The library has been tested with PHP 5.3+ and relies on CURL and hash_hmac. The
+vast majority of hosting providers include these libraries and run with PHP 5.1+.
+
+The code makes use of hash_hmac, which was introduced in PHP 5.1.2. If your version
+of PHP is lower than this you should ask your hosting provider for an update.
+
+## A note about security and SSL
+
+Version 0.60 hardens the security of the library and defaults `curl_ssl_verifypeer` to `true`.
+As some hosting providers do not provide the most current certificate root file
+it is now included in this repository. If the version is out of date OR you prefer
+to download the certificate roots yourself, you can get them
+from: http://curl.haxx.se/ca/cacert.pem
+
+Before upgrading the version of tmhOAuth that you use, be sure to verify the SSL
+handling works on your server by running the `examples/verify_ssl.php` script.
+
+## Usage
+
+This will be built out later but for the moment review the examples for ways
+the library can be used. Each example contains instructions on how to use it
+
+## Notes for users of previous versions
+
+If you previously used version 0.4 be aware the utility functions
+have now been broken into their own file. Before you use version 0.5+ in your app
+test locally to ensure your code doesn't need tmhUtilities included.
+
+If you used custom HTTP request headers when they were defined as `'key: value'` strings
+you should now define them as `'key' => 'value'` pairs.
+
+## Change History ##
+
+### 0.621 - 12 March 2012
+- Ensure $_SERVER['HTTPS'] isset before checking it's value. Props: kud
+
+### 0.62 - 01 March 2012
+- Fix array merging bug. Props: julien-c
+- use is_callable instead of function_exists: Props: samwierema
+- Allow options to be specified for the entify function. Props: davidcroda
+- protocol was not inferred correctly for https when ['HTTPS'] == 'on'. Props: ospector
+- Switched to https for twitter.com display URLs
+- Improved the search results example
+
+### 0.61 - 16 January 2012
+- Removed trailing ?> from tmhOAuth.php and tmhUtilities.php to meet the Zend Framework's coding practices. Props: reedy
+- Fixed bug where CURLOPT_SSL_VERIFYHOST was defaulted to true when it should have been defaulted to 2. Props: kevinsmcarthur
+
+### 0.60 - 29 December 2011
+- Changed any use of implode to the preferred format of implode($glue, $pieces). Props: reedy
+- Moved oauth_verifier to the authorization header as shown in example of RFC 5849. Props: spacenick
+- added curl error and error number values to the $tmhOAuth->response object
+- added an example script for testing the SSL connection to twitter.com with the new SSL configuration of tmhOAuth
+- added a function to generate the useragent depending on whether SSL is on or not
+- defaulted CURLOPT_SSL_VERIFYPEER to true
+- added CURLOPT_SSL_VERIFYHOST and defaulted it to true
+- added the most current cacert.pem file from http://curl.haxx.se/ca/cacert.pem and configured curl to use it
+
+### 0.58 - 29 December 2011
+- Rearranged some configuration variables around to make commenting easier
+- Standarised on lowercase booleans
+
+### 0.57 - 11 December 2011
+- Fixed prevent_request so OAuth Echo requests work again.
+- Added a TwitPic OAuth Echo example
+
+### 0.56 - 29 September 2011
+- Fixed version reference in the UserAgent
+- Updated tmhUtilities::entify with support for media
+- Updated tmhUtilities::entify with support for multibyte characters. Props: andersonshatch
+
+### 0.55 - 29 September 2011
+- Added support for content encoding. Defaults to whatever localhost supports. Props: yusuke
+
+### 0.54 - 29 September 2011
+- User-Agent is now configurable and includes the current version number of the script
+- Updated the Streaming examples to use SSL
+
+### 0.53 - 15 July 2011
+- Fixed issue where headers were being duplicated if the library was called more than once.
+- Updated examples to fit the new location of access tokens and secrets on dev.twitter.com
+- Added Photo Tweet example
+
+### 0.52 - 06 July 2011
+- Fixed issue where the preference for include_time in create_nonce was being ignored
+
+### 0.51 - 06 July 2011
+- Use isset instead of suppress errors. Props: funkatron
+- Added example of using the Search API
+- Added example of using friends/ids and users/lookup to get details of a users friends
+- Added example of the authorize OAuth webflow
+
+### 0.5 - 29 March 2011
+- Moved utility functions out of the main class and into the tmhUtilities class.
+- Added the ability to send OAuth parameters as part of the querystring or POST body.
+- Section 3.4.1.2 says the url must be lowercase so prepare URL now does this.
+- Added a convenience method for accessing the safe_encode/decode transforms.
+- Updated the examples to use the new utilities library.
+- Added examples for sitestreams and userstreams.
+- Added a more advanced streaming API example.
+
+### 0.4 - 03 March 2011
+- Fixed handling of parameters when using DELETE. Thanks to yusuke for reporting
+- Fixed php_self to handle port numbers other than 80/443. Props: yusuke
+- Updated function pr to use pre only when not running in CLI mode
+- Add support for proxy servers. Props juanchorossi
+- Function request now returns the HTTP status code. Props: kronenthaler
+- Documentation fixes for xAuth. Props: 140dev
+- Some minor code formatting changes
+
+### 0.3 - 28 September 2010
+- Moved entities rendering into the library
+
+### 0.2 - 17 September 2010
+- Added support for the Streaming API
+
+### 0.14 - 17 September 2010
+- Fixed authorisation header for use with OAuth Echo
+
+### 0.13 - 17 September 2010
+- Added use_ssl configuration parameter
+- Fixed config array typo
+- Removed v from the config
+- Remove protocol from the host (configured by use_ssl)
+- Added include for easier debugging
+
+### 0.12 - 17 September 2010
+
+- Moved curl options to config
+- Added the ability for curl to follow redirects, default false
+
+### 0.11 - 17 September 2010
+
+- Fixed a bug in the GET requests
+
+### 0.1 - 26 August 2010
+
+- Initial beta version
+
+## Community
+
+License: Apache 2 (see included LICENSE file)
+
+Follow me on Twitter: <https://twitter.com/intent/follow?screen_name=themattharris>
+Check out the Twitter Developer Resources: <http://dev.twitter.com>
+
+## To Do
+
+- Add good behavior logic to the Streaming API handler - i.e. on disconnect back off
+- Async Curl support
656 classes/Strategy/Vendor/tmhOAuth/tmhOAuth.php
@@ -0,0 +1,656 @@
+<?php
+/**
+ * tmhOAuth
+ *
+ * An OAuth 1.0A library written in PHP.
+ * The library supports file uploading using multipart/form as well as general
+ * REST requests. OAuth authentication is sent using the an Authorization Header.
+ *
+ * @author themattharris
+ * @version 0.621
+ *
+ * 12 March 2012
+ */
+class tmhOAuth {
+ const VERSION = 0.621;
+
+ /**
+ * Creates a new tmhOAuth object
+ *
+ * @param string $config, the configuration to use for this request
+ */
+ function __construct($config) {
+ $this->params = array();
+ $this->headers = array();
+ $this->auto_fixed_time = false;
+ $this->buffer = null;
+
+ // default configuration options
+ $this->config = array_merge(
+ array(
+ // leave 'user_agent' blank for default, otherwise set this to
+ // something that clearly identifies your app
+ 'user_agent' => '',
+
+ 'use_ssl' => true,
+ 'host' => 'api.twitter.com',
+
+ 'consumer_key' => '',
+ 'consumer_secret' => '',
+ 'user_token' => '',
+ 'user_secret' => '',
+ 'force_nonce' => false,
+ 'nonce' => false, // used for checking signatures. leave as false for auto
+ 'force_timestamp' => false,
+ 'timestamp' => false, // used for checking signatures. leave as false for auto
+
+ // oauth signing variables that are not dynamic
+ 'oauth_version' => '1.0',
+ 'oauth_signature_method' => 'HMAC-SHA1',
+
+ // you probably don't want to change any of these curl values
+ 'curl_connecttimeout' => 30,
+ 'curl_timeout' => 10,
+
+ // for security this should always be set to 2.
+ 'curl_ssl_verifyhost' => 2,
+ // for security this should always be set to true.
+ 'curl_ssl_verifypeer' => true,
+
+ // you can get the latest cacert.pem from here http://curl.haxx.se/ca/cacert.pem
+ 'curl_cainfo' => dirname(__FILE__) . '/cacert.pem',
+ 'curl_capath' => dirname(__FILE__),
+
+ 'curl_followlocation' => false, // whether to follow redirects or not
+
+ // support for proxy servers
+ 'curl_proxy' => false, // really you don't want to use this if you are using streaming
+ 'curl_proxyuserpwd' => false, // format username:password for proxy, if required
+ 'curl_encoding' => '', // leave blank for all supported formats, else use gzip, deflate, identity
+
+ // streaming API
+ 'is_streaming' => false,
+ 'streaming_eol' => "\r\n",
+ 'streaming_metrics_interval' => 60,
+
+ // header or querystring. You should always use header!
+ // this is just to help me debug other developers implementations
+ 'as_header' => true,
+ 'debug' => false,
+ ),
+ $config
+ );
+ $this->set_user_agent();
+ }
+
+ function set_user_agent() {
+ if (!empty($this->config['user_agent']))
+ return;
+
+ if ($this->config['curl_ssl_verifyhost'] && $this->config['curl_ssl_verifypeer']) {
+ $ssl = '+SSL';
+ } else {
+ $ssl = '-SSL';
+ }
+
+ $ua = 'tmhOAuth ' . self::VERSION . $ssl . ' - //github.com/themattharris/tmhOAuth';
+ $this->config['user_agent'] = $ua;
+ }
+
+ /**
+ * Generates a random OAuth nonce.
+ * If 'force_nonce' is true a nonce is not generated and the value in the configuration will be retained.
+ *
+ * @param string $length how many characters the nonce should be before MD5 hashing. default 12
+ * @param string $include_time whether to include time at the beginning of the nonce. default true
+ * @return void
+ */
+ private function create_nonce($length=12, $include_time=true) {
+ if ($this->config['force_nonce'] == false) {
+ $sequence = array_merge(range(0,9), range('A','Z'), range('a','z'));
+ $length = $length > count($sequence) ? count($sequence) : $length;
+ shuffle($sequence);
+
+ $prefix = $include_time ? microtime() : '';
+ $this->config['nonce'] = md5(substr($prefix . implode('', $sequence), 0, $length));
+ }
+ }
+
+ /**
+ * Generates a timestamp.
+ * If 'force_timestamp' is true a nonce is not generated and the value in the configuration will be retained.
+ *
+ * @return void
+ */
+ private function create_timestamp() {
+ $this->config['timestamp'] = ($this->config['force_timestamp'] == false ? time() : $this->config['timestamp']);
+ }
+
+ /**
+ * Encodes the string or array passed in a way compatible with OAuth.
+ * If an array is passed each array value will will be encoded.
+ *
+ * @param mixed $data the scalar or array to encode
+ * @return $data encoded in a way compatible with OAuth
+ */
+ private function safe_encode($data) {
+ if (is_array($data)) {
+ return array_map(array($this, 'safe_encode'), $data);
+ } else if (is_scalar($data)) {
+ return str_ireplace(
+ array('+', '%7E'),
+ array(' ', '~'),
+ rawurlencode($data)
+ );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Decodes the string or array from it's URL encoded form
+ * If an array is passed each array value will will be decoded.
+ *
+ * @param mixed $data the scalar or array to decode
+ * @return $data decoded from the URL encoded form
+ */
+ private function safe_decode($data) {
+ if (is_array($data)) {
+ return array_map(array($this, 'safe_decode'), $data);
+ } else if (is_scalar($data)) {
+ return rawurldecode($data);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Returns an array of the standard OAuth parameters.
+ *
+ * @return array all required OAuth parameters, safely encoded
+ */
+ private function get_defaults() {
+ $defaults = array(
+ 'oauth_version' => $this->config['oauth_version'],
+ 'oauth_nonce' => $this->config['nonce'],
+ 'oauth_timestamp' => $this->config['timestamp'],
+ 'oauth_consumer_key' => $this->config['consumer_key'],
+ 'oauth_signature_method' => $this->config['oauth_signature_method'],
+ );
+
+ // include the user token if it exists
+ if ( $this->config['user_token'] )
+ $defaults['oauth_token'] = $this->config['user_token'];
+
+ // safely encode
+ foreach ($defaults as $k => $v) {
+ $_defaults[$this->safe_encode($k)] = $this->safe_encode($v);
+ }
+
+ return $_defaults;
+ }
+
+ /**
+ * Extracts and decodes OAuth parameters from the passed string
+ *
+ * @param string $body the response body from an OAuth flow method
+ * @return array the response body safely decoded to an array of key => values
+ */
+ function extract_params($body) {
+ $kvs = explode('&', $body);
+ $decoded = array();
+ foreach ($kvs as $kv) {
+ $kv = explode('=', $kv, 2);
+ $kv[0] = $this->safe_decode($kv[0]);
+ $kv[1] = $this->safe_decode($kv[1]);
+ $decoded[$kv[0]] = $kv[1];
+ }
+ return $decoded;
+ }
+
+ /**
+ * Prepares the HTTP method for use in the base string by converting it to
+ * uppercase.
+ *
+ * @param string $method an HTTP method such as GET or POST
+ * @return void value is stored to a class variable
+ * @author themattharris
+ */
+ private function prepare_method($method) {
+ $this->method = strtoupper($method);
+ }
+
+ /**
+ * Prepares the URL for use in the base string by ripping it apart and
+ * reconstructing it.
+ *
+ * Ref: 3.4.1.2
+ *
+ * @param string $url the request URL
+ * @return void value is stored to a class variable
+ * @author themattharris
+ */
+ private function prepare_url($url) {
+ $parts = parse_url($url);
+
+ $port = isset($parts['port']) ? $parts['port'] : false;
+ $scheme = $parts['scheme'];
+ $host = $parts['host'];
+ $path = isset($parts['path']) ? $parts['path'] : false;
+
+ $port or $port = ($scheme == 'https') ? '443' : '80';
+
+ if (($scheme == 'https' && $port != '443')
+ || ($scheme == 'http' && $port != '80')) {
+ $host = "$host:$port";
+ }
+ $this->url = strtolower("$scheme://$host$path");
+ }
+
+ /**
+ * Prepares all parameters for the base string and request.
+ * Multipart parameters are ignored as they are not defined in the specification,
+ * all other types of parameter are encoded for compatibility with OAuth.
+ *
+ * @param array $params the parameters for the request
+ * @return void prepared values are stored in class variables
+ */
+ private function prepare_params($params) {
+ // do not encode multipart parameters, leave them alone
+ if ($this->config['multipart']) {
+ $this->request_params = $params;
+ $params = array();
+ }
+
+ // signing parameters are request parameters + OAuth default parameters
+ $this->signing_params = array_merge($this->get_defaults(), (array)$params);
+
+ // Remove oauth_signature if present
+ // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+ if (isset($this->signing_params['oauth_signature'])) {
+ unset($this->signing_params['oauth_signature']);
+ }
+
+ // Parameters are sorted by name, using lexicographical byte value ordering.
+ // Ref: Spec: 9.1.1 (1)
+ uksort($this->signing_params, 'strcmp');
+
+ // encode. Also sort the signed parameters from the POST parameters
+ foreach ($this->signing_params as $k => $v) {
+ $k = $this->safe_encode($k);
+ $v = $this->safe_encode($v);
+ $_signing_params[$k] = $v;
+ $kv[] = "{$k}={$v}";
+ }
+
+ // auth params = the default oauth params which are present in our collection of signing params
+ $this->auth_params = array_intersect_key($this->get_defaults(), $_signing_params);
+ if (isset($_signing_params['oauth_callback'])) {
+ $this->auth_params['oauth_callback'] = $_signing_params['oauth_callback'];
+ unset($_signing_params['oauth_callback']);
+ }
+
+ if (isset($_signing_params['oauth_verifier'])) {
+ $this->auth_params['oauth_verifier'] = $_signing_params['oauth_verifier'];
+ unset($_signing_params['oauth_verifier']);
+ }
+
+ // request_params is already set if we're doing multipart, if not we need to set them now
+ if ( ! $this->config['multipart'])
+ $this->request_params = array_diff_key($_signing_params, $this->get_defaults());
+
+ // create the parameter part of the base string
+ $this->signing_params = implode('&', $kv);
+ }
+
+ /**
+ * Prepares the OAuth signing key
+ *
+ * @return void prepared signing key is stored in a class variables
+ */
+ private function prepare_signing_key() {
+ $this->signing_key = $this->safe_encode($this->config['consumer_secret']) . '&' . $this->safe_encode($this->config['user_secret']);
+ }
+
+ /**
+ * Prepare the base string.
+ * Ref: Spec: 9.1.3 ("Concatenate Request Elements")
+ *
+ * @return void prepared base string is stored in a class variables
+ */
+ private function prepare_base_string() {
+ $base = array(
+ $this->method,
+ $this->url,
+ $this->signing_params
+ );
+ $this->base_string = implode('&', $this->safe_encode($base));
+ }
+
+ /**
+ * Prepares the Authorization header
+ *
+ * @return void prepared authorization header is stored in a class variables
+ */
+ private function prepare_auth_header() {
+ $this->headers = array();
+ uksort($this->auth_params, 'strcmp');
+ if (!$this->config['as_header']) :
+ $this->request_params = array_merge($this->request_params, $this->auth_params);
+ return;
+ endif;
+
+ foreach ($this->auth_params as $k => $v) {
+ $kv[] = "{$k}=\"{$v}\"";
+ }
+ $this->auth_header = 'OAuth ' . implode(', ', $kv);
+ $this->headers['Authorization'] = $this->auth_header;
+ }
+
+ /**
+ * Signs the request and adds the OAuth signature. This runs all the request
+ * parameter preparation methods.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $useauth whether to use authentication when making the request.
+ */
+ private function sign($method, $url, $params, $useauth) {
+ $this->prepare_method($method);
+ $this->prepare_url($url);
+ $this->prepare_params($params);
+
+ // we don't sign anything is we're not using auth
+ if ($useauth) {
+ $this->prepare_base_string();
+ $this->prepare_signing_key();
+
+ $this->auth_params['oauth_signature'] = $this->safe_encode(
+ base64_encode(
+ hash_hmac(
+ 'sha1', $this->base_string, $this->signing_key, true
+ )));
+
+ $this->prepare_auth_header();
+ }
+ }
+
+ /**
+ * Make an HTTP request using this library. This method doesn't return anything.
+ * Instead the response should be inspected directly.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $useauth whether to use authentication when making the request. Default true.
+ * @param string $multipart whether this request contains multipart data. Default false
+ */
+ function request($method, $url, $params=array(), $useauth=true, $multipart=false) {
+ $this->config['multipart'] = $multipart;
+
+ $this->create_nonce();
+ $this->create_timestamp();
+
+ $this->sign($method, $url, $params, $useauth);
+ return $this->curlit();
+ }
+
+ /**
+ * Make a long poll HTTP request using this library. This method is
+ * different to the other request methods as it isn't supposed to disconnect
+ *
+ * Using this method expects a callback which will receive the streaming
+ * responses.
+ *
+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
+ * @param string $url the request URL without query string parameters
+ * @param array $params the request parameters as an array of key=value pairs
+ * @param string $callback the callback function to stream the buffer to.
+ */
+ function streaming_request($method, $url, $params=array(), $callback='') {
+ if ( ! empty($callback) ) {
+ if ( ! is_callable($callback) ) {
+ return false;
+ }
+ $this->config['streaming_callback'] = $callback;
+ }
+ $this->metrics['start'] = time();
+ $this->metrics['interval_start'] = $this->metrics['start'];
+ $this->metrics['tweets'] = 0;
+ $this->metrics['last_tweets'] = 0;
+ $this->metrics['bytes'] = 0;
+ $this->metrics['last_bytes'] = 0;
+ $this->config['is_streaming'] = true;
+ $this->request($method, $url, $params);
+ }
+
+ /**
+ * Handles the updating of the current Streaming API metrics.
+ */
+ function update_metrics() {
+ $now = time();
+ if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now)
+ return false;
+
+ $this->metrics['tps'] = round( ($this->metrics['tweets'] - $this->metrics['last_tweets']) / $this->config['streaming_metrics_interval'], 2);
+ $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2);
+
+ $this->metrics['last_bytes'] = $this->metrics['bytes'];
+ $this->metrics['last_tweets'] = $this->metrics['tweets'];
+ $this->metrics['interval_start'] = $now;
+ return $this->metrics;
+ }
+
+ /**
+ * Utility function to create the request URL in the requested format
+ *
+ * @param string $request the API method without extension
+ * @param string $format the format of the response. Default json. Set to an empty string to exclude the format
+ * @return string the concatenation of the host, API version, API method and format
+ */
+ function url($request, $format='json') {
+ $format = strlen($format) > 0 ? ".$format" : '';
+ $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/';
+
+ // backwards compatibility with v0.1
+ if (isset($this->config['v']))
+ $this->config['host'] = $this->config['host'] . '/' . $this->config['v'];
+
+ return implode('/', array(
+ $proto,
+ $this->config['host'],
+ $request . $format
+ ));
+ }
+
+ /**
+ * Public access to the private safe decode/encode methods
+ *
+ * @param string $text the text to transform
+ * @param string $mode the transformation mode. either encode or decode
+ * @return the string as transformed by the given mode
+ */
+ function transformText($text, $mode='encode') {
+ return $this->{"safe_$mode"}($text);
+ }
+
+ /**
+ * Utility function to parse the returned curl headers and store them in the
+ * class array variable.
+ *
+ * @param object $ch curl handle
+ * @param string $header the response headers
+ * @return the string length of the header
+ */
+ private function curlHeader($ch, $header) {
+ $i = strpos($header, ':');
+ if ( ! empty($i) ) {
+ $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
+ $value = trim(substr($header, $i + 2));
+ $this->response['headers'][$key] = $value;
+ }
+ return strlen($header);
+ }
+
+ /**
+ * Utility function to parse the returned curl buffer and store them until
+ * an EOL is found. The buffer for curl is an undefined size so we need
+ * to collect the content until an EOL is found.
+ *
+ * This function calls the previously defined streaming callback method.
+ *
+ * @param object $ch curl handle
+ * @param string $data the current curl buffer
+ */
+ private function curlWrite($ch, $data) {
+ $l = strlen($data);
+ if (strpos($data, $this->config['streaming_eol']) === false) {
+ $this->buffer .= $data;
+ return $l;
+ }
+
+ $buffered = explode($this->config['streaming_eol'], $data);
+ $content = $this->buffer . $buffered[0];
+
+ $this->metrics['tweets']++;
+ $this->metrics['bytes'] += strlen($content);
+
+ if ( ! is_callable($this->config['streaming_callback']))
+ return 0;
+
+ $metrics = $this->update_metrics();
+ $stop = call_user_func(
+ $this->config['streaming_callback'],
+ $content,
+ strlen($content),
+ $metrics
+ );
+ $this->buffer = $buffered[1];
+ if ($stop)
+ return 0;
+
+ return $l;
+ }
+
+ /**
+ * Makes a curl request. Takes no parameters as all should have been prepared
+ * by the request method
+ *
+ * @return void response data is stored in the class variable 'response'
+ */
+ private function curlit() {
+ // method handling
+ switch ($this->method) {
+ case 'POST':
+ break;
+ default:
+ // GET, DELETE request so convert the parameters to a querystring
+ if ( ! empty($this->request_params)) {
+ foreach ($this->request_params as $k => $v) {
+ // Multipart params haven't been encoded yet.
+ // Not sure why you would do a multipart GET but anyway, here's the support for it
+ if ($this->config['multipart']) {
+ $params[] = $this->safe_encode($k) . '=' . $this->safe_encode($v);
+ } else {
+ $params[] = $k . '=' . $v;
+ }
+ }
+ $qs = implode('&', $params);
+ $this->url = strlen($qs) > 0 ? $this->url . '?' . $qs : $this->url;
+ $this->request_params = array();
+ }
+ break;
+ }
+
+ // configure curl
+ $c = curl_init();
+ curl_setopt_array($c, array(
+ CURLOPT_USERAGENT => $this->config['user_agent'],
+ CURLOPT_CONNECTTIMEOUT => $this->config['curl_connecttimeout'],
+ CURLOPT_TIMEOUT => $this->config['curl_timeout'],
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_SSL_VERIFYPEER => $this->config['curl_ssl_verifypeer'],
+ CURLOPT_SSL_VERIFYHOST => $this->config['curl_ssl_verifyhost'],
+
+ CURLOPT_FOLLOWLOCATION => $this->config['curl_followlocation'],
+ CURLOPT_PROXY => $this->config['curl_proxy'],
+ CURLOPT_ENCODING => $this->config['curl_encoding'],
+ CURLOPT_URL => $this->url,
+ // process the headers
+ CURLOPT_HEADERFUNCTION => array($this, 'curlHeader'),
+ CURLOPT_HEADER => false,
+ CURLINFO_HEADER_OUT => true,
+ ));
+
+ if ($this->config['curl_cainfo'] !== false)
+ curl_setopt($c, CURLOPT_CAINFO, $this->config['curl_cainfo']);
+
+ if ($this->config['curl_capath'] !== false)
+ curl_setopt($c, CURLOPT_CAPATH, $this->config['curl_capath']);
+
+ if ($this->config['curl_proxyuserpwd'] !== false)
+ curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']);
+
+ if ($this->config['is_streaming']) {
+ // process the body
+ $this->response['content-length'] = 0;
+ curl_setopt($c, CURLOPT_TIMEOUT, 0);
+ curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
+ }
+
+ switch ($this->method) {
+ case 'GET':
+ break;
+ case 'POST':
+ curl_setopt($c, CURLOPT_POST, true);
+ break;
+ default:
+ curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method);