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