OAuth.php 26 KB

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