· 7 years ago · Jun 12, 2018, 08:20 PM
1<?php
2
3/**
4 * Perform a signed OAuth request with a GET, POST, PUT or DELETE operation.
5 *
6 * @version $Id: OAuthRequester.php 174 2010-11-24 15:15:41Z brunobg@corollarium.com $
7 * @author Marc Worrell <marcw@pobox.com>
8 * @date Nov 20, 2007 1:41:38 PM
9 *
10 * The MIT License
11 *
12 * Copyright (c) 2007-2008 Mediamatic Lab
13 *
14 * Permission is hereby granted, free of charge, to any person obtaining a copy
15 * of this software and associated documentation files (the "Software"), to deal
16 * in the Software without restriction, including without limitation the rights
17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 * copies of the Software, and to permit persons to whom the Software is
19 * furnished to do so, subject to the following conditions:
20 *
21 * The above copyright notice and this permission notice shall be included in
22 * all copies or substantial portions of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30 * THE SOFTWARE.
31 */
32
33require_once dirname(__FILE__) . '/OAuthRequestSigner.php';
34require_once dirname(__FILE__) . '/body/OAuthBodyContentDisposition.php';
35
36
37class OAuthRequester extends OAuthRequestSigner
38{
39 protected $files;
40
41 /**
42 * Construct a new request signer. Perform the request with the doRequest() method below.
43 *
44 * A request can have either one file or a body, not both.
45 *
46 * The files array consists of arrays:
47 * - file the filename/path containing the data for the POST/PUT
48 * - data data for the file, omit when you have a file
49 * - mime content-type of the file
50 * - filename filename for content disposition header
51 *
52 * When OAuth (and PHP) can support multipart/form-data then we can handle more than one file.
53 * For now max one file, with all the params encoded in the query string.
54 *
55 * @param string request
56 * @param string method http method. GET, PUT, POST etc.
57 * @param array params name=>value array with request parameters
58 * @param string body optional body to send
59 * @param array files optional files to send (max 1 till OAuth support multipart/form-data posts)
60 */
61 function __construct ( $request, $method = null, $params = null, $body = null, $files = null )
62 {
63 parent::__construct($request, $method, $params, $body);
64
65 // When there are files, then we can construct a POST with a single file
66 if (!empty($files))
67 {
68 $empty = true;
69 foreach ($files as $f)
70 {
71 $empty = $empty && empty($f['file']) && !isset($f['data']);
72 }
73
74 if (!$empty)
75 {
76 if (!is_null($body))
77 {
78 throw new OAuthException2('When sending files, you can\'t send a body as well.');
79 }
80 $this->files = $files;
81 }
82 }
83 }
84
85
86 /**
87 * Perform the request, returns the response code, headers and body.
88 *
89 * @param int usr_id optional user id for which we make the request
90 * @param array curl_options optional extra options for curl request
91 * @param array options options like name and token_ttl
92 * @exception OAuthException2 when authentication not accepted
93 * @exception OAuthException2 when signing was not possible
94 * @return array (code=>int, headers=>array(), body=>string)
95 */
96 function doRequest ( $usr_id = 0, $curl_options = array(), $options = array() )
97 {
98 $name = isset($options['name']) ? $options['name'] : '';
99 if (isset($options['token_ttl']))
100 {
101 $this->setParam('xoauth_token_ttl', intval($options['token_ttl']));
102 }
103
104 if (!empty($this->files))
105 {
106 // At the moment OAuth does not support multipart/form-data, so try to encode
107 // the supplied file (or data) as the request body and add a content-disposition header.
108 list($extra_headers, $body) = OAuthBodyContentDisposition::encodeBody($this->files);
109 $this->setBody($body);
110 $curl_options = $this->prepareCurlOptions($curl_options, $extra_headers);
111 }
112 $this->sign($usr_id, null, $name);
113 $text = $this->curl_raw($curl_options);
114 $result = $this->curl_parse($text);
115 if ($result['code'] >= 400)
116 {
117 throw new OAuthException2('Request failed with code ' . $result['code'] . ': ' . $result['body']);
118 }
119
120 // Record the token time to live for this server access token, immediate delete iff ttl <= 0
121 // Only done on a succesful request.
122 $token_ttl = $this->getParam('xoauth_token_ttl', false);
123 if (is_numeric($token_ttl))
124 {
125 $this->store->setServerTokenTtl($this->getParam('oauth_consumer_key',true), $this->getParam('oauth_token',true), $token_ttl);
126 }
127
128 return $result;
129 }
130
131
132 /**
133 * Request a request token from the site belonging to consumer_key
134 *
135 * @param string consumer_key
136 * @param int usr_id
137 * @param array params (optional) extra arguments for when requesting the request token
138 * @param string method (optional) change the method of the request, defaults to POST (as it should be)
139 * @param array options (optional) options like name and token_ttl
140 * @param array curl_options optional extra options for curl request
141 * @exception OAuthException2 when no key could be fetched
142 * @exception OAuthException2 when no server with consumer_key registered
143 * @return array (authorize_uri, token)
144 */
145 static function requestRequestToken ( $consumer_key, $usr_id, $params = null, $method = 'POST', $options = array(), $curl_options = array())
146 {
147 OAuthRequestLogger::start();
148
149 if (isset($options['token_ttl']) && is_numeric($options['token_ttl']))
150 {
151 $params['xoauth_token_ttl'] = intval($options['token_ttl']);
152 }
153
154 $store = OAuthStore::instance();
155 $r = $store->getServer($consumer_key, $usr_id);
156 $uri = $r['request_token_uri'];
157
158 $oauth = new OAuthRequester($uri, $method, $params);
159 $oauth->sign($usr_id, $r, '', 'requestToken');
160 $text = $oauth->curl_raw($curl_options);
161
162 if (empty($text))
163 {
164 throw new OAuthException2('No answer from the server "'.$uri.'" while requesting a request token');
165 }
166 $data = $oauth->curl_parse($text);
167
168 var_dump($data);
169
170 if ($data['code'] != 200)
171 {
172 throw new OAuthException2('Unexpected result from the server "'.$uri.'" ('.$data['code'].') while requesting a request token');
173 }
174 $token = array();
175 $params = explode('&', $data['body']);
176 foreach ($params as $p)
177 {
178 @list($name, $value) = explode('=', $p, 2);
179 $token[$name] = $oauth->urldecode($value);
180 }
181
182 if (!empty($token['oauth_token']) && !empty($token['oauth_token_secret']))
183 {
184 $opts = array();
185 if (isset($options['name']))
186 {
187 $opts['name'] = $options['name'];
188 }
189 if (isset($token['xoauth_token_ttl']))
190 {
191 $opts['token_ttl'] = $token['xoauth_token_ttl'];
192 }
193 $store->addServerToken($consumer_key, 'request', $token['oauth_token'], $token['oauth_token_secret'], $usr_id, $opts);
194 }
195 else
196 {
197 throw new OAuthException2('The server "'.$uri.'" did not return the oauth_token or the oauth_token_secret');
198 }
199
200 OAuthRequestLogger::flush();
201
202 // Now we can direct a browser to the authorize_uri
203 return array(
204 'authorize_uri' => $r['authorize_uri'],
205 'token' => $token['oauth_token']
206 );
207 }
208
209
210 /**
211 * Request an access token from the site belonging to consumer_key.
212 * Before this we got an request token, now we want to exchange it for
213 * an access token.
214 *
215 * @param string consumer_key
216 * @param string token
217 * @param int usr_id user requesting the access token
218 * @param string method (optional) change the method of the request, defaults to POST (as it should be)
219 * @param array options (optional) extra options for request, eg token_ttl
220 * @param array curl_options optional extra options for curl request
221 *
222 * @exception OAuthException2 when no key could be fetched
223 * @exception OAuthException2 when no server with consumer_key registered
224 */
225 static function requestAccessToken ( $consumer_key, $token, $usr_id, $method = 'POST', $options = array(), $curl_options = array() )
226 {
227 OAuthRequestLogger::start();
228
229 $store = OAuthStore::instance();
230 $r = $store->getServerTokenSecrets($consumer_key, $token, 'request', $usr_id);
231 $uri = $r['access_token_uri'];
232 $token_name = $r['token_name'];
233
234 // Delete the server request token, this one was for one use only
235 $store->deleteServerToken($consumer_key, $r['token'], 0, true);
236
237 // Try to exchange our request token for an access token
238 $oauth = new OAuthRequester($uri, $method);
239
240 if (isset($options['oauth_verifier']))
241 {
242 $oauth->setParam('oauth_verifier', $options['oauth_verifier']);
243 }
244 if (isset($options['token_ttl']) && is_numeric($options['token_ttl']))
245 {
246 $oauth->setParam('xoauth_token_ttl', intval($options['token_ttl']));
247 }
248
249 OAuthRequestLogger::setRequestObject($oauth);
250
251 $oauth->sign($usr_id, $r, '', 'accessToken');
252 $text = $oauth->curl_raw($curl_options);
253 if (empty($text))
254 {
255 throw new OAuthException2('No answer from the server "'.$uri.'" while requesting an access token');
256 }
257 $data = $oauth->curl_parse($text);
258
259 var_dump($data);
260
261 if ($data['code'] != 200)
262 {
263 throw new OAuthException2('Unexpected result from the server "'.$uri.'" ('.$data['code'].') while requesting an access token');
264 }
265
266 $token = array();
267 $params = explode('&', $data['body']);
268 foreach ($params as $p)
269 {
270 @list($name, $value) = explode('=', $p, 2);
271 $token[$oauth->urldecode($name)] = $oauth->urldecode($value);
272 }
273
274 if (!empty($token['oauth_token']) && !empty($token['oauth_token_secret']))
275 {
276 $opts = array();
277 $opts['name'] = $token_name;
278 if (isset($token['xoauth_token_ttl']))
279 {
280 $opts['token_ttl'] = $token['xoauth_token_ttl'];
281 }
282 $store->addServerToken($consumer_key, 'access', $token['oauth_token'], $token['oauth_token_secret'], $usr_id, $opts);
283 }
284 else
285 {
286 throw new OAuthException2('The server "'.$uri.'" did not return the oauth_token or the oauth_token_secret');
287 }
288
289 OAuthRequestLogger::flush();
290 }
291
292
293
294 /**
295 * Open and close a curl session passing all the options to the curl libs
296 *
297 * @param array opts the curl options.
298 * @exception OAuthException2 when temporary file for PUT operation could not be created
299 * @return string the result of the curl action
300 */
301 protected function curl_raw ( $opts = array() )
302 {
303 if (isset($opts[CURLOPT_HTTPHEADER]))
304 {
305 $header = $opts[CURLOPT_HTTPHEADER];
306 }
307 else
308 {
309 $header = array();
310 }
311
312 $ch = curl_init();
313 $method = $this->getMethod();
314 $url = $this->getRequestUrl();
315 $header[] = $this->getAuthorizationHeader();
316 $query = $this->getQueryString();
317 $body = $this->getBody();
318
319 $has_content_type = false;
320 foreach ($header as $h)
321 {
322 if (strncasecmp($h, 'Content-Type:', 13) == 0)
323 {
324 $has_content_type = true;
325 }
326 }
327
328 if (!is_null($body))
329 {
330 if ($method == 'TRACE')
331 {
332 throw new OAuthException2('A body can not be sent with a TRACE operation');
333 }
334
335 // PUT and POST allow a request body
336 if (!empty($query))
337 {
338 $url .= '?'.$query;
339 }
340
341 // Make sure that the content type of the request is ok
342 if (!$has_content_type)
343 {
344 $header[] = 'Content-Type: application/octet-stream';
345 $has_content_type = true;
346 }
347
348 // When PUTting, we need to use an intermediate file (because of the curl implementation)
349 if ($method == 'PUT')
350 {
351 /*
352 if (version_compare(phpversion(), '5.2.0') >= 0)
353 {
354 // Use the data wrapper to create the file expected by the put method
355 $put_file = fopen('data://application/octet-stream;base64,'.base64_encode($body));
356 }
357 */
358
359 $put_file = @tmpfile();
360 if (!$put_file)
361 {
362 throw new OAuthException2('Could not create tmpfile for PUT operation');
363 }
364 fwrite($put_file, $body);
365 fseek($put_file, 0);
366
367 curl_setopt($ch, CURLOPT_PUT, true);
368 curl_setopt($ch, CURLOPT_INFILE, $put_file);
369 curl_setopt($ch, CURLOPT_INFILESIZE, strlen($body));
370 }
371 else
372 {
373 curl_setopt($ch, CURLOPT_POST, true);
374 curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
375 }
376 }
377 else
378 {
379 // a 'normal' request, no body to be send
380 if ($method == 'POST')
381 {
382 if (!$has_content_type)
383 {
384 $header[] = 'Content-Type: application/x-www-form-urlencoded';
385 $has_content_type = true;
386 }
387
388 curl_setopt($ch, CURLOPT_POST, true);
389 curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
390 }
391 else
392 {
393 if (!empty($query))
394 {
395 $url .= '?'.$query;
396 }
397 if ($method != 'GET')
398 {
399 curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
400 }
401 }
402 }
403
404 curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
405 curl_setopt($ch, CURLOPT_USERAGENT, 'anyMeta/OAuth 1.0 - ($LastChangedRevision: 174 $)');
406 curl_setopt($ch, CURLOPT_URL, $url);
407 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
408 curl_setopt($ch, CURLOPT_HEADER, true);
409 curl_setopt($ch, CURLOPT_TIMEOUT, 30);
410
411 foreach ($opts as $k => $v)
412 {
413 if ($k != CURLOPT_HTTPHEADER)
414 {
415 curl_setopt($ch, $k, $v);
416 }
417 }
418
419 $txt = curl_exec($ch);
420 if ($txt === false) {
421 $error = curl_error($ch);
422 curl_close($ch);
423 throw new OAuthException2('CURL error: ' . $error);
424 }
425 curl_close($ch);
426
427 if (!empty($put_file))
428 {
429 fclose($put_file);
430 }
431
432 // Tell the logger what we requested and what we received back
433 $data = $method . " $url\n".implode("\n",$header);
434 if (is_string($body))
435 {
436 $data .= "\n\n".$body;
437 }
438 else if ($method == 'POST')
439 {
440 $data .= "\n\n".$query;
441 }
442
443 OAuthRequestLogger::setSent($data, $body);
444 OAuthRequestLogger::setReceived($txt);
445
446 return $txt;
447 }
448
449
450 /**
451 * Parse an http response
452 *
453 * @param string response the http text to parse
454 * @return array (code=>http-code, headers=>http-headers, body=>body)
455 */
456 protected function curl_parse ( $response )
457 {
458 if (empty($response))
459 {
460 return array();
461 }
462
463 @list($headers,$body) = explode("\r\n\r\n",$response,2);
464 $lines = explode("\r\n",$headers);
465
466 if (preg_match('@^HTTP/[0-9]\.[0-9] +100@', $lines[0]))
467 {
468 /* HTTP/1.x 100 Continue
469 * the real data is on the next line
470 */
471 @list($headers,$body) = explode("\r\n\r\n",$body,2);
472 $lines = explode("\r\n",$headers);
473 }
474
475 // first line of headers is the HTTP response code
476 $http_line = array_shift($lines);
477 if (preg_match('@^HTTP/[0-9]\.[0-9] +([0-9]{3})@', $http_line, $matches))
478 {
479 $code = $matches[1];
480 }
481
482 // put the rest of the headers in an array
483 $headers = array();
484 foreach ($lines as $l)
485 {
486 list($k, $v) = explode(': ', $l, 2);
487 $headers[strtolower($k)] = $v;
488 }
489
490 return array( 'code' => $code, 'headers' => $headers, 'body' => $body);
491 }
492
493
494 /**
495 * Mix the given headers into the headers that were given to curl
496 *
497 * @param array curl_options
498 * @param array extra_headers
499 * @return array new curl options
500 */
501 protected function prepareCurlOptions ( $curl_options, $extra_headers )
502 {
503 $hs = array();
504 if (!empty($curl_options[CURLOPT_HTTPHEADER]) && is_array($curl_options[CURLOPT_HTTPHEADER]))
505 {
506 foreach ($curl_options[CURLOPT_HTTPHEADER] as $h)
507 {
508 list($opt, $val) = explode(':', $h, 2);
509 $opt = str_replace(' ', '-', ucwords(str_replace('-', ' ', $opt)));
510 $hs[$opt] = $val;
511 }
512 }
513
514 $curl_options[CURLOPT_HTTPHEADER] = array();
515 $hs = array_merge($hs, $extra_headers);
516 foreach ($hs as $h => $v)
517 {
518 $curl_options[CURLOPT_HTTPHEADER][] = "$h: $v";
519 }
520 return $curl_options;
521 }
522}
523
524/* vi:set ts=4 sts=4 sw=4 binary noeol: */
525
526?>