r107654 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r107653‎ | r107654 | r107655 >
Date:21:05, 30 December 2011
Author:reedy
Status:reverted
Tags:
Comment:
Modified paths:
  • /trunk/extensions/TweetANew/lib/tmhOAuth.php (modified) (history)

Diff [purge]

Index: trunk/extensions/TweetANew/lib/tmhOAuth.php
@@ -7,604 +7,649 @@
88 * REST requests. OAuth authentication is sent using the an Authorization Header.
99 *
1010 * @author themattharris
11 - * @version 0.57
 11+ * @version 0.60
1212 *
13 - * 11 December 2011
 13+ * 29 December 2011
1414 */
1515 class tmhOAuth {
16 - const VERSION = 0.57;
 16+ const VERSION = 0.60;
1717
18 - /**
19 - * Creates a new tmhOAuth object
20 - *
21 - * @param string $config, the configuration to use for this request
22 - */
23 - function __construct($config) {
24 - $this->params = array();
25 - $this->headers = array();
26 - $this->auto_fixed_time = false;
27 - $this->buffer = null;
 18+ /**
 19+ * Creates a new tmhOAuth object
 20+ *
 21+ * @param string $config, the configuration to use for this request
 22+ */
 23+ function __construct($config) {
 24+ $this->params = array();
 25+ $this->headers = array();
 26+ $this->auto_fixed_time = false;
 27+ $this->buffer = null;
2828
29 - // default configuration options
30 - $this->config = array_merge(
31 - array(
32 - 'user_agent' => 'tmhOAuth ' . self::VERSION . ' - //github.com/themattharris/tmhOAuth',
33 - 'consumer_key' => '',
34 - 'consumer_secret' => '',
35 - 'user_token' => '',
36 - 'user_secret' => '',
37 - 'use_ssl' => true,
38 - 'host' => 'api.twitter.com',
39 - 'debug' => false,
40 - 'force_nonce' => false,
41 - 'nonce' => false, // used for checking signatures. leave as false for auto
42 - 'force_timestamp' => false,
43 - 'timestamp' => false, // used for checking signatures. leave as false for auto
44 - 'oauth_version' => '1.0',
 29+ // default configuration options
 30+ $this->config = array_merge(
 31+ array(
 32+ // leave 'user_agent' blank for default, otherwise set this to
 33+ // something that clearly identifies your app
 34+ 'user_agent' => '',
4535
46 - // you probably don't want to change any of these curl values
47 - 'curl_connecttimeout' => 30,
48 - 'curl_timeout' => 10,
49 - // for security you may want to set this to TRUE. If you do you need
50 - // to install the servers certificate in your local certificate store.
51 - 'curl_ssl_verifypeer' => false,
52 - 'curl_followlocation' => false, // whether to follow redirects or not
53 - // support for proxy servers
54 - 'curl_proxy' => false, // really you don't want to use this if you are using streaming
55 - 'curl_proxyuserpwd' => false, // format username:password for proxy, if required
56 - 'curl_encoding' => '', // leave blank for all supported formats, else use gzip, deflate, identity
 36+ 'use_ssl' => true,
 37+ 'host' => 'api.twitter.com',
5738
58 - // streaming API
59 - 'is_streaming' => false,
60 - 'streaming_eol' => "\r\n",
61 - 'streaming_metrics_interval' => 60,
 39+ 'consumer_key' => '',
 40+ 'consumer_secret' => '',
 41+ 'user_token' => '',
 42+ 'user_secret' => '',
 43+ 'force_nonce' => false,
 44+ 'nonce' => false, // used for checking signatures. leave as false for auto
 45+ 'force_timestamp' => false,
 46+ 'timestamp' => false, // used for checking signatures. leave as false for auto
6247
63 - // header or querystring. You should always use header
64 - // this is just to help me debug other developers
65 - // implementations
66 - 'as_header' => true,
67 - ),
68 - $config
69 - );
70 - }
 48+ // oauth signing variables that are not dynamic
 49+ 'oauth_version' => '1.0',
 50+ 'oauth_signature_method' => 'HMAC-SHA1',
7151
72 - /**
73 - * Generates a random OAuth nonce.
74 - * If 'force_nonce' is true a nonce is not generated and the value in the configuration will be retained.
75 - *
76 - * @param string $length how many characters the nonce should be before MD5 hashing. default 12
77 - * @param string $include_time whether to include time at the beginning of the nonce. default true
78 - * @return void
79 - */
80 - private function create_nonce($length=12, $include_time=true) {
81 - if ($this->config['force_nonce'] == false) {
82 - $sequence = array_merge(range(0,9), range('A','Z'), range('a','z'));
83 - $length = $length > count($sequence) ? count($sequence) : $length;
84 - shuffle($sequence);
 52+ // you probably don't want to change any of these curl values
 53+ 'curl_connecttimeout' => 30,
 54+ 'curl_timeout' => 10,
8555
86 - $prefix = $include_time ? microtime() : '';
87 - $this->config['nonce'] = md5(substr($prefix . implode($sequence), 0, $length));
88 - }
89 - }
 56+ // for security these should always be set to true.
 57+ 'curl_ssl_verifyhost' => true,
 58+ 'curl_ssl_verifypeer' => true,
9059
91 - /**
92 - * Generates a timestamp.
93 - * If 'force_timestamp' is true a nonce is not generated and the value in the configuration will be retained.
94 - *
95 - * @return void
96 - */
97 - private function create_timestamp() {
98 - $this->config['timestamp'] = ($this->config['force_timestamp'] == false ? time() : $this->config['timestamp']);
99 - }
 60+ // you can get the latest cacert.pem from here http://curl.haxx.se/ca/cacert.pem
 61+ 'curl_cainfo' => dirname(__FILE__) . '/cacert.pem',
 62+ 'curl_capath' => dirname(__FILE__),
10063
101 - /**
102 - * Encodes the string or array passed in a way compatible with OAuth.
103 - * If an array is passed each array value will will be encoded.
104 - *
105 - * @param mixed $data the scalar or array to encode
106 - * @return $data encoded in a way compatible with OAuth
107 - */
108 - private function safe_encode($data) {
109 - if (is_array($data)) {
110 - return array_map(array($this, 'safe_encode'), $data);
111 - } else if (is_scalar($data)) {
112 - return str_ireplace(
113 - array('+', '%7E'),
114 - array(' ', '~'),
115 - rawurlencode($data)
116 - );
117 - } else {
118 - return '';
119 - }
120 - }
 64+ 'curl_followlocation' => false, // whether to follow redirects or not
12165
122 - /**
123 - * Decodes the string or array from it's URL encoded form
124 - * If an array is passed each array value will will be decoded.
125 - *
126 - * @param mixed $data the scalar or array to decode
127 - * @return $data decoded from the URL encoded form
128 - */
129 - private function safe_decode($data) {
130 - if (is_array($data)) {
131 - return array_map(array($this, 'safe_decode'), $data);
132 - } else if (is_scalar($data)) {
133 - return rawurldecode($data);
134 - } else {
135 - return '';
136 - }
137 - }
 66+ // support for proxy servers
 67+ 'curl_proxy' => false, // really you don't want to use this if you are using streaming
 68+ 'curl_proxyuserpwd' => false, // format username:password for proxy, if required
 69+ 'curl_encoding' => '', // leave blank for all supported formats, else use gzip, deflate, identity
13870
139 - /**
140 - * Returns an array of the standard OAuth parameters.
141 - *
142 - * @return array all required OAuth parameters, safely encoded
143 - */
144 - private function get_defaults() {
145 - $defaults = array(
146 - 'oauth_version' => $this->config['oauth_version'],
147 - 'oauth_nonce' => $this->config['nonce'],
148 - 'oauth_timestamp' => $this->config['timestamp'],
149 - 'oauth_consumer_key' => $this->config['consumer_key'],
150 - 'oauth_signature_method' => 'HMAC-SHA1',
151 - );
 71+ // streaming API
 72+ 'is_streaming' => false,
 73+ 'streaming_eol' => "\r\n",
 74+ 'streaming_metrics_interval' => 60,
15275
153 - // include the user token if it exists
154 - if ( $this->config['user_token'] )
155 - $defaults['oauth_token'] = $this->config['user_token'];
 76+ // header or querystring. You should always use header!
 77+ // this is just to help me debug other developers implementations
 78+ 'as_header' => true,
 79+ 'debug' => false,
 80+ ),
 81+ $config
 82+ );
 83+ $this->set_user_agent();
 84+ }
15685
157 - // safely encode
158 - foreach ($defaults as $k => $v) {
159 - $_defaults[$this->safe_encode($k)] = $this->safe_encode($v);
160 - }
 86+ function set_user_agent() {
 87+ if (!empty($this->config['user_agent']))
 88+ return;
16189
162 - return $_defaults;
163 - }
 90+ if ($this->config['curl_ssl_verifyhost'] && $this->config['curl_ssl_verifypeer']) {
 91+ $ssl = '+SSL';
 92+ } else {
 93+ $ssl = '-SSL';
 94+ }
16495
165 - /**
166 - * Extracts and decodes OAuth parameters from the passed string
167 - *
168 - * @param string $body the response body from an OAuth flow method
169 - * @return array the response body safely decoded to an array of key => values
170 - */
171 - function extract_params($body) {
172 - $kvs = explode('&', $body);
173 - $decoded = array();
174 - foreach ($kvs as $kv) {
175 - $kv = explode('=', $kv, 2);
176 - $kv[0] = $this->safe_decode($kv[0]);
177 - $kv[1] = $this->safe_decode($kv[1]);
178 - $decoded[$kv[0]] = $kv[1];
179 - }
180 - return $decoded;
181 - }
 96+ $ua = 'tmhOAuth ' . self::VERSION . $ssl . ' - //github.com/themattharris/tmhOAuth';
 97+ $this->config['user_agent'] = $ua;
 98+ }
18299
183 - /**
184 - * Prepares the HTTP method for use in the base string by converting it to
185 - * uppercase.
186 - *
187 - * @param string $method an HTTP method such as GET or POST
188 - * @return void value is stored to a class variable
189 - * @author themattharris
190 - */
191 - private function prepare_method($method) {
192 - $this->method = strtoupper($method);
193 - }
 100+ /**
 101+ * Generates a random OAuth nonce.
 102+ * If 'force_nonce' is true a nonce is not generated and the value in the configuration will be retained.
 103+ *
 104+ * @param string $length how many characters the nonce should be before MD5 hashing. default 12
 105+ * @param string $include_time whether to include time at the beginning of the nonce. default true
 106+ * @return void
 107+ */
 108+ private function create_nonce($length=12, $include_time=true) {
 109+ if ($this->config['force_nonce'] == false) {
 110+ $sequence = array_merge(range(0,9), range('A','Z'), range('a','z'));
 111+ $length = $length > count($sequence) ? count($sequence) : $length;
 112+ shuffle($sequence);
194113
195 - /**
196 - * Prepares the URL for use in the base string by ripping it apart and
197 - * reconstructing it.
198 - *
199 - * Ref: 3.4.1.2
200 - *
201 - * @param string $url the request URL
202 - * @return void value is stored to a class variable
203 - * @author themattharris
204 - */
205 - private function prepare_url($url) {
206 - $parts = parse_url($url);
 114+ $prefix = $include_time ? microtime() : '';
 115+ $this->config['nonce'] = md5(substr($prefix . implode('', $sequence), 0, $length));
 116+ }
 117+ }
207118
208 - $port = isset($parts['port']) ? $parts['port'] : false;
209 - $scheme = $parts['scheme'];
210 - $host = $parts['host'];
211 - $path = isset($parts['path']) ? $parts['path'] : false;
 119+ /**
 120+ * Generates a timestamp.
 121+ * If 'force_timestamp' is true a nonce is not generated and the value in the configuration will be retained.
 122+ *
 123+ * @return void
 124+ */
 125+ private function create_timestamp() {
 126+ $this->config['timestamp'] = ($this->config['force_timestamp'] == false ? time() : $this->config['timestamp']);
 127+ }
212128
213 - $port or $port = ($scheme == 'https') ? '443' : '80';
 129+ /**
 130+ * Encodes the string or array passed in a way compatible with OAuth.
 131+ * If an array is passed each array value will will be encoded.
 132+ *
 133+ * @param mixed $data the scalar or array to encode
 134+ * @return $data encoded in a way compatible with OAuth
 135+ */
 136+ private function safe_encode($data) {
 137+ if (is_array($data)) {
 138+ return array_map(array($this, 'safe_encode'), $data);
 139+ } else if (is_scalar($data)) {
 140+ return str_ireplace(
 141+ array('+', '%7E'),
 142+ array(' ', '~'),
 143+ rawurlencode($data)
 144+ );
 145+ } else {
 146+ return '';
 147+ }
 148+ }
214149
215 - if (($scheme == 'https' && $port != '443')
216 - || ($scheme == 'http' && $port != '80')) {
217 - $host = "$host:$port";
218 - }
219 - $this->url = strtolower("$scheme://$host$path");
220 - }
 150+ /**
 151+ * Decodes the string or array from it's URL encoded form
 152+ * If an array is passed each array value will will be decoded.
 153+ *
 154+ * @param mixed $data the scalar or array to decode
 155+ * @return $data decoded from the URL encoded form
 156+ */
 157+ private function safe_decode($data) {
 158+ if (is_array($data)) {
 159+ return array_map(array($this, 'safe_decode'), $data);
 160+ } else if (is_scalar($data)) {
 161+ return rawurldecode($data);
 162+ } else {
 163+ return '';
 164+ }
 165+ }
221166
222 - /**
223 - * Prepares all parameters for the base string and request.
224 - * Multipart parameters are ignored as they are not defined in the specification,
225 - * all other types of parameter are encoded for compatibility with OAuth.
226 - *
227 - * @param array $params the parameters for the request
228 - * @return void prepared values are stored in class variables
229 - */
230 - private function prepare_params($params) {
231 - // do not encode multipart parameters, leave them alone
232 - if ($this->config['multipart']) {
233 - $this->request_params = $params;
234 - $params = array();
235 - }
 167+ /**
 168+ * Returns an array of the standard OAuth parameters.
 169+ *
 170+ * @return array all required OAuth parameters, safely encoded
 171+ */
 172+ private function get_defaults() {
 173+ $defaults = array(
 174+ 'oauth_version' => $this->config['oauth_version'],
 175+ 'oauth_nonce' => $this->config['nonce'],
 176+ 'oauth_timestamp' => $this->config['timestamp'],
 177+ 'oauth_consumer_key' => $this->config['consumer_key'],
 178+ 'oauth_signature_method' => $this->config['oauth_signature_method'],
 179+ );
236180
237 - // signing parameters are request parameters + OAuth default parameters
238 - $this->signing_params = array_merge($this->get_defaults(), (array)$params);
 181+ // include the user token if it exists
 182+ if ( $this->config['user_token'] )
 183+ $defaults['oauth_token'] = $this->config['user_token'];
239184
240 - // Remove oauth_signature if present
241 - // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
242 - if (isset($this->signing_params['oauth_signature'])) {
243 - unset($this->signing_params['oauth_signature']);
244 - }
 185+ // safely encode
 186+ foreach ($defaults as $k => $v) {
 187+ $_defaults[$this->safe_encode($k)] = $this->safe_encode($v);
 188+ }
245189
246 - // Parameters are sorted by name, using lexicographical byte value ordering.
247 - // Ref: Spec: 9.1.1 (1)
248 - uksort($this->signing_params, 'strcmp');
 190+ return $_defaults;
 191+ }
249192
250 - // encode. Also sort the signed parameters from the POST parameters
251 - foreach ($this->signing_params as $k => $v) {
252 - $k = $this->safe_encode($k);
253 - $v = $this->safe_encode($v);
254 - $_signing_params[$k] = $v;
255 - $kv[] = "{$k}={$v}";
256 - }
 193+ /**
 194+ * Extracts and decodes OAuth parameters from the passed string
 195+ *
 196+ * @param string $body the response body from an OAuth flow method
 197+ * @return array the response body safely decoded to an array of key => values
 198+ */
 199+ function extract_params($body) {
 200+ $kvs = explode('&', $body);
 201+ $decoded = array();
 202+ foreach ($kvs as $kv) {
 203+ $kv = explode('=', $kv, 2);
 204+ $kv[0] = $this->safe_decode($kv[0]);
 205+ $kv[1] = $this->safe_decode($kv[1]);
 206+ $decoded[$kv[0]] = $kv[1];
 207+ }
 208+ return $decoded;
 209+ }
257210
258 - // auth params = the default oauth params which are present in our collection of signing params
259 - $this->auth_params = array_intersect_key($this->get_defaults(), $_signing_params);
260 - if (isset($_signing_params['oauth_callback'])) {
261 - $this->auth_params['oauth_callback'] = $_signing_params['oauth_callback'];
262 - unset($_signing_params['oauth_callback']);
263 - }
 211+ /**
 212+ * Prepares the HTTP method for use in the base string by converting it to
 213+ * uppercase.
 214+ *
 215+ * @param string $method an HTTP method such as GET or POST
 216+ * @return void value is stored to a class variable
 217+ * @author themattharris
 218+ */
 219+ private function prepare_method($method) {
 220+ $this->method = strtoupper($method);
 221+ }
264222
265 - // request_params is already set if we're doing multipart, if not we need to set them now
266 - if ( ! $this->config['multipart'])
267 - $this->request_params = array_diff_key($_signing_params, $this->get_defaults());
 223+ /**
 224+ * Prepares the URL for use in the base string by ripping it apart and
 225+ * reconstructing it.
 226+ *
 227+ * Ref: 3.4.1.2
 228+ *
 229+ * @param string $url the request URL
 230+ * @return void value is stored to a class variable
 231+ * @author themattharris
 232+ */
 233+ private function prepare_url($url) {
 234+ $parts = parse_url($url);
268235
269 - // create the parameter part of the base string
270 - $this->signing_params = implode('&', $kv);
271 - }
 236+ $port = isset($parts['port']) ? $parts['port'] : false;
 237+ $scheme = $parts['scheme'];
 238+ $host = $parts['host'];
 239+ $path = isset($parts['path']) ? $parts['path'] : false;
272240
273 - /**
274 - * Prepares the OAuth signing key
275 - *
276 - * @return void prepared signing key is stored in a class variables
277 - */
278 - private function prepare_signing_key() {
279 - $this->signing_key = $this->safe_encode($this->config['consumer_secret']) . '&' . $this->safe_encode($this->config['user_secret']);
280 - }
 241+ $port or $port = ($scheme == 'https') ? '443' : '80';
281242
282 - /**
283 - * Prepare the base string.
284 - * Ref: Spec: 9.1.3 ("Concatenate Request Elements")
285 - *
286 - * @return void prepared base string is stored in a class variables
287 - */
288 - private function prepare_base_string() {
289 - $base = array(
290 - $this->method,
291 - $this->url,
292 - $this->signing_params
293 - );
294 - $this->base_string = implode('&', $this->safe_encode($base));
295 - }
 243+ if (($scheme == 'https' && $port != '443')
 244+ || ($scheme == 'http' && $port != '80')) {
 245+ $host = "$host:$port";
 246+ }
 247+ $this->url = strtolower("$scheme://$host$path");
 248+ }
296249
297 - /**
298 - * Prepares the Authorization header
299 - *
300 - * @return void prepared authorization header is stored in a class variables
301 - */
302 - private function prepare_auth_header() {
303 - $this->headers = array();
304 - uksort($this->auth_params, 'strcmp');
305 - if (!$this->config['as_header']) :
306 - $this->request_params = array_merge($this->request_params, $this->auth_params);
307 - return;
308 - endif;
 250+ /**
 251+ * Prepares all parameters for the base string and request.
 252+ * Multipart parameters are ignored as they are not defined in the specification,
 253+ * all other types of parameter are encoded for compatibility with OAuth.
 254+ *
 255+ * @param array $params the parameters for the request
 256+ * @return void prepared values are stored in class variables
 257+ */
 258+ private function prepare_params($params) {
 259+ // do not encode multipart parameters, leave them alone
 260+ if ($this->config['multipart']) {
 261+ $this->request_params = $params;
 262+ $params = array();
 263+ }
309264
310 - foreach ($this->auth_params as $k => $v) {
311 - $kv[] = "{$k}=\"{$v}\"";
312 - }
313 - $this->auth_header = 'OAuth ' . implode(', ', $kv);
314 - $this->headers['Authorization'] = $this->auth_header;
315 - }
 265+ // signing parameters are request parameters + OAuth default parameters
 266+ $this->signing_params = array_merge($this->get_defaults(), (array)$params);
316267
317 - /**
318 - * Signs the request and adds the OAuth signature. This runs all the request
319 - * parameter preparation methods.
320 - *
321 - * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
322 - * @param string $url the request URL without query string parameters
323 - * @param array $params the request parameters as an array of key=value pairs
324 - * @param string $useauth whether to use authentication when making the request.
325 - */
326 - private function sign($method, $url, $params, $useauth) {
327 - $this->prepare_method($method);
328 - $this->prepare_url($url);
329 - $this->prepare_params($params);
 268+ // Remove oauth_signature if present
 269+ // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
 270+ if (isset($this->signing_params['oauth_signature'])) {
 271+ unset($this->signing_params['oauth_signature']);
 272+ }
330273
331 - // we don't sign anything is we're not using auth
332 - if ($useauth) {
333 - $this->prepare_base_string();
334 - $this->prepare_signing_key();
 274+ // Parameters are sorted by name, using lexicographical byte value ordering.
 275+ // Ref: Spec: 9.1.1 (1)
 276+ uksort($this->signing_params, 'strcmp');
335277
336 - $this->auth_params['oauth_signature'] = $this->safe_encode(
337 - base64_encode(
338 - hash_hmac(
339 - 'sha1', $this->base_string, $this->signing_key, true
340 - )));
 278+ // encode. Also sort the signed parameters from the POST parameters
 279+ foreach ($this->signing_params as $k => $v) {
 280+ $k = $this->safe_encode($k);
 281+ $v = $this->safe_encode($v);
 282+ $_signing_params[$k] = $v;
 283+ $kv[] = "{$k}={$v}";
 284+ }
341285
342 - $this->prepare_auth_header();
343 - }
344 - }
 286+ // auth params = the default oauth params which are present in our collection of signing params
 287+ $this->auth_params = array_intersect_key($this->get_defaults(), $_signing_params);
 288+ if (isset($_signing_params['oauth_callback'])) {
 289+ $this->auth_params['oauth_callback'] = $_signing_params['oauth_callback'];
 290+ unset($_signing_params['oauth_callback']);
 291+ }
345292
346 - /**
347 - * Make an HTTP request using this library. This method doesn't return anything.
348 - * Instead the response should be inspected directly.
349 - *
350 - * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
351 - * @param string $url the request URL without query string parameters
352 - * @param array $params the request parameters as an array of key=value pairs
353 - * @param string $useauth whether to use authentication when making the request. Default true.
354 - * @param string $multipart whether this request contains multipart data. Default false
355 - */
356 - function request($method, $url, $params=array(), $useauth=true, $multipart=false) {
357 - $this->config['multipart'] = $multipart;
 293+ if (isset($_signing_params['oauth_verifier'])) {
 294+ $this->auth_params['oauth_verifier'] = $_signing_params['oauth_verifier'];
 295+ unset($_signing_params['oauth_verifier']);
 296+ }
358297
359 - $this->create_nonce();
360 - $this->create_timestamp();
 298+ // request_params is already set if we're doing multipart, if not we need to set them now
 299+ if ( ! $this->config['multipart'])
 300+ $this->request_params = array_diff_key($_signing_params, $this->get_defaults());
361301
362 - $this->sign($method, $url, $params, $useauth);
363 - return $this->curlit();
364 - }
 302+ // create the parameter part of the base string
 303+ $this->signing_params = implode('&', $kv);
 304+ }
365305
366 - /**
367 - * Make a long poll HTTP request using this library. This method is
368 - * different to the other request methods as it isn't supposed to disconnect
369 - *
370 - * Using this method expects a callback which will receive the streaming
371 - * responses.
372 - *
373 - * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
374 - * @param string $url the request URL without query string parameters
375 - * @param array $params the request parameters as an array of key=value pairs
376 - * @param string $callback the callback function to stream the buffer to.
377 - */
378 - function streaming_request($method, $url, $params=array(), $callback='') {
379 - if ( ! empty($callback) ) {
380 - if ( ! function_exists($callback) ) {
381 - return false;
382 - }
383 - $this->config['streaming_callback'] = $callback;
384 - }
385 - $this->metrics['start'] = time();
386 - $this->metrics['interval_start'] = $this->metrics['start'];
387 - $this->metrics['tweets'] = 0;
388 - $this->metrics['last_tweets'] = 0;
389 - $this->metrics['bytes'] = 0;
390 - $this->metrics['last_bytes'] = 0;
391 - $this->config['is_streaming'] = true;
392 - $this->request($method, $url, $params);
393 - }
 306+ /**
 307+ * Prepares the OAuth signing key
 308+ *
 309+ * @return void prepared signing key is stored in a class variables
 310+ */
 311+ private function prepare_signing_key() {
 312+ $this->signing_key = $this->safe_encode($this->config['consumer_secret']) . '&' . $this->safe_encode($this->config['user_secret']);
 313+ }
394314
395 - /**
396 - * Handles the updating of the current Streaming API metrics.
397 - */
398 - function update_metrics() {
399 - $now = time();
400 - if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now)
401 - return false;
 315+ /**
 316+ * Prepare the base string.
 317+ * Ref: Spec: 9.1.3 ("Concatenate Request Elements")
 318+ *
 319+ * @return void prepared base string is stored in a class variables
 320+ */
 321+ private function prepare_base_string() {
 322+ $base = array(
 323+ $this->method,
 324+ $this->url,
 325+ $this->signing_params
 326+ );
 327+ $this->base_string = implode('&', $this->safe_encode($base));
 328+ }
402329
403 - $this->metrics['tps'] = round( ($this->metrics['tweets'] - $this->metrics['last_tweets']) / $this->config['streaming_metrics_interval'], 2);
404 - $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2);
 330+ /**
 331+ * Prepares the Authorization header
 332+ *
 333+ * @return void prepared authorization header is stored in a class variables
 334+ */
 335+ private function prepare_auth_header() {
 336+ $this->headers = array();
 337+ uksort($this->auth_params, 'strcmp');
 338+ if (!$this->config['as_header']) :
 339+ $this->request_params = array_merge($this->request_params, $this->auth_params);
 340+ return;
 341+ endif;
405342
406 - $this->metrics['last_bytes'] = $this->metrics['bytes'];
407 - $this->metrics['last_tweets'] = $this->metrics['tweets'];
408 - $this->metrics['interval_start'] = $now;
409 - return $this->metrics;
410 - }
 343+ foreach ($this->auth_params as $k => $v) {
 344+ $kv[] = "{$k}=\"{$v}\"";
 345+ }
 346+ $this->auth_header = 'OAuth ' . implode(', ', $kv);
 347+ $this->headers['Authorization'] = $this->auth_header;
 348+ }
411349
412 - /**
413 - * Utility function to create the request URL in the requested format
414 - *
415 - * @param string $request the API method without extension
416 - * @param string $format the format of the response. Default json. Set to an empty string to exclude the format
417 - * @return string the concatenation of the host, API version, API method and format
418 - */
419 - function url($request, $format='json') {
420 - $format = strlen($format) > 0 ? ".$format" : '';
421 - $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/';
 350+ /**
 351+ * Signs the request and adds the OAuth signature. This runs all the request
 352+ * parameter preparation methods.
 353+ *
 354+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
 355+ * @param string $url the request URL without query string parameters
 356+ * @param array $params the request parameters as an array of key=value pairs
 357+ * @param string $useauth whether to use authentication when making the request.
 358+ */
 359+ private function sign($method, $url, $params, $useauth) {
 360+ $this->prepare_method($method);
 361+ $this->prepare_url($url);
 362+ $this->prepare_params($params);
422363
423 - // backwards compatibility with v0.1
424 - if (isset($this->config['v']))
425 - $this->config['host'] = $this->config['host'] . '/' . $this->config['v'];
 364+ // we don't sign anything is we're not using auth
 365+ if ($useauth) {
 366+ $this->prepare_base_string();
 367+ $this->prepare_signing_key();
426368
427 - return implode('/', array(
428 - $proto,
429 - $this->config['host'],
430 - $request . $format
431 - ));
432 - }
 369+ $this->auth_params['oauth_signature'] = $this->safe_encode(
 370+ base64_encode(
 371+ hash_hmac(
 372+ 'sha1', $this->base_string, $this->signing_key, true
 373+ )));
433374
434 - /**
435 - * Public access to the private safe decode/encode methods
436 - *
437 - * @param string $text the text to transform
438 - * @param string $mode the transformation mode. either encode or decode
439 - * @return the string as transformed by the given mode
440 - */
441 - function transformText($text, $mode='encode') {
442 - return $this->{"safe_$mode"}($text);
443 - }
 375+ $this->prepare_auth_header();
 376+ }
 377+ }
444378
445 - /**
446 - * Utility function to parse the returned curl headers and store them in the
447 - * class array variable.
448 - *
449 - * @param object $ch curl handle
450 - * @param string $header the response headers
451 - * @return the string length of the header
452 - */
453 - private function curlHeader($ch, $header) {
454 - $i = strpos($header, ':');
455 - if ( ! empty($i) ) {
456 - $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
457 - $value = trim(substr($header, $i + 2));
458 - $this->response['headers'][$key] = $value;
459 - }
460 - return strlen($header);
461 - }
 379+ /**
 380+ * Make an HTTP request using this library. This method doesn't return anything.
 381+ * Instead the response should be inspected directly.
 382+ *
 383+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
 384+ * @param string $url the request URL without query string parameters
 385+ * @param array $params the request parameters as an array of key=value pairs
 386+ * @param string $useauth whether to use authentication when making the request. Default true.
 387+ * @param string $multipart whether this request contains multipart data. Default false
 388+ */
 389+ function request($method, $url, $params=array(), $useauth=true, $multipart=false) {
 390+ $this->config['multipart'] = $multipart;
462391
463 - /**
464 - * Utility function to parse the returned curl buffer and store them until
465 - * an EOL is found. The buffer for curl is an undefined size so we need
466 - * to collect the content until an EOL is found.
467 - *
468 - * This function calls the previously defined streaming callback method.
469 - *
470 - * @param object $ch curl handle
471 - * @param string $data the current curl buffer
472 - */
473 - private function curlWrite($ch, $data) {
474 - $l = strlen($data);
475 - if (strpos($data, $this->config['streaming_eol']) === false) {
476 - $this->buffer .= $data;
477 - return $l;
478 - }
 392+ $this->create_nonce();
 393+ $this->create_timestamp();
479394
480 - $buffered = explode($this->config['streaming_eol'], $data);
481 - $content = $this->buffer . $buffered[0];
 395+ $this->sign($method, $url, $params, $useauth);
 396+ return $this->curlit();
 397+ }
482398
483 - $this->metrics['tweets']++;
484 - $this->metrics['bytes'] += strlen($content);
 399+ /**
 400+ * Make a long poll HTTP request using this library. This method is
 401+ * different to the other request methods as it isn't supposed to disconnect
 402+ *
 403+ * Using this method expects a callback which will receive the streaming
 404+ * responses.
 405+ *
 406+ * @param string $method the HTTP method being used. e.g. POST, GET, HEAD etc
 407+ * @param string $url the request URL without query string parameters
 408+ * @param array $params the request parameters as an array of key=value pairs
 409+ * @param string $callback the callback function to stream the buffer to.
 410+ */
 411+ function streaming_request($method, $url, $params=array(), $callback='') {
 412+ if ( ! empty($callback) ) {
 413+ if ( ! function_exists($callback) ) {
 414+ return false;
 415+ }
 416+ $this->config['streaming_callback'] = $callback;
 417+ }
 418+ $this->metrics['start'] = time();
 419+ $this->metrics['interval_start'] = $this->metrics['start'];
 420+ $this->metrics['tweets'] = 0;
 421+ $this->metrics['last_tweets'] = 0;
 422+ $this->metrics['bytes'] = 0;
 423+ $this->metrics['last_bytes'] = 0;
 424+ $this->config['is_streaming'] = true;
 425+ $this->request($method, $url, $params);
 426+ }
485427
486 - if ( ! function_exists($this->config['streaming_callback']))
487 - return 0;
 428+ /**
 429+ * Handles the updating of the current Streaming API metrics.
 430+ */
 431+ function update_metrics() {
 432+ $now = time();
 433+ if (($this->metrics['interval_start'] + $this->config['streaming_metrics_interval']) > $now)
 434+ return false;
488435
489 - $metrics = $this->update_metrics();
490 - $stop = call_user_func(
491 - $this->config['streaming_callback'],
492 - $content,
493 - strlen($content),
494 - $metrics
495 - );
496 - $this->buffer = $buffered[1];
497 - if ($stop)
498 - return 0;
 436+ $this->metrics['tps'] = round( ($this->metrics['tweets'] - $this->metrics['last_tweets']) / $this->config['streaming_metrics_interval'], 2);
 437+ $this->metrics['bps'] = round( ($this->metrics['bytes'] - $this->metrics['last_bytes']) / $this->config['streaming_metrics_interval'], 2);
499438
500 - return $l;
501 - }
 439+ $this->metrics['last_bytes'] = $this->metrics['bytes'];
 440+ $this->metrics['last_tweets'] = $this->metrics['tweets'];
 441+ $this->metrics['interval_start'] = $now;
 442+ return $this->metrics;
 443+ }
502444
503 - /**
504 - * Makes a curl request. Takes no parameters as all should have been prepared
505 - * by the request method
506 - *
507 - * @return void response data is stored in the class variable 'response'
508 - */
509 - private function curlit() {
510 - // method handling
511 - switch ($this->method) {
512 - case 'POST':
513 - break;
514 - default:
515 - // GET, DELETE request so convert the parameters to a querystring
516 - if ( ! empty($this->request_params)) {
517 - foreach ($this->request_params as $k => $v) {
518 - // Multipart params haven't been encoded yet.
519 - // Not sure why you would do a multipart GET but anyway, here's the support for it
520 - if ($this->config['multipart']) {
521 - $params[] = $this->safe_encode($k) . '=' . $this->safe_encode($v);
522 - } else {
523 - $params[] = $k . '=' . $v;
524 - }
525 - }
526 - $qs = implode('&', $params);
527 - $this->url = strlen($qs) > 0 ? $this->url . '?' . $qs : $this->url;
528 - $this->request_params = array();
529 - }
530 - break;
531 - }
 445+ /**
 446+ * Utility function to create the request URL in the requested format
 447+ *
 448+ * @param string $request the API method without extension
 449+ * @param string $format the format of the response. Default json. Set to an empty string to exclude the format
 450+ * @return string the concatenation of the host, API version, API method and format
 451+ */
 452+ function url($request, $format='json') {
 453+ $format = strlen($format) > 0 ? ".$format" : '';
 454+ $proto = $this->config['use_ssl'] ? 'https:/' : 'http:/';
532455
533 - // configure curl
534 - $c = curl_init();
535 - curl_setopt_array($c, array(
536 - CURLOPT_USERAGENT => $this->config['user_agent'],
537 - CURLOPT_CONNECTTIMEOUT => $this->config['curl_connecttimeout'],
538 - CURLOPT_TIMEOUT => $this->config['curl_timeout'],
539 - CURLOPT_RETURNTRANSFER => TRUE,
540 - CURLOPT_SSL_VERIFYPEER => $this->config['curl_ssl_verifypeer'],
541 - CURLOPT_FOLLOWLOCATION => $this->config['curl_followlocation'],
542 - CURLOPT_PROXY => $this->config['curl_proxy'],
543 - CURLOPT_ENCODING => $this->config['curl_encoding'],
544 - CURLOPT_URL => $this->url,
545 - // process the headers
546 - CURLOPT_HEADERFUNCTION => array($this, 'curlHeader'),
547 - CURLOPT_HEADER => FALSE,
548 - CURLINFO_HEADER_OUT => true,
549 - ));
 456+ // backwards compatibility with v0.1
 457+ if (isset($this->config['v']))
 458+ $this->config['host'] = $this->config['host'] . '/' . $this->config['v'];
550459
551 - if ($this->config['curl_proxyuserpwd'] !== false)
552 - curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']);
 460+ return implode('/', array(
 461+ $proto,
 462+ $this->config['host'],
 463+ $request . $format
 464+ ));
 465+ }
553466
554 - if ($this->config['is_streaming']) {
555 - // process the body
556 - $this->response['content-length'] = 0;
557 - curl_setopt($c, CURLOPT_TIMEOUT, 0);
558 - curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
559 - }
 467+ /**
 468+ * Public access to the private safe decode/encode methods
 469+ *
 470+ * @param string $text the text to transform
 471+ * @param string $mode the transformation mode. either encode or decode
 472+ * @return the string as transformed by the given mode
 473+ */
 474+ function transformText($text, $mode='encode') {
 475+ return $this->{"safe_$mode"}($text);
 476+ }
560477
561 - switch ($this->method) {
562 - case 'GET':
563 - break;
564 - case 'POST':
565 - curl_setopt($c, CURLOPT_POST, TRUE);
566 - break;
567 - default:
568 - curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method);
569 - }
 478+ /**
 479+ * Utility function to parse the returned curl headers and store them in the
 480+ * class array variable.
 481+ *
 482+ * @param object $ch curl handle
 483+ * @param string $header the response headers
 484+ * @return the string length of the header
 485+ */
 486+ private function curlHeader($ch, $header) {
 487+ $i = strpos($header, ':');
 488+ if ( ! empty($i) ) {
 489+ $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
 490+ $value = trim(substr($header, $i + 2));
 491+ $this->response['headers'][$key] = $value;
 492+ }
 493+ return strlen($header);
 494+ }
570495
571 - if ( ! empty($this->request_params) ) {
572 - // if not doing multipart we need to implode the parameters
573 - if ( ! $this->config['multipart'] ) {
574 - foreach ($this->request_params as $k => $v) {
575 - $ps[] = "{$k}={$v}";
576 - }
577 - $this->request_params = implode('&', $ps);
578 - }
579 - curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_params);
580 - } else {
581 - // CURL will set length to -1 when there is no data, which breaks Twitter
582 - $this->headers['Content-Type'] = '';
583 - $this->headers['Content-Length'] = '';
584 - }
 496+ /**
 497+ * Utility function to parse the returned curl buffer and store them until
 498+ * an EOL is found. The buffer for curl is an undefined size so we need
 499+ * to collect the content until an EOL is found.
 500+ *
 501+ * This function calls the previously defined streaming callback method.
 502+ *
 503+ * @param object $ch curl handle
 504+ * @param string $data the current curl buffer
 505+ */
 506+ private function curlWrite($ch, $data) {
 507+ $l = strlen($data);
 508+ if (strpos($data, $this->config['streaming_eol']) === false) {
 509+ $this->buffer .= $data;
 510+ return $l;
 511+ }
585512
586 - // CURL defaults to setting this to Expect: 100-Continue which Twitter rejects
587 - $this->headers['Expect'] = '';
 513+ $buffered = explode($this->config['streaming_eol'], $data);
 514+ $content = $this->buffer . $buffered[0];
588515
589 - if ( ! empty($this->headers)) {
590 - foreach ($this->headers as $k => $v) {
591 - $headers[] = trim($k . ': ' . $v);
592 - }
593 - curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
594 - }
 516+ $this->metrics['tweets']++;
 517+ $this->metrics['bytes'] += strlen($content);
595518
596 - if (isset($this->config['prevent_request']) && true == $this->config['prevent_request'])
597 - return;
 519+ if ( ! function_exists($this->config['streaming_callback']))
 520+ return 0;
598521
599 - // do it!
600 - $response = curl_exec($c);
601 - $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
602 - $info = curl_getinfo($c);
603 - curl_close($c);
 522+ $metrics = $this->update_metrics();
 523+ $stop = call_user_func(
 524+ $this->config['streaming_callback'],
 525+ $content,
 526+ strlen($content),
 527+ $metrics
 528+ );
 529+ $this->buffer = $buffered[1];
 530+ if ($stop)
 531+ return 0;
604532
605 - // store the response
606 - $this->response['code'] = $code;
607 - $this->response['response'] = $response;
608 - $this->response['info'] = $info;
609 - return $code;
610 - }
 533+ return $l;
 534+ }
 535+
 536+ /**
 537+ * Makes a curl request. Takes no parameters as all should have been prepared
 538+ * by the request method
 539+ *
 540+ * @return void response data is stored in the class variable 'response'
 541+ */
 542+ private function curlit() {
 543+ // method handling
 544+ switch ($this->method) {
 545+ case 'POST':
 546+ break;
 547+ default:
 548+ // GET, DELETE request so convert the parameters to a querystring
 549+ if ( ! empty($this->request_params)) {
 550+ foreach ($this->request_params as $k => $v) {
 551+ // Multipart params haven't been encoded yet.
 552+ // Not sure why you would do a multipart GET but anyway, here's the support for it
 553+ if ($this->config['multipart']) {
 554+ $params[] = $this->safe_encode($k) . '=' . $this->safe_encode($v);
 555+ } else {
 556+ $params[] = $k . '=' . $v;
 557+ }
 558+ }
 559+ $qs = implode('&', $params);
 560+ $this->url = strlen($qs) > 0 ? $this->url . '?' . $qs : $this->url;
 561+ $this->request_params = array();
 562+ }
 563+ break;
 564+ }
 565+
 566+ // configure curl
 567+ $c = curl_init();
 568+ curl_setopt_array($c, array(
 569+ CURLOPT_USERAGENT => $this->config['user_agent'],
 570+ CURLOPT_CONNECTTIMEOUT => $this->config['curl_connecttimeout'],
 571+ CURLOPT_TIMEOUT => $this->config['curl_timeout'],
 572+ CURLOPT_RETURNTRANSFER => true,
 573+ CURLOPT_SSL_VERIFYPEER => $this->config['curl_ssl_verifypeer'],
 574+ CURLOPT_SSL_VERIFYHOST => $this->config['curl_ssl_verifyhost'],
 575+
 576+ CURLOPT_FOLLOWLOCATION => $this->config['curl_followlocation'],
 577+ CURLOPT_PROXY => $this->config['curl_proxy'],
 578+ CURLOPT_ENCODING => $this->config['curl_encoding'],
 579+ CURLOPT_URL => $this->url,
 580+ // process the headers
 581+ CURLOPT_HEADERFUNCTION => array($this, 'curlHeader'),
 582+ CURLOPT_HEADER => false,
 583+ CURLINFO_HEADER_OUT => true,
 584+ ));
 585+
 586+ if ($this->config['curl_cainfo'] !== false)
 587+ curl_setopt($c, CURLOPT_CAINFO, $this->config['curl_cainfo']);
 588+
 589+ if ($this->config['curl_capath'] !== false)
 590+ curl_setopt($c, CURLOPT_CAPATH, $this->config['curl_capath']);
 591+
 592+ if ($this->config['curl_proxyuserpwd'] !== false)
 593+ curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->config['curl_proxyuserpwd']);
 594+
 595+ if ($this->config['is_streaming']) {
 596+ // process the body
 597+ $this->response['content-length'] = 0;
 598+ curl_setopt($c, CURLOPT_TIMEOUT, 0);
 599+ curl_setopt($c, CURLOPT_WRITEFUNCTION, array($this, 'curlWrite'));
 600+ }
 601+
 602+ switch ($this->method) {
 603+ case 'GET':
 604+ break;
 605+ case 'POST':
 606+ curl_setopt($c, CURLOPT_POST, true);
 607+ break;
 608+ default:
 609+ curl_setopt($c, CURLOPT_CUSTOMREQUEST, $this->method);
 610+ }
 611+
 612+ if ( ! empty($this->request_params) ) {
 613+ // if not doing multipart we need to implode the parameters
 614+ if ( ! $this->config['multipart'] ) {
 615+ foreach ($this->request_params as $k => $v) {
 616+ $ps[] = "{$k}={$v}";
 617+ }
 618+ $this->request_params = implode('&', $ps);
 619+ }
 620+ curl_setopt($c, CURLOPT_POSTFIELDS, $this->request_params);
 621+ } else {
 622+ // CURL will set length to -1 when there is no data, which breaks Twitter
 623+ $this->headers['Content-Type'] = '';
 624+ $this->headers['Content-Length'] = '';
 625+ }
 626+
 627+ // CURL defaults to setting this to Expect: 100-Continue which Twitter rejects
 628+ $this->headers['Expect'] = '';
 629+
 630+ if ( ! empty($this->headers)) {
 631+ foreach ($this->headers as $k => $v) {
 632+ $headers[] = trim($k . ': ' . $v);
 633+ }
 634+ curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
 635+ }
 636+
 637+ if (isset($this->config['prevent_request']) && true == $this->config['prevent_request'])
 638+ return;
 639+
 640+ // do it!
 641+ $response = curl_exec($c);
 642+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
 643+ $info = curl_getinfo($c);
 644+ $error = curl_error($c);
 645+ $errno = curl_errno($c);
 646+ curl_close($c);
 647+
 648+ // store the response
 649+ $this->response['code'] = $code;
 650+ $this->response['response'] = $response;
 651+ $this->response['info'] = $info;
 652+ $this->response['error'] = $error;
 653+ $this->response['errno'] = $errno;
 654+ return $code;
 655+ }
611656 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r107660Revert r107654, whitespace conversion wasn't intentionalreedy21:58, 30 December 2011
r107661Do r107654 again properly...reedy21:59, 30 December 2011

Status & tagging log