. * * @category Plugin * @package StatusNet * @author Brion Vibber * @copyright 2010 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ class OAuthData { public $consumer_key, $consumer_secret, $token, $token_secret; } /** * */ abstract class JsonStreamReader { const CRLF = "\r\n"; public $id; protected $socket = null; protected $state = 'init'; // 'init', 'connecting', 'waiting', 'headers', 'active' public function __construct() { $this->id = get_class($this) . '.' . substr(md5(mt_rand()), 0, 8); } /** * Starts asynchronous connect operation... * * @fixme Can we do the open-socket fully async to? (need write select infrastructure) * * @param string $url */ public function connect($url) { common_debug("$this->id opening connection to $url"); $scheme = parse_url($url, PHP_URL_SCHEME); if ($scheme == 'http') { $rawScheme = 'tcp'; } else if ($scheme == 'https') { $rawScheme = 'ssl'; } else { // TRANS: Server exception thrown when an invalid URL scheme is detected. throw new ServerException(_m('Invalid URL scheme for HTTP stream reader.')); } $host = parse_url($url, PHP_URL_HOST); $port = parse_url($url, PHP_URL_PORT); if (!$port) { if ($scheme == 'https') { $port = 443; } else { $port = 80; } } $path = parse_url($url, PHP_URL_PATH); $query = parse_url($url, PHP_URL_QUERY); if ($query) { $path .= '?' . $query; } $errno = $errstr = null; $timeout = 5; //$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT; $flags = STREAM_CLIENT_CONNECT; // @fixme add SSL params $this->socket = stream_socket_client("$rawScheme://$host:$port", $errno, $errstr, $timeout, $flags); $this->send($this->httpOpen($host, $path)); stream_set_blocking($this->socket, false); $this->state = 'waiting'; } /** * Send some fun data off to the server. * * @param string $buffer */ function send($buffer) { fwrite($this->socket, $buffer); } /** * Read next packet of data from the socket. * * @return string */ function read() { $buffer = fread($this->socket, 65536); return $buffer; } /** * Build HTTP request headers. * * @param string $host * @param string $path * @return string */ protected function httpOpen($host, $path) { $lines = array( "GET $path HTTP/1.1", "Host: $host", 'User-Agent: ' . HTTPClient::userAgent() . ' (TwitterBridgePlugin)', "Connection: close", "", "" ); return implode(self::CRLF, $lines); } /** * Close the current connection, if open. */ public function close() { if ($this->isConnected()) { common_debug("$this->id closing connection."); fclose($this->socket); $this->socket = null; } } /** * Are we currently connected? * * @return boolean */ public function isConnected() { return $this->socket !== null; } /** * Send any sockets we're listening on to the IO manager * to wait for input. * * @return array of resources */ public function getSockets() { if ($this->isConnected()) { return array($this->socket); } return array(); } /** * Take a chunk of input over the horn and go go go! :D * * @param string $buffer */ public function handleInput($socket) { if ($this->socket !== $socket) { // TRANS: Exception thrown when input from an inexpected socket is encountered. throw new Exception(_m('Got input from unexpected socket!')); } try { $buffer = $this->read(); $lines = explode(self::CRLF, $buffer); foreach ($lines as $line) { $this->handleLine($line); } } catch (Exception $e) { common_log(LOG_ERR, "$this->id aborting connection due to error: " . $e->getMessage()); fclose($this->socket); throw $e; } } protected function handleLine($line) { switch ($this->state) { case 'waiting': $this->handleLineWaiting($line); break; case 'headers': $this->handleLineHeaders($line); break; case 'active': $this->handleLineActive($line); break; default: // TRANS: Exception thrown when an invalid state is encountered in handleLine. // TRANS: %s is the invalid state. throw new Exception(sprintf(_m('Invalid state in handleLine: %s.'),$this->state)); } } /** * * @param $line */ protected function handleLineWaiting($line) { $bits = explode(' ', $line, 3); if (count($bits) != 3) { // TRANS: Exception thrown when an invalid response line is encountered. // TRANS: %s is the invalid line. throw new Exception(sprintf(_m('Invalid HTTP response line: %s.'),$line)); } list($http, $status, $text) = $bits; if (substr($http, 0, 5) != 'HTTP/') { // TRANS: Exception thrown when an invalid response line part is encountered. // TRANS: %1$s is the chunk, %2$s is the line. throw new Exception(sprintf(_m('Invalid HTTP response line chunk "%1$s": %2$s.'),$http, $line)); } if ($status != '200') { // TRANS: Exception thrown when an invalid response code is encountered. // TRANS: %1$s is the response code, %2$s is the line. throw new Exception(sprintf(_m('Bad HTTP response code %1$s: %2$s.'),$status,$line)); } common_debug("$this->id $line"); $this->state = 'headers'; } protected function handleLineHeaders($line) { if ($line == '') { $this->state = 'active'; common_debug("$this->id connection is active!"); } else { common_debug("$this->id read HTTP header: $line"); $this->responseHeaders[] = $line; } } protected function handleLineActive($line) { if ($line == "") { // Server sends empty lines as keepalive. return; } $data = json_decode($line); if ($data) { $this->handleJson($data); } else { common_log(LOG_ERR, "$this->id received bogus JSON data: " . var_export($line, true)); } } abstract protected function handleJson(stdClass $data); }