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