Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

893 lines
26 KiB

10 years ago
10 years ago
  1. <?php
  2. // vim: foldmethod=marker
  3. /* Generic exception class
  4. */
  5. if (!class_exists('OAuthException')) {
  6. class OAuthException extends Exception {
  7. // pass
  8. }
  9. }
  10. class OAuthConsumer {
  11. public $key;
  12. public $secret;
  13. function __construct($key, $secret, $callback_url=NULL) {
  14. $this->key = $key;
  15. $this->secret = $secret;
  16. $this->callback_url = $callback_url;
  17. }
  18. function __toString() {
  19. return "OAuthConsumer[key=$this->key,secret=$this->secret]";
  20. }
  21. }
  22. class OAuthToken {
  23. // access tokens and request tokens
  24. public $key;
  25. public $secret;
  26. public $expires;
  27. public $scope;
  28. public $uid;
  29. /**
  30. * key = the token
  31. * secret = the token secret
  32. */
  33. function __construct($key, $secret) {
  34. $this->key = $key;
  35. $this->secret = $secret;
  36. }
  37. /**
  38. * generates the basic string serialization of a token that a server
  39. * would respond to request_token and access_token calls with
  40. */
  41. function to_string() {
  42. return "oauth_token=" .
  43. OAuthUtil::urlencode_rfc3986($this->key) .
  44. "&oauth_token_secret=" .
  45. OAuthUtil::urlencode_rfc3986($this->secret);
  46. }
  47. function __toString() {
  48. return $this->to_string();
  49. }
  50. }
  51. /**
  52. * A class for implementing a Signature Method
  53. * See section 9 ("Signing Requests") in the spec
  54. */
  55. abstract class OAuthSignatureMethod {
  56. /**
  57. * Needs to return the name of the Signature Method (ie HMAC-SHA1)
  58. * @return string
  59. */
  60. abstract public function get_name();
  61. /**
  62. * Build up the signature
  63. * NOTE: The output of this function MUST NOT be urlencoded.
  64. * the encoding is handled in OAuthRequest when the final
  65. * request is serialized
  66. * @param OAuthRequest $request
  67. * @param OAuthConsumer $consumer
  68. * @param OAuthToken $token
  69. * @return string
  70. */
  71. abstract public function build_signature($request, $consumer, $token);
  72. /**
  73. * Verifies that a given signature is correct
  74. * @param OAuthRequest $request
  75. * @param OAuthConsumer $consumer
  76. * @param OAuthToken $token
  77. * @param string $signature
  78. * @return bool
  79. */
  80. public function check_signature($request, $consumer, $token, $signature) {
  81. $built = $this->build_signature($request, $consumer, $token);
  82. //echo "<pre>"; var_dump($signature, $built, ($built == $signature)); killme();
  83. return ($built == $signature);
  84. }
  85. }
  86. /**
  87. * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
  88. * where the Signature Base String is the text and the key is the concatenated values (each first
  89. * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
  90. * character (ASCII code 38) even if empty.
  91. * - Chapter 9.2 ("HMAC-SHA1")
  92. */
  93. class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
  94. function get_name() {
  95. return "HMAC-SHA1";
  96. }
  97. public function build_signature($request, $consumer, $token) {
  98. $base_string = $request->get_signature_base_string();
  99. $request->base_string = $base_string;
  100. $key_parts = array(
  101. $consumer->secret,
  102. ($token) ? $token->secret : ""
  103. );
  104. $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
  105. $key = implode('&', $key_parts);
  106. $r = base64_encode(hash_hmac('sha1', $base_string, $key, true));
  107. return $r;
  108. }
  109. }
  110. /**
  111. * The PLAINTEXT method does not provide any security protection and SHOULD only be used
  112. * over a secure channel such as HTTPS. It does not use the Signature Base String.
  113. * - Chapter 9.4 ("PLAINTEXT")
  114. */
  115. class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
  116. public function get_name() {
  117. return "PLAINTEXT";
  118. }
  119. /**
  120. * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
  121. * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
  122. * empty. The result MUST be encoded again.
  123. * - Chapter 9.4.1 ("Generating Signatures")
  124. *
  125. * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
  126. * OAuthRequest handles this!
  127. */
  128. public function build_signature($request, $consumer, $token) {
  129. $key_parts = array(
  130. $consumer->secret,
  131. ($token) ? $token->secret : ""
  132. );
  133. $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
  134. $key = implode('&', $key_parts);
  135. $request->base_string = $key;
  136. return $key;
  137. }
  138. }
  139. /**
  140. * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
  141. * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
  142. * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
  143. * verified way to the Service Provider, in a manner which is beyond the scope of this
  144. * specification.
  145. * - Chapter 9.3 ("RSA-SHA1")
  146. */
  147. abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
  148. public function get_name() {
  149. return "RSA-SHA1";
  150. }
  151. // Up to the SP to implement this lookup of keys. Possible ideas are:
  152. // (1) do a lookup in a table of trusted certs keyed off of consumer
  153. // (2) fetch via http using a url provided by the requester
  154. // (3) some sort of specific discovery code based on request
  155. //
  156. // Either way should return a string representation of the certificate
  157. protected abstract function fetch_public_cert(&$request);
  158. // Up to the SP to implement this lookup of keys. Possible ideas are:
  159. // (1) do a lookup in a table of trusted certs keyed off of consumer
  160. //
  161. // Either way should return a string representation of the certificate
  162. protected abstract function fetch_private_cert(&$request);
  163. public function build_signature($request, $consumer, $token) {
  164. $base_string = $request->get_signature_base_string();
  165. $request->base_string = $base_string;
  166. // Fetch the private key cert based on the request
  167. $cert = $this->fetch_private_cert($request);
  168. // Pull the private key ID from the certificate
  169. $privatekeyid = openssl_get_privatekey($cert);
  170. // Sign using the key
  171. $ok = openssl_sign($base_string, $signature, $privatekeyid);
  172. // Release the key resource
  173. openssl_free_key($privatekeyid);
  174. return base64_encode($signature);
  175. }
  176. public function check_signature($request, $consumer, $token, $signature) {
  177. $decoded_sig = base64_decode($signature);
  178. $base_string = $request->get_signature_base_string();
  179. // Fetch the public key cert based on the request
  180. $cert = $this->fetch_public_cert($request);
  181. // Pull the public key ID from the certificate
  182. $publickeyid = openssl_get_publickey($cert);
  183. // Check the computed signature against the one passed in the query
  184. $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
  185. // Release the key resource
  186. openssl_free_key($publickeyid);
  187. return $ok == 1;
  188. }
  189. }
  190. class OAuthRequest {
  191. private $parameters;
  192. private $http_method;
  193. private $http_url;
  194. // for debug purposes
  195. public $base_string;
  196. public static $version = '1.0';
  197. public static $POST_INPUT = 'php://input';
  198. function __construct($http_method, $http_url, $parameters=NULL) {
  199. @$parameters or $parameters = array();
  200. $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
  201. $this->parameters = $parameters;
  202. $this->http_method = $http_method;
  203. $this->http_url = $http_url;
  204. }
  205. /**
  206. * attempt to build up a request from what was passed to the server
  207. */
  208. public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
  209. $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
  210. ? 'http'
  211. : 'https';
  212. @$http_url or $http_url = $scheme .
  213. '://' . $_SERVER['HTTP_HOST'] .
  214. ':' .
  215. $_SERVER['SERVER_PORT'] .
  216. $_SERVER['REQUEST_URI'];
  217. @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
  218. // We weren't handed any parameters, so let's find the ones relevant to
  219. // this request.
  220. // If you run XML-RPC or similar you should use this to provide your own
  221. // parsed parameter-list
  222. if (!$parameters) {
  223. // Find request headers
  224. $request_headers = OAuthUtil::get_headers();
  225. // Parse the query-string to find GET parameters
  226. $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
  227. // It's a POST request of the proper content-type, so parse POST
  228. // parameters and add those overriding any duplicates from GET
  229. if ($http_method == "POST"
  230. && @strstr($request_headers["Content-Type"],
  231. "application/x-www-form-urlencoded")
  232. ) {
  233. $post_data = OAuthUtil::parse_parameters(
  234. file_get_contents(self::$POST_INPUT)
  235. );
  236. $parameters = array_merge($parameters, $post_data);
  237. }
  238. // We have a Authorization-header with OAuth data. Parse the header
  239. // and add those overriding any duplicates from GET or POST
  240. if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
  241. $header_parameters = OAuthUtil::split_header(
  242. $request_headers['Authorization']
  243. );
  244. $parameters = array_merge($parameters, $header_parameters);
  245. }
  246. }
  247. // fix for friendica redirect system
  248. $http_url = substr($http_url, 0, strpos($http_url,$parameters['pagename'])+strlen($parameters['pagename']));
  249. unset( $parameters['pagename'] );
  250. //echo "<pre>".__function__."\n"; var_dump($http_method, $http_url, $parameters, $_SERVER['REQUEST_URI']); killme();
  251. return new OAuthRequest($http_method, $http_url, $parameters);
  252. }
  253. /**
  254. * pretty much a helper function to set up the request
  255. */
  256. public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
  257. @$parameters or $parameters = array();
  258. $defaults = array("oauth_version" => OAuthRequest::$version,
  259. "oauth_nonce" => OAuthRequest::generate_nonce(),
  260. "oauth_timestamp" => OAuthRequest::generate_timestamp(),
  261. "oauth_consumer_key" => $consumer->key);
  262. if ($token)
  263. $defaults['oauth_token'] = $token->key;
  264. $parameters = array_merge($defaults, $parameters);
  265. return new OAuthRequest($http_method, $http_url, $parameters);
  266. }
  267. public function set_parameter($name, $value, $allow_duplicates = true) {
  268. if ($allow_duplicates && isset($this->parameters[$name])) {
  269. // We have already added parameter(s) with this name, so add to the list
  270. if (is_scalar($this->parameters[$name])) {
  271. // This is the first duplicate, so transform scalar (string)
  272. // into an array so we can add the duplicates
  273. $this->parameters[$name] = array($this->parameters[$name]);
  274. }
  275. $this->parameters[$name][] = $value;
  276. } else {
  277. $this->parameters[$name] = $value;
  278. }
  279. }
  280. public function get_parameter($name) {
  281. return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
  282. }
  283. public function get_parameters() {
  284. return $this->parameters;
  285. }
  286. public function unset_parameter($name) {
  287. unset($this->parameters[$name]);
  288. }
  289. /**
  290. * The request parameters, sorted and concatenated into a normalized string.
  291. * @return string
  292. */
  293. public function get_signable_parameters() {
  294. // Grab all parameters
  295. $params = $this->parameters;
  296. // Remove oauth_signature if present
  297. // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
  298. if (isset($params['oauth_signature'])) {
  299. unset($params['oauth_signature']);
  300. }
  301. return OAuthUtil::build_http_query($params);
  302. }
  303. /**
  304. * Returns the base string of this request
  305. *
  306. * The base string defined as the method, the url
  307. * and the parameters (normalized), each urlencoded
  308. * and the concated with &.
  309. */
  310. public function get_signature_base_string() {
  311. $parts = array(
  312. $this->get_normalized_http_method(),
  313. $this->get_normalized_http_url(),
  314. $this->get_signable_parameters()
  315. );
  316. $parts = OAuthUtil::urlencode_rfc3986($parts);
  317. return implode('&', $parts);
  318. }
  319. /**
  320. * just uppercases the http method
  321. */
  322. public function get_normalized_http_method() {
  323. return strtoupper($this->http_method);
  324. }
  325. /**
  326. * parses the url and rebuilds it to be
  327. * scheme://host/path
  328. */
  329. public function get_normalized_http_url() {
  330. $parts = parse_url($this->http_url);
  331. $port = @$parts['port'];
  332. $scheme = $parts['scheme'];
  333. $host = $parts['host'];
  334. $path = @$parts['path'];
  335. $port or $port = ($scheme == 'https') ? '443' : '80';
  336. if (($scheme == 'https' && $port != '443')
  337. || ($scheme == 'http' && $port != '80')) {
  338. $host = "$host:$port";
  339. }
  340. return "$scheme://$host$path";
  341. }
  342. /**
  343. * builds a url usable for a GET request
  344. */
  345. public function to_url() {
  346. $post_data = $this->to_postdata();
  347. $out = $this->get_normalized_http_url();
  348. if ($post_data) {
  349. $out .= '?'.$post_data;
  350. }
  351. return $out;
  352. }
  353. /**
  354. * builds the data one would send in a POST request
  355. */
  356. public function to_postdata($raw = false) {
  357. if ($raw)
  358. return($this->parameters);
  359. else
  360. return OAuthUtil::build_http_query($this->parameters);
  361. }
  362. /**
  363. * builds the Authorization: header
  364. */
  365. public function to_header($realm=null) {
  366. $first = true;
  367. if($realm) {
  368. $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
  369. $first = false;
  370. } else
  371. $out = 'Authorization: OAuth';
  372. $total = array();
  373. foreach ($this->parameters as $k => $v) {
  374. if (substr($k, 0, 5) != "oauth") continue;
  375. if (is_array($v)) {
  376. throw new OAuthException('Arrays not supported in headers');
  377. }
  378. $out .= ($first) ? ' ' : ',';
  379. $out .= OAuthUtil::urlencode_rfc3986($k) .
  380. '="' .
  381. OAuthUtil::urlencode_rfc3986($v) .
  382. '"';
  383. $first = false;
  384. }
  385. return $out;
  386. }
  387. public function __toString() {
  388. return $this->to_url();
  389. }
  390. public function sign_request($signature_method, $consumer, $token) {
  391. $this->set_parameter(
  392. "oauth_signature_method",
  393. $signature_method->get_name(),
  394. false
  395. );
  396. $signature = $this->build_signature($signature_method, $consumer, $token);
  397. $this->set_parameter("oauth_signature", $signature, false);
  398. }
  399. public function build_signature($signature_method, $consumer, $token) {
  400. $signature = $signature_method->build_signature($this, $consumer, $token);
  401. return $signature;
  402. }
  403. /**
  404. * util function: current timestamp
  405. */
  406. private static function generate_timestamp() {
  407. return time();
  408. }
  409. /**
  410. * util function: current nonce
  411. */
  412. private static function generate_nonce() {
  413. $mt = microtime();
  414. $rand = mt_rand();
  415. return md5($mt . $rand); // md5s look nicer than numbers
  416. }
  417. }
  418. class OAuthServer {
  419. protected $timestamp_threshold = 300; // in seconds, five minutes
  420. protected $version = '1.0'; // hi blaine
  421. protected $signature_methods = array();
  422. protected $data_store;
  423. function __construct($data_store) {
  424. $this->data_store = $data_store;
  425. }
  426. public function add_signature_method($signature_method) {
  427. $this->signature_methods[$signature_method->get_name()] =
  428. $signature_method;
  429. }
  430. // high level functions
  431. /**
  432. * process a request_token request
  433. * returns the request token on success
  434. */
  435. public function fetch_request_token(&$request) {
  436. $this->get_version($request);
  437. $consumer = $this->get_consumer($request);
  438. // no token required for the initial token request
  439. $token = NULL;
  440. $this->check_signature($request, $consumer, $token);
  441. // Rev A change
  442. $callback = $request->get_parameter('oauth_callback');
  443. $new_token = $this->data_store->new_request_token($consumer, $callback);
  444. return $new_token;
  445. }
  446. /**
  447. * process an access_token request
  448. * returns the access token on success
  449. */
  450. public function fetch_access_token(&$request) {
  451. $this->get_version($request);
  452. $consumer = $this->get_consumer($request);
  453. // requires authorized request token
  454. $token = $this->get_token($request, $consumer, "request");
  455. $this->check_signature($request, $consumer, $token);
  456. // Rev A change
  457. $verifier = $request->get_parameter('oauth_verifier');
  458. $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
  459. return $new_token;
  460. }
  461. /**
  462. * verify an api call, checks all the parameters
  463. */
  464. public function verify_request(&$request) {
  465. $this->get_version($request);
  466. $consumer = $this->get_consumer($request);
  467. //echo __file__.__line__.__function__."<pre>"; var_dump($consumer); die();
  468. $token = $this->get_token($request, $consumer, "access");
  469. $this->check_signature($request, $consumer, $token);
  470. return array($consumer, $token);
  471. }
  472. // Internals from here
  473. /**
  474. * version 1
  475. */
  476. private function get_version(&$request) {
  477. $version = $request->get_parameter("oauth_version");
  478. if (!$version) {
  479. // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
  480. // Chapter 7.0 ("Accessing Protected Ressources")
  481. $version = '1.0';
  482. }
  483. if ($version !== $this->version) {
  484. throw new OAuthException("OAuth version '$version' not supported");
  485. }
  486. return $version;
  487. }
  488. /**
  489. * figure out the signature with some defaults
  490. */
  491. private function get_signature_method(&$request) {
  492. $signature_method =
  493. @$request->get_parameter("oauth_signature_method");
  494. if (!$signature_method) {
  495. // According to chapter 7 ("Accessing Protected Ressources") the signature-method
  496. // parameter is required, and we can't just fallback to PLAINTEXT
  497. throw new OAuthException('No signature method parameter. This parameter is required');
  498. }
  499. if (!in_array($signature_method,
  500. array_keys($this->signature_methods))) {
  501. throw new OAuthException(
  502. "Signature method '$signature_method' not supported " .
  503. "try one of the following: " .
  504. implode(", ", array_keys($this->signature_methods))
  505. );
  506. }
  507. return $this->signature_methods[$signature_method];
  508. }
  509. /**
  510. * try to find the consumer for the provided request's consumer key
  511. */
  512. private function get_consumer(&$request) {
  513. $consumer_key = @$request->get_parameter("oauth_consumer_key");
  514. if (!$consumer_key) {
  515. throw new OAuthException("Invalid consumer key");
  516. }
  517. $consumer = $this->data_store->lookup_consumer($consumer_key);
  518. if (!$consumer) {
  519. throw new OAuthException("Invalid consumer");
  520. }
  521. return $consumer;
  522. }
  523. /**
  524. * try to find the token for the provided request's token key
  525. */
  526. private function get_token(&$request, $consumer, $token_type="access") {
  527. $token_field = @$request->get_parameter('oauth_token');
  528. $token = $this->data_store->lookup_token(
  529. $consumer, $token_type, $token_field
  530. );
  531. if (!$token) {
  532. throw new OAuthException("Invalid $token_type token: $token_field");
  533. }
  534. return $token;
  535. }
  536. /**
  537. * all-in-one function to check the signature on a request
  538. * should guess the signature method appropriately
  539. */
  540. private function check_signature(&$request, $consumer, $token) {
  541. // this should probably be in a different method
  542. $timestamp = @$request->get_parameter('oauth_timestamp');
  543. $nonce = @$request->get_parameter('oauth_nonce');
  544. $this->check_timestamp($timestamp);
  545. $this->check_nonce($consumer, $token, $nonce, $timestamp);
  546. $signature_method = $this->get_signature_method($request);
  547. $signature = $request->get_parameter('oauth_signature');
  548. $valid_sig = $signature_method->check_signature(
  549. $request,
  550. $consumer,
  551. $token,
  552. $signature
  553. );
  554. if (!$valid_sig) {
  555. throw new OAuthException("Invalid signature");
  556. }
  557. }
  558. /**
  559. * check that the timestamp is new enough
  560. */
  561. private function check_timestamp($timestamp) {
  562. if( ! $timestamp )
  563. throw new OAuthException(
  564. 'Missing timestamp parameter. The parameter is required'
  565. );
  566. // verify that timestamp is recentish
  567. $now = time();
  568. if (abs($now - $timestamp) > $this->timestamp_threshold) {
  569. throw new OAuthException(
  570. "Expired timestamp, yours $timestamp, ours $now"
  571. );
  572. }
  573. }
  574. /**
  575. * check that the nonce is not repeated
  576. */
  577. private function check_nonce($consumer, $token, $nonce, $timestamp) {
  578. if( ! $nonce )
  579. throw new OAuthException(
  580. 'Missing nonce parameter. The parameter is required'
  581. );
  582. // verify that the nonce is uniqueish
  583. $found = $this->data_store->lookup_nonce(
  584. $consumer,
  585. $token,
  586. $nonce,
  587. $timestamp
  588. );
  589. if ($found) {
  590. throw new OAuthException("Nonce already used: $nonce");
  591. }
  592. }
  593. }
  594. class OAuthDataStore {
  595. function lookup_consumer($consumer_key) {
  596. // implement me
  597. }
  598. function lookup_token($consumer, $token_type, $token) {
  599. // implement me
  600. }
  601. function lookup_nonce($consumer, $token, $nonce, $timestamp) {
  602. // implement me
  603. }
  604. function new_request_token($consumer, $callback = null) {
  605. // return a new token attached to this consumer
  606. }
  607. function new_access_token($token, $consumer, $verifier = null) {
  608. // return a new access token attached to this consumer
  609. // for the user associated with this token if the request token
  610. // is authorized
  611. // should also invalidate the request token
  612. }
  613. }
  614. class OAuthUtil {
  615. public static function urlencode_rfc3986($input) {
  616. if (is_array($input)) {
  617. return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
  618. } else if (is_scalar($input)) {
  619. return str_replace(
  620. '+',
  621. ' ',
  622. str_replace('%7E', '~', rawurlencode($input))
  623. );
  624. } else {
  625. return '';
  626. }
  627. }
  628. // This decode function isn't taking into consideration the above
  629. // modifications to the encoding process. However, this method doesn't
  630. // seem to be used anywhere so leaving it as is.
  631. public static function urldecode_rfc3986($string) {
  632. return urldecode($string);
  633. }
  634. // Utility function for turning the Authorization: header into
  635. // parameters, has to do some unescaping
  636. // Can filter out any non-oauth parameters if needed (default behaviour)
  637. public static function split_header($header, $only_allow_oauth_parameters = true) {
  638. $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
  639. $offset = 0;
  640. $params = array();
  641. while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
  642. $match = $matches[0];
  643. $header_name = $matches[2][0];
  644. $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
  645. if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
  646. $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
  647. }
  648. $offset = $match[1] + strlen($match[0]);
  649. }
  650. if (isset($params['realm'])) {
  651. unset($params['realm']);
  652. }
  653. return $params;
  654. }
  655. // helper to try to sort out headers for people who aren't running apache
  656. public static function get_headers() {
  657. if (function_exists('apache_request_headers')) {
  658. // we need this to get the actual Authorization: header
  659. // because apache tends to tell us it doesn't exist
  660. $headers = apache_request_headers();
  661. // sanitize the output of apache_request_headers because
  662. // we always want the keys to be Cased-Like-This and arh()
  663. // returns the headers in the same case as they are in the
  664. // request
  665. $out = array();
  666. foreach( $headers AS $key => $value ) {
  667. $key = str_replace(
  668. " ",
  669. "-",
  670. ucwords(strtolower(str_replace("-", " ", $key)))
  671. );
  672. $out[$key] = $value;
  673. }
  674. } else {
  675. // otherwise we don't have apache and are just going to have to hope
  676. // that $_SERVER actually contains what we need
  677. $out = array();
  678. if( isset($_SERVER['CONTENT_TYPE']) )
  679. $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
  680. if( isset($_ENV['CONTENT_TYPE']) )
  681. $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
  682. foreach ($_SERVER as $key => $value) {
  683. if (substr($key, 0, 5) == "HTTP_") {
  684. // this is chaos, basically it is just there to capitalize the first
  685. // letter of every word that is not an initial HTTP and strip HTTP
  686. // code from przemek
  687. $key = str_replace(
  688. " ",
  689. "-",
  690. ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
  691. );
  692. $out[$key] = $value;
  693. }
  694. }
  695. }
  696. return $out;
  697. }
  698. // This function takes a input like a=b&a=c&d=e and returns the parsed
  699. // parameters like this
  700. // array('a' => array('b','c'), 'd' => 'e')
  701. public static function parse_parameters( $input ) {
  702. if (!isset($input) || !$input) return array();
  703. $pairs = explode('&', $input);
  704. $parsed_parameters = array();
  705. foreach ($pairs as $pair) {
  706. $split = explode('=', $pair, 2);
  707. $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
  708. $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
  709. if (isset($parsed_parameters[$parameter])) {
  710. // We have already recieved parameter(s) with this name, so add to the list
  711. // of parameters with this name
  712. if (is_scalar($parsed_parameters[$parameter])) {
  713. // This is the first duplicate, so transform scalar (string) into an array
  714. // so we can add the duplicates
  715. $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
  716. }
  717. $parsed_parameters[$parameter][] = $value;
  718. } else {
  719. $parsed_parameters[$parameter] = $value;
  720. }
  721. }
  722. return $parsed_parameters;
  723. }
  724. public static function build_http_query($params) {
  725. if (!$params) return '';
  726. // Urlencode both keys and values
  727. $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
  728. $values = OAuthUtil::urlencode_rfc3986(array_values($params));
  729. $params = array_combine($keys, $values);
  730. // Parameters are sorted by name, using lexicographical byte value ordering.
  731. // Ref: Spec: 9.1.1 (1)
  732. uksort($params, 'strcmp');
  733. $pairs = array();
  734. foreach ($params as $parameter => $value) {
  735. if (is_array($value)) {
  736. // If two or more parameters share the same name, they are sorted by their value
  737. // Ref: Spec: 9.1.1 (1)
  738. natsort($value);
  739. foreach ($value as $duplicate_value) {
  740. $pairs[] = $parameter . '=' . $duplicate_value;
  741. }
  742. } else {
  743. $pairs[] = $parameter . '=' . $value;
  744. }
  745. }
  746. // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
  747. // Each name-value pair is separated by an '&' character (ASCII code 38)
  748. return implode('&', $pairs);
  749. }
  750. }
  751. ?>