PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/addons/plugin/Login/lib/qq/oauth.php

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