From 54a3dd8d1019cbeba2d312e61378ced7c670038a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 6 Mar 2011 12:05:58 +0300 Subject: [PATCH] switch twitter support from twitteroauth to tmhOAuth --- functions.php | 44 +- lib/tmhoauth/LICENSE | 202 +++++++ lib/tmhoauth/README.md | 76 +++ lib/tmhoauth/tmhOAuth.php | 726 +++++++++++++++++++++++++ lib/twitteroauth/LICENSE | 22 - lib/twitteroauth/OAuth.php | 874 ------------------------------ lib/twitteroauth/twitteroauth.php | 245 --------- twitter.php | 68 +-- 8 files changed, 1072 insertions(+), 1185 deletions(-) create mode 100644 lib/tmhoauth/LICENSE create mode 100644 lib/tmhoauth/README.md create mode 100644 lib/tmhoauth/tmhOAuth.php delete mode 100644 lib/twitteroauth/LICENSE delete mode 100644 lib/twitteroauth/OAuth.php delete mode 100644 lib/twitteroauth/twitteroauth.php diff --git a/functions.php b/functions.php index 33ac51d2..a0608ded 100644 --- a/functions.php +++ b/functions.php @@ -108,7 +108,7 @@ require_once 'lib/phpmailer/class.phpmailer.php'; require_once 'lib/sphinxapi.php'; - require_once 'lib/twitteroauth/twitteroauth.php'; + require_once 'lib/tmhoauth/tmhOAuth.php'; //define('MAGPIE_USER_AGENT_EXT', ' (Tiny Tiny RSS/' . VERSION . ')'); define('MAGPIE_OUTPUT_ENCODING', 'UTF-8'); @@ -2933,7 +2933,7 @@ $has_oauth = db_fetch_result($result, 0, 'twitter_oauth'); - if (!$has_oauth || strpos($url, '://twitter.com') === false) { + if (!$has_oauth || strpos($url, '://api.twitter.com') === false) { if (!fetch_file_contents($url)) return 5; if (url_is_html($url)) { @@ -7056,25 +7056,47 @@ return $obj; } + function fetch_twitter_rss($link, $url, $owner_uid) { $result = db_query($link, "SELECT twitter_oauth FROM ttrss_users WHERE id = $owner_uid"); $access_token = json_decode(db_fetch_result($result, 0, 'twitter_oauth'), true); + $url_escaped = db_escape_string($url); if ($access_token) { - - /* Create a TwitterOauth object with consumer/user tokens. */ - $connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $access_token['oauth_token'], $access_token['oauth_token_secret']); - - /* If method is set change API call made. Test is called by default. */ - $content = $connection->get($url); - $rss = new MagpieRSS($content, MAGPIE_OUTPUT_ENCODING, - MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING ); + $tmhOAuth = new tmhOAuth(array( + 'consumer_key' => CONSUMER_KEY, + 'consumer_secret' => CONSUMER_SECRET, + 'user_token' => $access_token['oauth_token'], + 'user_secret' => $access_token['oauth_token_secret'], + )); + + $code = $tmhOAuth->request('GET', $url); + + if ($code == 200) { + + $content = $tmhOAuth->response['response']; + + $rss = new MagpieRSS($content, MAGPIE_OUTPUT_ENCODING, + MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING ); + + return $rss; + + } else { + + db_query($link, "UPDATE ttrss_feeds + SET last_error = 'OAuth authorization failed ($code).' + WHERE feed_url = '$url_escaped' AND owner_uid = $owner_uid"); + } - return $rss; } else { + + db_query($link, "UPDATE ttrss_feeds + SET last_error = 'OAuth information not found.' + WHERE feed_url = '$url_escaped' AND owner_uid = " . $_SESSION['uid']); + return false; } } diff --git a/lib/tmhoauth/LICENSE b/lib/tmhoauth/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/lib/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. diff --git a/lib/tmhoauth/README.md b/lib/tmhoauth/README.md new file mode 100644 index 00000000..3b647258 --- /dev/null +++ b/lib/tmhoauth/README.md @@ -0,0 +1,76 @@ +# 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 and may contain bugs. + +## 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 + +## Dependancies + +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 you version +of PHP is lower than this you should ask your hosting provider for an update. + +## 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 + +## Change History +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: +Check out the Twitter Developer Resources: + +## To Do + +- Add good behavior logic to the Streaming API handler - i.e. on disconnect back off +- Add demo of responsible rate limit handling +- Async Curl support +- Split Utilities functions out \ No newline at end of file diff --git a/lib/tmhoauth/tmhOAuth.php b/lib/tmhoauth/tmhOAuth.php new file mode 100644 index 00000000..643ad09e --- /dev/null +++ b/lib/tmhoauth/tmhOAuth.php @@ -0,0 +1,726 @@ +params = array(); + $this->auto_fixed_time = false; + + // default configuration options + $this->config = array_merge( + array( + 'consumer_key' => '', + 'consumer_secret' => '', + 'user_token' => '', + 'user_secret' => '', + 'use_ssl' => true, + 'host' => 'api.twitter.com', + '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', + + // you probably don't want to change any of these curl values + 'curl_connecttimeout' => 30, + 'curl_timeout' => 10, + // for security you may want to set this to TRUE. If you do you need + // to install the servers certificate in your local certificate store. + 'curl_ssl_verifypeer' => false, + '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 + + // streaming API + 'is_streaming' => false, + 'streaming_eol' => "\r\n", + 'streaming_metrics_interval' => 60, + ), + $config + ); + } + + /** + * 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); + $this->config['nonce'] = md5(substr(microtime() . 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' => 'HMAC-SHA1', + ); + + // 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. + * + * @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 = @$parts['port']; + $scheme = $parts['scheme']; + $host = $parts['host']; + $path = @$parts['path']; + + $port or $port = ($scheme == 'https') ? '443' : '80'; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + $this->url = "$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']); + } + + // 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'); + 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($multipart); + } + + /** + * Make an HTTP request using this library. This method is different to 'request' + * because on a 401 error it will retry the request. + * + * When a 401 error is returned it is possible the timestamp of the client is + * too different to that of the API server. In this situation it is recommended + * the request is retried with the OAuth timestamp set to the same as the API + * server. This method will automatically try that technique. + * + * 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 auto_fix_time_request($method, $url, $params=array(), $useauth=true, $multipart=false) { + $this->request($method, $url, $params, $useauth, $multipart); + + // if we're not doing auth the timestamp isn't important + if ( ! $useauth) + return; + + // some error that isn't a 401 + if ($this->response['code'] != 401) + return; + + // some error that is a 401 but isn't because the OAuth token and signature are incorrect + // TODO: this check is horrid but helps avoid requesting twice when the username and password are wrong + if (stripos($this->response['response'], 'password') !== false) + return; + + // force the timestamp to be the same as the Twitter servers, and re-request + $this->auto_fixed_time = true; + $this->config['force_timestamp'] = true; + $this->config['timestamp'] = strtotime($this->response['headers']['date']); + $this->request($method, $url, $params, $useauth, $multipart); + } + + /** + * 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 ( ! function_exists($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 + )); + } + + /** + * 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 ( ! function_exists($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; + } + + if (@$this->config['prevent_request']) + return; + + // configure curl + $c = curl_init(); + curl_setopt($c, CURLOPT_USERAGENT, "themattharris' HTTP Client"); + curl_setopt($c, CURLOPT_CONNECTTIMEOUT, $this->config['curl_connecttimeout']); + curl_setopt($c, CURLOPT_TIMEOUT, $this->config['curl_timeout']); + curl_setopt($c, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($c, CURLOPT_SSL_VERIFYPEER, $this->config['curl_ssl_verifypeer']); + curl_setopt($c, CURLOPT_FOLLOWLOCATION, $this->config['curl_followlocation']); + curl_setopt($c, CURLOPT_PROXY, $this->config['curl_proxy']); + curl_setopt($c, CURLOPT_URL, $this->url); + // process the headers + curl_setopt($c, CURLOPT_HEADERFUNCTION, array($this, 'curlHeader')); + curl_setopt($c, CURLOPT_HEADER, FALSE); + curl_setopt($c, CURLINFO_HEADER_OUT, true); + + 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); + } + + if ( ! empty($this->request_params) ) { + // if not doing multipart we need to implode the parameters + if ( ! $this->config['multipart'] ) { + foreach ($this->request_params as $k => $v) { + $ps[] = "{$k}={$v}"; + } + $this->request_params = implode('&', $ps); + } + curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_params); + } else { + // CURL will set length to -1 when there is no data, which breaks Twitter + $this->headers[] = 'Content-Type:'; + $this->headers[] = 'Content-Length:'; + } + + // CURL defaults to setting this to Expect: 100-Continue which Twitter rejects + $this->headers[] = 'Expect:'; + + if ( ! empty($this->headers)) + curl_setopt($c, CURLOPT_HTTPHEADER, $this->headers); + + // do it! + $response = curl_exec($c); + $code = curl_getinfo($c, CURLINFO_HTTP_CODE); + $info = curl_getinfo($c); + curl_close($c); + + // store the response + $this->response['code'] = $code; + $this->response['response'] = $response; + $this->response['info'] = $info; + return $code; + } + + /** + * Debug function for printing the content of an object + * + * @param mixes $obj + */ + function pr($obj) { + $cli = (PHP_SAPI == 'cli' && empty($_SERVER['REMOTE_ADDR'])); + if (!$cli) + echo '
';
+    if ( is_object($obj) )
+      print_r($obj);
+    elseif ( is_array($obj) )
+      print_r($obj);
+    else
+      echo $obj;
+    if (!$cli)
+      echo '
'; + } + + /** + * Returns the current URL. This is instead of PHP_SELF which is unsafe + * + * @param bool $dropqs whether to drop the querystring or not. Default true + * @return string the current URL + */ + function php_self($dropqs=true) { + $url = sprintf('%s://%s%s', + empty($_SERVER['HTTPS']) ? 'http' : 'https', + $_SERVER['SERVER_NAME'], + $_SERVER['REQUEST_URI'] + ); + + $parts = parse_url($url); + + $port = $_SERVER['SERVER_PORT']; + $scheme = $parts['scheme']; + $host = $parts['host']; + $path = @$parts['path']; + $qs = @$parts['query']; + + $port or $port = ($scheme == 'https') ? '443' : '80'; + + if (($scheme == 'https' && $port != '443') + || ($scheme == 'http' && $port != '80')) { + $host = "$host:$port"; + } + $url = "$scheme://$host$path"; + if ( ! $dropqs) + return "{$url}?{$qs}"; + else + return $url; + } + + /** + * Entifies the tweet using the given entities element + * + * @param array $tweet the json converted to normalised array + * @return the tweet text with entities replaced with hyperlinks + */ + function entify($tweet) { + $keys = array(); + $replacements = array(); + $is_retweet = false; + + if (isset($tweet['retweeted_status'])) { + $tweet = $tweet['retweeted_status']; + $is_retweet = true; + } + + if (!isset($tweet['entities'])) { + return $tweet['text']; + } + + // prepare the entities + foreach ($tweet['entities'] as $type => $things) { + foreach ($things as $entity => $value) { + $tweet_link = "{$tweet['created_at']}"; + + switch ($type) { + case 'hashtags': + $href = "#{$value['text']}"; + break; + case 'user_mentions': + $href = "@{$value['screen_name']}"; + break; + case 'urls': + $url = empty($value['expanded_url']) ? $value['url'] : $value['expanded_url']; + $display = isset($value['display_url']) ? $value['display_url'] : str_replace('http://', '', $url); + // Not all pages are served in UTF-8 so you may need to do this ... + $display = urldecode(str_replace('%E2%80%A6', '…', urlencode($display))); + $href = "{$display}"; + break; + } + $keys[$value['indices']['0']] = substr( + $tweet['text'], + $value['indices']['0'], + $value['indices']['1'] - $value['indices']['0'] + ); + $replacements[$value['indices']['0']] = $href; + } + } + + ksort($replacements); + $replacements = array_reverse($replacements, true); + $entified_tweet = $tweet['text']; + foreach ($replacements as $k => $v) { + $entified_tweet = substr_replace($entified_tweet, $v, $k, strlen($keys[$k])); + } + return $entified_tweet; + } +} + +?> \ No newline at end of file diff --git a/lib/twitteroauth/LICENSE b/lib/twitteroauth/LICENSE deleted file mode 100644 index 233854f1..00000000 --- a/lib/twitteroauth/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2009 Abraham Williams - http://abrah.am - abraham@poseurte.ch - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/twitteroauth/OAuth.php b/lib/twitteroauth/OAuth.php deleted file mode 100644 index 67a94c47..00000000 --- a/lib/twitteroauth/OAuth.php +++ /dev/null @@ -1,874 +0,0 @@ -key = $key; - $this->secret = $secret; - $this->callback_url = $callback_url; - } - - function __toString() { - return "OAuthConsumer[key=$this->key,secret=$this->secret]"; - } -} - -class OAuthToken { - // access tokens and request tokens - public $key; - public $secret; - - /** - * key = the token - * secret = the token secret - */ - function __construct($key, $secret) { - $this->key = $key; - $this->secret = $secret; - } - - /** - * generates the basic string serialization of a token that a server - * would respond to request_token and access_token calls with - */ - function to_string() { - return "oauth_token=" . - OAuthUtil::urlencode_rfc3986($this->key) . - "&oauth_token_secret=" . - OAuthUtil::urlencode_rfc3986($this->secret); - } - - function __toString() { - return $this->to_string(); - } -} - -/** - * A class for implementing a Signature Method - * See section 9 ("Signing Requests") in the spec - */ -abstract class OAuthSignatureMethod { - /** - * Needs to return the name of the Signature Method (ie HMAC-SHA1) - * @return string - */ - abstract public function get_name(); - - /** - * Build up the signature - * NOTE: The output of this function MUST NOT be urlencoded. - * the encoding is handled in OAuthRequest when the final - * request is serialized - * @param OAuthRequest $request - * @param OAuthConsumer $consumer - * @param OAuthToken $token - * @return string - */ - abstract public function build_signature($request, $consumer, $token); - - /** - * Verifies that a given signature is correct - * @param OAuthRequest $request - * @param OAuthConsumer $consumer - * @param OAuthToken $token - * @param string $signature - * @return bool - */ - public function check_signature($request, $consumer, $token, $signature) { - $built = $this->build_signature($request, $consumer, $token); - return $built == $signature; - } -} - -/** - * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] - * where the Signature Base String is the text and the key is the concatenated values (each first - * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' - * character (ASCII code 38) even if empty. - * - Chapter 9.2 ("HMAC-SHA1") - */ -class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { - function get_name() { - return "HMAC-SHA1"; - } - - public function build_signature($request, $consumer, $token) { - $base_string = $request->get_signature_base_string(); - $request->base_string = $base_string; - - $key_parts = array( - $consumer->secret, - ($token) ? $token->secret : "" - ); - - $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); - $key = implode('&', $key_parts); - - return base64_encode(hash_hmac('sha1', $base_string, $key, true)); - } -} - -/** - * The PLAINTEXT method does not provide any security protection and SHOULD only be used - * over a secure channel such as HTTPS. It does not use the Signature Base String. - * - Chapter 9.4 ("PLAINTEXT") - */ -class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { - public function get_name() { - return "PLAINTEXT"; - } - - /** - * oauth_signature is set to the concatenated encoded values of the Consumer Secret and - * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is - * empty. The result MUST be encoded again. - * - Chapter 9.4.1 ("Generating Signatures") - * - * Please note that the second encoding MUST NOT happen in the SignatureMethod, as - * OAuthRequest handles this! - */ - public function build_signature($request, $consumer, $token) { - $key_parts = array( - $consumer->secret, - ($token) ? $token->secret : "" - ); - - $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); - $key = implode('&', $key_parts); - $request->base_string = $key; - - return $key; - } -} - -/** - * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in - * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for - * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a - * verified way to the Service Provider, in a manner which is beyond the scope of this - * specification. - * - Chapter 9.3 ("RSA-SHA1") - */ -abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { - public function get_name() { - return "RSA-SHA1"; - } - - // Up to the SP to implement this lookup of keys. Possible ideas are: - // (1) do a lookup in a table of trusted certs keyed off of consumer - // (2) fetch via http using a url provided by the requester - // (3) some sort of specific discovery code based on request - // - // Either way should return a string representation of the certificate - protected abstract function fetch_public_cert(&$request); - - // Up to the SP to implement this lookup of keys. Possible ideas are: - // (1) do a lookup in a table of trusted certs keyed off of consumer - // - // Either way should return a string representation of the certificate - protected abstract function fetch_private_cert(&$request); - - public function build_signature($request, $consumer, $token) { - $base_string = $request->get_signature_base_string(); - $request->base_string = $base_string; - - // Fetch the private key cert based on the request - $cert = $this->fetch_private_cert($request); - - // Pull the private key ID from the certificate - $privatekeyid = openssl_get_privatekey($cert); - - // Sign using the key - $ok = openssl_sign($base_string, $signature, $privatekeyid); - - // Release the key resource - openssl_free_key($privatekeyid); - - return base64_encode($signature); - } - - public function check_signature($request, $consumer, $token, $signature) { - $decoded_sig = base64_decode($signature); - - $base_string = $request->get_signature_base_string(); - - // Fetch the public key cert based on the request - $cert = $this->fetch_public_cert($request); - - // Pull the public key ID from the certificate - $publickeyid = openssl_get_publickey($cert); - - // Check the computed signature against the one passed in the query - $ok = openssl_verify($base_string, $decoded_sig, $publickeyid); - - // Release the key resource - openssl_free_key($publickeyid); - - return $ok == 1; - } -} - -class OAuthRequest { - private $parameters; - private $http_method; - private $http_url; - // for debug purposes - public $base_string; - public static $version = '1.0'; - public static $POST_INPUT = 'php://input'; - - function __construct($http_method, $http_url, $parameters=NULL) { - @$parameters or $parameters = array(); - $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); - $this->parameters = $parameters; - $this->http_method = $http_method; - $this->http_url = $http_url; - } - - - /** - * attempt to build up a request from what was passed to the server - */ - public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) { - $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") - ? 'http' - : 'https'; - @$http_url or $http_url = $scheme . - '://' . $_SERVER['HTTP_HOST'] . - ':' . - $_SERVER['SERVER_PORT'] . - $_SERVER['REQUEST_URI']; - @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; - - // We weren't handed any parameters, so let's find the ones relevant to - // this request. - // If you run XML-RPC or similar you should use this to provide your own - // parsed parameter-list - if (!$parameters) { - // Find request headers - $request_headers = OAuthUtil::get_headers(); - - // Parse the query-string to find GET parameters - $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); - - // It's a POST request of the proper content-type, so parse POST - // parameters and add those overriding any duplicates from GET - if ($http_method == "POST" - && @strstr($request_headers["Content-Type"], - "application/x-www-form-urlencoded") - ) { - $post_data = OAuthUtil::parse_parameters( - file_get_contents(self::$POST_INPUT) - ); - $parameters = array_merge($parameters, $post_data); - } - - // We have a Authorization-header with OAuth data. Parse the header - // and add those overriding any duplicates from GET or POST - if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { - $header_parameters = OAuthUtil::split_header( - $request_headers['Authorization'] - ); - $parameters = array_merge($parameters, $header_parameters); - } - - } - - return new OAuthRequest($http_method, $http_url, $parameters); - } - - /** - * pretty much a helper function to set up the request - */ - public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) { - @$parameters or $parameters = array(); - $defaults = array("oauth_version" => OAuthRequest::$version, - "oauth_nonce" => OAuthRequest::generate_nonce(), - "oauth_timestamp" => OAuthRequest::generate_timestamp(), - "oauth_consumer_key" => $consumer->key); - if ($token) - $defaults['oauth_token'] = $token->key; - - $parameters = array_merge($defaults, $parameters); - - return new OAuthRequest($http_method, $http_url, $parameters); - } - - public function set_parameter($name, $value, $allow_duplicates = true) { - if ($allow_duplicates && isset($this->parameters[$name])) { - // We have already added parameter(s) with this name, so add to the list - if (is_scalar($this->parameters[$name])) { - // This is the first duplicate, so transform scalar (string) - // into an array so we can add the duplicates - $this->parameters[$name] = array($this->parameters[$name]); - } - - $this->parameters[$name][] = $value; - } else { - $this->parameters[$name] = $value; - } - } - - public function get_parameter($name) { - return isset($this->parameters[$name]) ? $this->parameters[$name] : null; - } - - public function get_parameters() { - return $this->parameters; - } - - public function unset_parameter($name) { - unset($this->parameters[$name]); - } - - /** - * The request parameters, sorted and concatenated into a normalized string. - * @return string - */ - public function get_signable_parameters() { - // Grab all parameters - $params = $this->parameters; - - // Remove oauth_signature if present - // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") - if (isset($params['oauth_signature'])) { - unset($params['oauth_signature']); - } - - return OAuthUtil::build_http_query($params); - } - - /** - * Returns the base string of this request - * - * The base string defined as the method, the url - * and the parameters (normalized), each urlencoded - * and the concated with &. - */ - public function get_signature_base_string() { - $parts = array( - $this->get_normalized_http_method(), - $this->get_normalized_http_url(), - $this->get_signable_parameters() - ); - - $parts = OAuthUtil::urlencode_rfc3986($parts); - - return implode('&', $parts); - } - - /** - * just uppercases the http method - */ - public function get_normalized_http_method() { - return strtoupper($this->http_method); - } - - /** - * parses the url and rebuilds it to be - * scheme://host/path - */ - public function get_normalized_http_url() { - $parts = parse_url($this->http_url); - - $port = @$parts['port']; - $scheme = $parts['scheme']; - $host = $parts['host']; - $path = @$parts['path']; - - $port or $port = ($scheme == 'https') ? '443' : '80'; - - if (($scheme == 'https' && $port != '443') - || ($scheme == 'http' && $port != '80')) { - $host = "$host:$port"; - } - return "$scheme://$host$path"; - } - - /** - * builds a url usable for a GET request - */ - public function to_url() { - $post_data = $this->to_postdata(); - $out = $this->get_normalized_http_url(); - if ($post_data) { - $out .= '?'.$post_data; - } - return $out; - } - - /** - * builds the data one would send in a POST request - */ - public function to_postdata() { - return OAuthUtil::build_http_query($this->parameters); - } - - /** - * builds the Authorization: header - */ - public function to_header($realm=null) { - $first = true; - if($realm) { - $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; - $first = false; - } else - $out = 'Authorization: OAuth'; - - $total = array(); - foreach ($this->parameters as $k => $v) { - if (substr($k, 0, 5) != "oauth") continue; - if (is_array($v)) { - throw new OAuthException('Arrays not supported in headers'); - } - $out .= ($first) ? ' ' : ','; - $out .= OAuthUtil::urlencode_rfc3986($k) . - '="' . - OAuthUtil::urlencode_rfc3986($v) . - '"'; - $first = false; - } - return $out; - } - - public function __toString() { - return $this->to_url(); - } - - - public function sign_request($signature_method, $consumer, $token) { - $this->set_parameter( - "oauth_signature_method", - $signature_method->get_name(), - false - ); - $signature = $this->build_signature($signature_method, $consumer, $token); - $this->set_parameter("oauth_signature", $signature, false); - } - - public function build_signature($signature_method, $consumer, $token) { - $signature = $signature_method->build_signature($this, $consumer, $token); - return $signature; - } - - /** - * util function: current timestamp - */ - private static function generate_timestamp() { - return time(); - } - - /** - * util function: current nonce - */ - private static function generate_nonce() { - $mt = microtime(); - $rand = mt_rand(); - - return md5($mt . $rand); // md5s look nicer than numbers - } -} - -class OAuthServer { - protected $timestamp_threshold = 300; // in seconds, five minutes - protected $version = '1.0'; // hi blaine - protected $signature_methods = array(); - - protected $data_store; - - function __construct($data_store) { - $this->data_store = $data_store; - } - - public function add_signature_method($signature_method) { - $this->signature_methods[$signature_method->get_name()] = - $signature_method; - } - - // high level functions - - /** - * process a request_token request - * returns the request token on success - */ - public function fetch_request_token(&$request) { - $this->get_version($request); - - $consumer = $this->get_consumer($request); - - // no token required for the initial token request - $token = NULL; - - $this->check_signature($request, $consumer, $token); - - // Rev A change - $callback = $request->get_parameter('oauth_callback'); - $new_token = $this->data_store->new_request_token($consumer, $callback); - - return $new_token; - } - - /** - * process an access_token request - * returns the access token on success - */ - public function fetch_access_token(&$request) { - $this->get_version($request); - - $consumer = $this->get_consumer($request); - - // requires authorized request token - $token = $this->get_token($request, $consumer, "request"); - - $this->check_signature($request, $consumer, $token); - - // Rev A change - $verifier = $request->get_parameter('oauth_verifier'); - $new_token = $this->data_store->new_access_token($token, $consumer, $verifier); - - return $new_token; - } - - /** - * verify an api call, checks all the parameters - */ - public function verify_request(&$request) { - $this->get_version($request); - $consumer = $this->get_consumer($request); - $token = $this->get_token($request, $consumer, "access"); - $this->check_signature($request, $consumer, $token); - return array($consumer, $token); - } - - // Internals from here - /** - * version 1 - */ - private function get_version(&$request) { - $version = $request->get_parameter("oauth_version"); - if (!$version) { - // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. - // Chapter 7.0 ("Accessing Protected Ressources") - $version = '1.0'; - } - if ($version !== $this->version) { - throw new OAuthException("OAuth version '$version' not supported"); - } - return $version; - } - - /** - * figure out the signature with some defaults - */ - private function get_signature_method(&$request) { - $signature_method = - @$request->get_parameter("oauth_signature_method"); - - if (!$signature_method) { - // According to chapter 7 ("Accessing Protected Ressources") the signature-method - // parameter is required, and we can't just fallback to PLAINTEXT - throw new OAuthException('No signature method parameter. This parameter is required'); - } - - if (!in_array($signature_method, - array_keys($this->signature_methods))) { - throw new OAuthException( - "Signature method '$signature_method' not supported " . - "try one of the following: " . - implode(", ", array_keys($this->signature_methods)) - ); - } - return $this->signature_methods[$signature_method]; - } - - /** - * try to find the consumer for the provided request's consumer key - */ - private function get_consumer(&$request) { - $consumer_key = @$request->get_parameter("oauth_consumer_key"); - if (!$consumer_key) { - throw new OAuthException("Invalid consumer key"); - } - - $consumer = $this->data_store->lookup_consumer($consumer_key); - if (!$consumer) { - throw new OAuthException("Invalid consumer"); - } - - return $consumer; - } - - /** - * try to find the token for the provided request's token key - */ - private function get_token(&$request, $consumer, $token_type="access") { - $token_field = @$request->get_parameter('oauth_token'); - $token = $this->data_store->lookup_token( - $consumer, $token_type, $token_field - ); - if (!$token) { - throw new OAuthException("Invalid $token_type token: $token_field"); - } - return $token; - } - - /** - * all-in-one function to check the signature on a request - * should guess the signature method appropriately - */ - private function check_signature(&$request, $consumer, $token) { - // this should probably be in a different method - $timestamp = @$request->get_parameter('oauth_timestamp'); - $nonce = @$request->get_parameter('oauth_nonce'); - - $this->check_timestamp($timestamp); - $this->check_nonce($consumer, $token, $nonce, $timestamp); - - $signature_method = $this->get_signature_method($request); - - $signature = $request->get_parameter('oauth_signature'); - $valid_sig = $signature_method->check_signature( - $request, - $consumer, - $token, - $signature - ); - - if (!$valid_sig) { - throw new OAuthException("Invalid signature"); - } - } - - /** - * check that the timestamp is new enough - */ - private function check_timestamp($timestamp) { - if( ! $timestamp ) - throw new OAuthException( - 'Missing timestamp parameter. The parameter is required' - ); - - // verify that timestamp is recentish - $now = time(); - if (abs($now - $timestamp) > $this->timestamp_threshold) { - throw new OAuthException( - "Expired timestamp, yours $timestamp, ours $now" - ); - } - } - - /** - * check that the nonce is not repeated - */ - private function check_nonce($consumer, $token, $nonce, $timestamp) { - if( ! $nonce ) - throw new OAuthException( - 'Missing nonce parameter. The parameter is required' - ); - - // verify that the nonce is uniqueish - $found = $this->data_store->lookup_nonce( - $consumer, - $token, - $nonce, - $timestamp - ); - if ($found) { - throw new OAuthException("Nonce already used: $nonce"); - } - } - -} - -class OAuthDataStore { - function lookup_consumer($consumer_key) { - // implement me - } - - function lookup_token($consumer, $token_type, $token) { - // implement me - } - - function lookup_nonce($consumer, $token, $nonce, $timestamp) { - // implement me - } - - function new_request_token($consumer, $callback = null) { - // return a new token attached to this consumer - } - - function new_access_token($token, $consumer, $verifier = null) { - // return a new access token attached to this consumer - // for the user associated with this token if the request token - // is authorized - // should also invalidate the request token - } - -} - -class OAuthUtil { - public static function urlencode_rfc3986($input) { - if (is_array($input)) { - return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input); - } else if (is_scalar($input)) { - return str_replace( - '+', - ' ', - str_replace('%7E', '~', rawurlencode($input)) - ); - } else { - return ''; - } -} - - - // This decode function isn't taking into consideration the above - // modifications to the encoding process. However, this method doesn't - // seem to be used anywhere so leaving it as is. - public static function urldecode_rfc3986($string) { - return urldecode($string); - } - - // Utility function for turning the Authorization: header into - // parameters, has to do some unescaping - // Can filter out any non-oauth parameters if needed (default behaviour) - public static function split_header($header, $only_allow_oauth_parameters = true) { - $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/'; - $offset = 0; - $params = array(); - while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) { - $match = $matches[0]; - $header_name = $matches[2][0]; - $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0]; - if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) { - $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content); - } - $offset = $match[1] + strlen($match[0]); - } - - if (isset($params['realm'])) { - unset($params['realm']); - } - - return $params; - } - - // helper to try to sort out headers for people who aren't running apache - public static function get_headers() { - if (function_exists('apache_request_headers')) { - // we need this to get the actual Authorization: header - // because apache tends to tell us it doesn't exist - $headers = apache_request_headers(); - - // sanitize the output of apache_request_headers because - // we always want the keys to be Cased-Like-This and arh() - // returns the headers in the same case as they are in the - // request - $out = array(); - foreach( $headers AS $key => $value ) { - $key = str_replace( - " ", - "-", - ucwords(strtolower(str_replace("-", " ", $key))) - ); - $out[$key] = $value; - } - } else { - // otherwise we don't have apache and are just going to have to hope - // that $_SERVER actually contains what we need - $out = array(); - if( isset($_SERVER['CONTENT_TYPE']) ) - $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; - if( isset($_ENV['CONTENT_TYPE']) ) - $out['Content-Type'] = $_ENV['CONTENT_TYPE']; - - foreach ($_SERVER as $key => $value) { - if (substr($key, 0, 5) == "HTTP_") { - // this is chaos, basically it is just there to capitalize the first - // letter of every word that is not an initial HTTP and strip HTTP - // code from przemek - $key = str_replace( - " ", - "-", - ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) - ); - $out[$key] = $value; - } - } - } - return $out; - } - - // This function takes a input like a=b&a=c&d=e and returns the parsed - // parameters like this - // array('a' => array('b','c'), 'd' => 'e') - public static function parse_parameters( $input ) { - if (!isset($input) || !$input) return array(); - - $pairs = explode('&', $input); - - $parsed_parameters = array(); - foreach ($pairs as $pair) { - $split = explode('=', $pair, 2); - $parameter = OAuthUtil::urldecode_rfc3986($split[0]); - $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; - - if (isset($parsed_parameters[$parameter])) { - // We have already recieved parameter(s) with this name, so add to the list - // of parameters with this name - - if (is_scalar($parsed_parameters[$parameter])) { - // This is the first duplicate, so transform scalar (string) into an array - // so we can add the duplicates - $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]); - } - - $parsed_parameters[$parameter][] = $value; - } else { - $parsed_parameters[$parameter] = $value; - } - } - return $parsed_parameters; - } - - public static function build_http_query($params) { - if (!$params) return ''; - - // Urlencode both keys and values - $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); - $values = OAuthUtil::urlencode_rfc3986(array_values($params)); - $params = array_combine($keys, $values); - - // Parameters are sorted by name, using lexicographical byte value ordering. - // Ref: Spec: 9.1.1 (1) - uksort($params, 'strcmp'); - - $pairs = array(); - foreach ($params as $parameter => $value) { - if (is_array($value)) { - // If two or more parameters share the same name, they are sorted by their value - // Ref: Spec: 9.1.1 (1) - natsort($value); - foreach ($value as $duplicate_value) { - $pairs[] = $parameter . '=' . $duplicate_value; - } - } else { - $pairs[] = $parameter . '=' . $value; - } - } - // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) - // Each name-value pair is separated by an '&' character (ASCII code 38) - return implode('&', $pairs); - } -} - -?> diff --git a/lib/twitteroauth/twitteroauth.php b/lib/twitteroauth/twitteroauth.php deleted file mode 100644 index 674308ae..00000000 --- a/lib/twitteroauth/twitteroauth.php +++ /dev/null @@ -1,245 +0,0 @@ -http_status; } - function lastAPICall() { return $this->last_api_call; } - - /** - * construct TwitterOAuth object - */ - function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) { - $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1(); - $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret); - if (!empty($oauth_token) && !empty($oauth_token_secret)) { - $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret); - } else { - $this->token = NULL; - } - } - - - /** - * Get a request_token from Twitter - * - * @returns a key/value array containing oauth_token and oauth_token_secret - */ - function getRequestToken($oauth_callback = NULL) { - $parameters = array(); - if (!empty($oauth_callback)) { - $parameters['oauth_callback'] = $oauth_callback; - } - $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters); - $token = OAuthUtil::parse_parameters($request); - $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); - return $token; - } - - /** - * Get the authorize URL - * - * @returns a string - */ - function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) { - if (is_array($token)) { - $token = $token['oauth_token']; - } - if (empty($sign_in_with_twitter)) { - return $this->authorizeURL() . "?oauth_token={$token}"; - } else { - return $this->authenticateURL() . "?oauth_token={$token}"; - } - } - - /** - * Exchange request token and secret for an access token and - * secret, to sign API calls. - * - * @returns array("oauth_token" => "the-access-token", - * "oauth_token_secret" => "the-access-secret", - * "user_id" => "9436992", - * "screen_name" => "abraham") - */ - function getAccessToken($oauth_verifier = FALSE) { - $parameters = array(); - if (!empty($oauth_verifier)) { - $parameters['oauth_verifier'] = $oauth_verifier; - } - $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters); - $token = OAuthUtil::parse_parameters($request); - $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); - return $token; - } - - /** - * One time exchange of username and password for access token and secret. - * - * @returns array("oauth_token" => "the-access-token", - * "oauth_token_secret" => "the-access-secret", - * "user_id" => "9436992", - * "screen_name" => "abraham", - * "x_auth_expires" => "0") - */ - function getXAuthToken($username, $password) { - $parameters = array(); - $parameters['x_auth_username'] = $username; - $parameters['x_auth_password'] = $password; - $parameters['x_auth_mode'] = 'client_auth'; - $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters); - $token = OAuthUtil::parse_parameters($request); - $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); - return $token; - } - - /** - * GET wrapper for oAuthRequest. - */ - function get($url, $parameters = array()) { - $response = $this->oAuthRequest($url, 'GET', $parameters); - if ($this->format === 'json' && $this->decode_json) { - return json_decode($response); - } - return $response; - } - - /** - * POST wrapper for oAuthRequest. - */ - function post($url, $parameters = array()) { - $response = $this->oAuthRequest($url, 'POST', $parameters); - if ($this->format === 'json' && $this->decode_json) { - return json_decode($response); - } - return $response; - } - - /** - * DELETE wrapper for oAuthReqeust. - */ - function delete($url, $parameters = array()) { - $response = $this->oAuthRequest($url, 'DELETE', $parameters); - if ($this->format === 'json' && $this->decode_json) { - return json_decode($response); - } - return $response; - } - - /** - * Format and sign an OAuth / API request - */ - function oAuthRequest($url, $method, $parameters) { - if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) { - $url = "{$this->host}{$url}.{$this->format}"; - } - $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters); - $request->sign_request($this->sha1_method, $this->consumer, $this->token); - switch ($method) { - case 'GET': - return $this->http($request->to_url(), 'GET'); - default: - return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata()); - } - } - - /** - * Make an HTTP request - * - * @return API results - */ - function http($url, $method, $postfields = NULL) { - $this->http_info = array(); - $ci = curl_init(); - /* Curl settings */ - curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent); - curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout); - curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout); - curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE); - curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:')); - curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); - curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); - curl_setopt($ci, CURLOPT_HEADER, FALSE); - - switch ($method) { - case 'POST': - curl_setopt($ci, CURLOPT_POST, TRUE); - if (!empty($postfields)) { - curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields); - } - break; - case 'DELETE': - curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE'); - if (!empty($postfields)) { - $url = "{$url}?{$postfields}"; - } - } - - curl_setopt($ci, CURLOPT_URL, $url); - $response = curl_exec($ci); - $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE); - $this->http_info = array_merge($this->http_info, curl_getinfo($ci)); - $this->url = $url; - curl_close ($ci); - return $response; - } - - /** - * Get the header info to store. - */ - function getHeader($ch, $header) { - $i = strpos($header, ':'); - if (!empty($i)) { - $key = str_replace('-', '_', strtolower(substr($header, 0, $i))); - $value = trim(substr($header, $i + 2)); - $this->http_header[$key] = $value; - } - return strlen($header); - } -} diff --git a/twitter.php b/twitter.php index 869cb08a..934b72cc 100644 --- a/twitter.php +++ b/twitter.php @@ -4,8 +4,9 @@ require_once "sanity_check.php"; require_once "config.php"; require_once "db.php"; - require_once "lib/twitteroauth/twitteroauth.php"; - + //require_once "lib/twitteroauth/twitteroauth.php"; + require_once "lib/tmhoauth/tmhOAuth.php"; + $link = db_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME); init_connection($link); @@ -21,35 +22,33 @@ $callback_url = get_self_url_prefix() . "/twitter.php?op=callback"; + $tmhOAuth = new tmhOAuth(array( + 'consumer_key' => CONSUMER_KEY, + 'consumer_secret' => CONSUMER_SECRET, + )); + if ($op == 'clear') { - /* Remove no longer needed request tokens */ - unset($_SESSION['oauth_token']); - unset($_SESSION['oauth_token_secret']); - unset($_SESSION['access_token']); + unset($_SESSION['oauth']); header("Location: twitter.php"); return; } - if ($op == 'callback') { - /* If the oauth_token is old redirect to the connect page. */ - if (isset($_REQUEST['oauth_token']) && - $_SESSION['oauth_token'] !== $_REQUEST['oauth_token']) { + if (isset($_REQUEST['oauth_verifier'])) { - $_SESSION['oauth_status'] = 'oldtoken'; - header('Location: twitter.php?op=clear'); - return; - } + $op = 'callback'; - /* Create TwitteroAuth object with app key/secret and token key/secret from default phase */ - $connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']); + $tmhOAuth->config['user_token'] = $_SESSION['oauth']['oauth_token']; + $tmhOAuth->config['user_secret'] = $_SESSION['oauth']['oauth_token_secret']; - /* Request access tokens from twitter */ - $access_token = $connection->getAccessToken($_REQUEST['oauth_verifier']); + $code = $tmhOAuth->request('POST', $tmhOAuth->url('oauth/access_token', ''), array( + 'oauth_verifier' => $_REQUEST['oauth_verifier'])); + + if ($code == 200) { - /* If HTTP response is 200 continue otherwise send to connect page to retry */ - if ($connection->http_code == 200) { - $access_token = db_escape_string(json_encode($access_token)); + $access_token = json_encode($tmhOAuth->extract_params($tmhOAuth->response['response'])); + + unset($_SESSION['oauth']); db_query($link, "UPDATE ttrss_users SET twitter_oauth = '$access_token' WHERE id = ".$_SESSION['uid']); @@ -63,20 +62,23 @@ if ($op == 'register') { - /* Build TwitterOAuth object with client credentials. */ - $connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET); + $code = $tmhOAuth->request('POST', + $tmhOAuth->url('oauth/request_token', ''), array( + 'oauth_callback' => $callback)); - /* Get temporary credentials. */ - $request_token = $connection->getRequestToken($callback_url); - - /* Save temporary credentials to session. */ - $_SESSION['oauth_token'] = $token = $request_token['oauth_token']; - $_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret']; + if ($code == 200) { + $_SESSION['oauth'] = $tmhOAuth->extract_params($tmhOAuth->response['response']); + + $method = isset($_REQUEST['signin']) ? 'authenticate' : 'authorize'; + $force = isset($_REQUEST['force']) ? '&force_login=1' : ''; + $forcewrite = isset($_REQUEST['force_write']) ? '&oauth_access_type=write' : ''; + $forceread = isset($_REQUEST['force_read']) ? '&oauth_access_type=read' : ''; + + $location = $tmhOAuth->url("oauth/{$method}", '') . + "?oauth_token={$_SESSION['oauth']['oauth_token']}{$force}{$forcewrite}{$forceread}"; + + header("Location: $location"); - if ($connection->http_code == 200) { - $url = $connection->getAuthorizeURL($token); - header('Location: ' . $url); - return; } } ?>