OAuth.php 23 KB

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