OAuth.php 26.9 KB
Newer Older
1 2 3 4 5
<?php
// vim: foldmethod=marker

/* Generic exception class
 */
6
if (!class_exists('OAuthException')) {
7 8 9
  class OAuthException extends Exception {
    // pass
  }
10
}
11

12
class OAuthConsumer {
13 14 15
  public $key;
  public $secret;

16
  function __construct($key, $secret, $callback_url=NULL) {
17 18 19
    $this->key = $key;
    $this->secret = $secret;
    $this->callback_url = $callback_url;
20
  }
21

22
  function __toString() {
23
    return "OAuthConsumer[key=$this->key,secret=$this->secret]";
24 25
  }
}
26

27
class OAuthToken {
28 29 30 31 32 33 34 35
  // access tokens and request tokens
  public $key;
  public $secret;

  /**
   * key = the token
   * secret = the token secret
   */
36
  function __construct($key, $secret) {
37 38
    $this->key = $key;
    $this->secret = $secret;
39
  }
40 41 42 43 44

  /**
   * generates the basic string serialization of a token that a server
   * would respond to request_token and access_token calls with
   */
45 46 47 48 49 50
  function to_string() {
    return "oauth_token=" .
           OAuthUtil::urlencode_rfc3986($this->key) .
           "&oauth_token_secret=" .
           OAuthUtil::urlencode_rfc3986($this->secret);
  }
51

52
  function __toString() {
53
    return $this->to_string();
54 55
  }
}
56

57 58 59 60 61 62 63 64 65 66
/**
 * A class for implementing a Signature Method
 * See section 9 ("Signing Requests") in the spec
 */
abstract class OAuthSignatureMethod {
  /**
   * Needs to return the name of the Signature Method (ie HMAC-SHA1)
   * @return string
   */
  abstract public function get_name();
67

68 69 70 71 72 73 74 75 76 77 78
  /**
   * Build up the signature
   * NOTE: The output of this function MUST NOT be urlencoded.
   * the encoding is handled in OAuthRequest when the final
   * request is serialized
   * @param OAuthRequest $request
   * @param OAuthConsumer $consumer
   * @param OAuthToken $token
   * @return string
   */
  abstract public function build_signature($request, $consumer, $token);
79

80 81 82 83 84 85 86 87 88 89
  /**
   * Verifies that a given signature is correct
   * @param OAuthRequest $request
   * @param OAuthConsumer $consumer
   * @param OAuthToken $token
   * @param string $signature
   * @return bool
   */
  public function check_signature($request, $consumer, $token, $signature) {
    $built = $this->build_signature($request, $consumer, $token);
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106

    // Check for zero length, although unlikely here
    if (strlen($built) == 0 || strlen($signature) == 0) {
      return false;
    }

    if (strlen($built) != strlen($signature)) {
      return false;
    }

    // Avoid a timing leak with a (hopefully) time insensitive compare
    $result = 0;
    for ($i = 0; $i < strlen($signature); $i++) {
      $result |= ord($built{$i}) ^ ord($signature{$i});
    }

    return $result == 0;
107
  }
108 109 110 111 112 113 114 115 116 117 118
}

/**
 * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 
 * where the Signature Base String is the text and the key is the concatenated values (each first 
 * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 
 * character (ASCII code 38) even if empty.
 *   - Chapter 9.2 ("HMAC-SHA1")
 */
class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
  function get_name() {
119
    return "HMAC-SHA1";
120
  }
121

122
  public function build_signature($request, $consumer, $token) {
123 124 125 126 127 128 129 130
    $base_string = $request->get_signature_base_string();
    $request->base_string = $base_string;

    $key_parts = array(
      $consumer->secret,
      ($token) ? $token->secret : ""
    );

131
    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
132 133
    $key = implode('&', $key_parts);

134 135 136
    return base64_encode(hash_hmac('sha1', $base_string, $key, true));
  }
}
137

138 139 140 141 142 143 144
/**
 * The PLAINTEXT method does not provide any security protection and SHOULD only be used 
 * over a secure channel such as HTTPS. It does not use the Signature Base String.
 *   - Chapter 9.4 ("PLAINTEXT")
 */
class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
  public function get_name() {
145
    return "PLAINTEXT";
146
  }
147

148 149 150 151 152 153 154 155 156 157 158 159 160
  /**
   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 
   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 
   * empty. The result MUST be encoded again.
   *   - Chapter 9.4.1 ("Generating Signatures")
   *
   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
   * OAuthRequest handles this!
   */
  public function build_signature($request, $consumer, $token) {
    $key_parts = array(
      $consumer->secret,
      ($token) ? $token->secret : ""
161 162
    );

163 164 165
    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
    $key = implode('&', $key_parts);
    $request->base_string = $key;
166

167 168 169 170 171 172 173 174 175 176 177 178 179 180
    return $key;
  }
}

/**
 * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 
 * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 
 * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 
 * verified way to the Service Provider, in a manner which is beyond the scope of this 
 * specification.
 *   - Chapter 9.3 ("RSA-SHA1")
 */
abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
  public function get_name() {
181
    return "RSA-SHA1";
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
  }

  // Up to the SP to implement this lookup of keys. Possible ideas are:
  // (1) do a lookup in a table of trusted certs keyed off of consumer
  // (2) fetch via http using a url provided by the requester
  // (3) some sort of specific discovery code based on request
  //
  // Either way should return a string representation of the certificate
  protected abstract function fetch_public_cert(&$request);

  // Up to the SP to implement this lookup of keys. Possible ideas are:
  // (1) do a lookup in a table of trusted certs keyed off of consumer
  //
  // Either way should return a string representation of the certificate
  protected abstract function fetch_private_cert(&$request);

  public function build_signature($request, $consumer, $token) {
199 200
    $base_string = $request->get_signature_base_string();
    $request->base_string = $base_string;
201

202 203 204 205 206 207 208
    // Fetch the private key cert based on the request
    $cert = $this->fetch_private_cert($request);

    // Pull the private key ID from the certificate
    $privatekeyid = openssl_get_privatekey($cert);

    // Sign using the key
209
    $ok = openssl_sign($base_string, $signature, $privatekeyid);
210 211 212

    // Release the key resource
    openssl_free_key($privatekeyid);
213

214
    return base64_encode($signature);
215
  }
216

217
  public function check_signature($request, $consumer, $token, $signature) {
218 219 220
    $decoded_sig = base64_decode($signature);

    $base_string = $request->get_signature_base_string();
221

222 223 224 225 226 227 228
    // Fetch the public key cert based on the request
    $cert = $this->fetch_public_cert($request);

    // Pull the public key ID from the certificate
    $publickeyid = openssl_get_publickey($cert);

    // Check the computed signature against the one passed in the query
229
    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
230 231 232

    // Release the key resource
    openssl_free_key($publickeyid);
233

234
    return $ok == 1;
235 236
  }
}
237

238 239 240 241
class OAuthRequest {
  protected $parameters;
  protected $http_method;
  protected $http_url;
242 243 244
  // for debug purposes
  public $base_string;
  public static $version = '1.0';
245
  public static $POST_INPUT = 'php://input';
246

247 248 249
  function __construct($http_method, $http_url, $parameters=NULL) {
    $parameters = ($parameters) ? $parameters : array();
    $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
250 251 252
    $this->parameters = $parameters;
    $this->http_method = $http_method;
    $this->http_url = $http_url;
253
  }
254 255 256 257 258


  /**
   * attempt to build up a request from what was passed to the server
   */
259 260 261 262 263
  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
              ? 'http'
              : 'https';
    $http_url = ($http_url) ? $http_url : $scheme .
264
                              '://' . $_SERVER['SERVER_NAME'] .
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
                              ':' .
                              $_SERVER['SERVER_PORT'] .
                              $_SERVER['REQUEST_URI'];
    $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD'];

    // We weren't handed any parameters, so let's find the ones relevant to
    // this request.
    // If you run XML-RPC or similar you should use this to provide your own
    // parsed parameter-list
    if (!$parameters) {
      // Find request headers
      $request_headers = OAuthUtil::get_headers();

      // Parse the query-string to find GET parameters
      $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);

      // It's a POST request of the proper content-type, so parse POST
      // parameters and add those overriding any duplicates from GET
      if ($http_method == "POST"
          &&  isset($request_headers['Content-Type'])
          && strstr($request_headers['Content-Type'],
                     'application/x-www-form-urlencoded')
          ) {
        $post_data = OAuthUtil::parse_parameters(
          file_get_contents(self::$POST_INPUT)
        );
        $parameters = array_merge($parameters, $post_data);
      }
293

294 295 296 297 298 299 300
      // We have a Authorization-header with OAuth data. Parse the header
      // and add those overriding any duplicates from GET or POST
      if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
        $header_parameters = OAuthUtil::split_header(
          $request_headers['Authorization']
        );
        $parameters = array_merge($parameters, $header_parameters);
301 302
      }

303
    }
304

305 306
    return new OAuthRequest($http_method, $http_url, $parameters);
  }
307 308 309 310

  /**
   * pretty much a helper function to set up the request
   */
311 312
  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
    $parameters = ($parameters) ?  $parameters : array();
313 314 315 316
    $defaults = array("oauth_version" => OAuthRequest::$version,
                      "oauth_nonce" => OAuthRequest::generate_nonce(),
                      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
                      "oauth_consumer_key" => $consumer->key);
317 318 319
    if ($token)
      $defaults['oauth_token'] = $token->key;

320 321 322
    $parameters = array_merge($defaults, $parameters);

    return new OAuthRequest($http_method, $http_url, $parameters);
323
  }
324

325 326 327 328 329 330 331 332
  public function set_parameter($name, $value, $allow_duplicates = true) {
    if ($allow_duplicates && isset($this->parameters[$name])) {
      // We have already added parameter(s) with this name, so add to the list
      if (is_scalar($this->parameters[$name])) {
        // This is the first duplicate, so transform scalar (string)
        // into an array so we can add the duplicates
        $this->parameters[$name] = array($this->parameters[$name]);
      }
333

334 335 336 337 338 339 340
      $this->parameters[$name][] = $value;
    } else {
      $this->parameters[$name] = $value;
    }
  }

  public function get_parameter($name) {
341
    return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
342
  }
343

344
  public function get_parameters() {
345
    return $this->parameters;
346 347 348 349 350
  }

  public function unset_parameter($name) {
    unset($this->parameters[$name]);
  }
351 352

  /**
353
   * The request parameters, sorted and concatenated into a normalized string.
354 355
   * @return string
   */
356
  public function get_signable_parameters() {
357 358
    // Grab all parameters
    $params = $this->parameters;
359

360
    // Remove oauth_signature if present
361
    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
362 363 364 365
    if (isset($params['oauth_signature'])) {
      unset($params['oauth_signature']);
    }

366 367
    return OAuthUtil::build_http_query($params);
  }
368 369 370 371 372 373 374 375

  /**
   * Returns the base string of this request
   *
   * The base string defined as the method, the url
   * and the parameters (normalized), each urlencoded
   * and the concated with &.
   */
376
  public function get_signature_base_string() {
377 378 379 380 381 382
    $parts = array(
      $this->get_normalized_http_method(),
      $this->get_normalized_http_url(),
      $this->get_signable_parameters()
    );

383
    $parts = OAuthUtil::urlencode_rfc3986($parts);
384 385

    return implode('&', $parts);
386
  }
387 388 389 390

  /**
   * just uppercases the http method
   */
391
  public function get_normalized_http_method() {
392
    return strtoupper($this->http_method);
393
  }
394 395 396 397 398

  /**
   * parses the url and rebuilds it to be
   * scheme://host/path
   */
399
  public function get_normalized_http_url() {
400 401
    $parts = parse_url($this->http_url);

402 403
    $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
    $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80');
404
    $host = (isset($parts['host'])) ? strtolower($parts['host']) : '';
405
    $path = (isset($parts['path'])) ? $parts['path'] : '';
406 407 408 409 410 411

    if (($scheme == 'https' && $port != '443')
        || ($scheme == 'http' && $port != '80')) {
      $host = "$host:$port";
    }
    return "$scheme://$host$path";
412
  }
413 414 415 416

  /**
   * builds a url usable for a GET request
   */
417 418 419 420 421 422
  public function to_url() {
    $post_data = $this->to_postdata();
    $out = $this->get_normalized_http_url();
    if ($post_data) {
      $out .= '?'.$post_data;
    }
423
    return $out;
424
  }
425 426 427 428

  /**
   * builds the data one would send in a POST request
   */
429 430 431
  public function to_postdata() {
    return OAuthUtil::build_http_query($this->parameters);
  }
432 433 434 435

  /**
   * builds the Authorization: header
   */
436 437 438 439 440 441 442 443
  public function to_header($realm=null) {
    $first = true;
	if($realm) {
      $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
      $first = false;
    } else
      $out = 'Authorization: OAuth';

444 445 446
    $total = array();
    foreach ($this->parameters as $k => $v) {
      if (substr($k, 0, 5) != "oauth") continue;
447 448 449 450 451 452 453 454 455
      if (is_array($v)) {
        throw new OAuthException('Arrays not supported in headers');
      }
      $out .= ($first) ? ' ' : ',';
      $out .= OAuthUtil::urlencode_rfc3986($k) .
              '="' .
              OAuthUtil::urlencode_rfc3986($v) .
              '"';
      $first = false;
456 457
    }
    return $out;
458
  }
459

460
  public function __toString() {
461
    return $this->to_url();
462
  }
463 464


465 466 467 468 469 470
  public function sign_request($signature_method, $consumer, $token) {
    $this->set_parameter(
      "oauth_signature_method",
      $signature_method->get_name(),
      false
    );
471
    $signature = $this->build_signature($signature_method, $consumer, $token);
472 473
    $this->set_parameter("oauth_signature", $signature, false);
  }
474

475
  public function build_signature($signature_method, $consumer, $token) {
476 477
    $signature = $signature_method->build_signature($this, $consumer, $token);
    return $signature;
478
  }
479 480 481 482

  /**
   * util function: current timestamp
   */
483
  private static function generate_timestamp() {
484
    return time();
485
  }
486 487 488 489

  /**
   * util function: current nonce
   */
490
  private static function generate_nonce() {
491 492 493 494
    $mt = microtime();
    $rand = mt_rand();

    return md5($mt . $rand); // md5s look nicer than numbers
495 496
  }
}
497

498
class OAuthServer {
499
  protected $timestamp_threshold = 300; // in seconds, five minutes
500
  protected $version = '1.0';             // hi blaine
501 502 503 504
  protected $signature_methods = array();

  protected $data_store;

505
  function __construct($data_store) {
506
    $this->data_store = $data_store;
507 508 509 510 511 512
  }

  public function add_signature_method($signature_method) {
    $this->signature_methods[$signature_method->get_name()] =
      $signature_method;
  }
513 514 515 516 517 518 519

  // high level functions

  /**
   * process a request_token request
   * returns the request token on success
   */
520
  public function fetch_request_token(&$request) {
521 522 523 524 525 526 527 528 529
    $this->get_version($request);

    $consumer = $this->get_consumer($request);

    // no token required for the initial token request
    $token = NULL;

    $this->check_signature($request, $consumer, $token);

530 531 532
    // Rev A change
    $callback = $request->get_parameter('oauth_callback');
    $new_token = $this->data_store->new_request_token($consumer, $callback);
533 534

    return $new_token;
535
  }
536 537 538 539 540

  /**
   * process an access_token request
   * returns the access token on success
   */
541
  public function fetch_access_token(&$request) {
542 543 544 545 546 547 548 549 550
    $this->get_version($request);

    $consumer = $this->get_consumer($request);

    // requires authorized request token
    $token = $this->get_token($request, $consumer, "request");

    $this->check_signature($request, $consumer, $token);

551 552 553
    // Rev A change
    $verifier = $request->get_parameter('oauth_verifier');
    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
554 555

    return $new_token;
556
  }
557 558 559 560

  /**
   * verify an api call, checks all the parameters
   */
561
  public function verify_request(&$request) {
562 563 564 565 566
    $this->get_version($request);
    $consumer = $this->get_consumer($request);
    $token = $this->get_token($request, $consumer, "access");
    $this->check_signature($request, $consumer, $token);
    return array($consumer, $token);
567
  }
568 569 570 571 572

  // Internals from here
  /**
   * version 1
   */
573
  private function get_version(&$request) {
574 575
    $version = $request->get_parameter("oauth_version");
    if (!$version) {
576 577 578
      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 
      // Chapter 7.0 ("Accessing Protected Ressources")
      $version = '1.0';
579
    }
580
    if ($version !== $this->version) {
581 582 583
      throw new OAuthException("OAuth version '$version' not supported");
    }
    return $version;
584
  }
585 586 587 588

  /**
   * figure out the signature with some defaults
   */
589 590 591 592 593
  private function get_signature_method($request) {
    $signature_method = $request instanceof OAuthRequest 
        ? $request->get_parameter("oauth_signature_method")
        : NULL;

594
    if (!$signature_method) {
595 596 597
      // According to chapter 7 ("Accessing Protected Ressources") the signature-method
      // parameter is required, and we can't just fallback to PLAINTEXT
      throw new OAuthException('No signature method parameter. This parameter is required');
598
    }
599 600

    if (!in_array($signature_method,
601 602
                  array_keys($this->signature_methods))) {
      throw new OAuthException(
603 604 605 606
        "Signature method '$signature_method' not supported " .
        "try one of the following: " .
        implode(", ", array_keys($this->signature_methods))
      );
607 608
    }
    return $this->signature_methods[$signature_method];
609
  }
610 611 612 613

  /**
   * try to find the consumer for the provided request's consumer key
   */
614 615 616 617 618
  private function get_consumer($request) {
    $consumer_key = $request instanceof OAuthRequest 
        ? $request->get_parameter("oauth_consumer_key")
        : NULL;

619 620 621 622 623 624 625 626 627 628
    if (!$consumer_key) {
      throw new OAuthException("Invalid consumer key");
    }

    $consumer = $this->data_store->lookup_consumer($consumer_key);
    if (!$consumer) {
      throw new OAuthException("Invalid consumer");
    }

    return $consumer;
629
  }
630 631 632 633

  /**
   * try to find the token for the provided request's token key
   */
634 635 636 637 638
  private function get_token($request, $consumer, $token_type="access") {
    $token_field = $request instanceof OAuthRequest
         ? $request->get_parameter('oauth_token')
         : NULL;

639 640 641 642 643 644 645
    $token = $this->data_store->lookup_token(
      $consumer, $token_type, $token_field
    );
    if (!$token) {
      throw new OAuthException("Invalid $token_type token: $token_field");
    }
    return $token;
646
  }
647 648 649 650 651

  /**
   * all-in-one function to check the signature on a request
   * should guess the signature method appropriately
   */
652
  private function check_signature($request, $consumer, $token) {
653
    // this should probably be in a different method
654 655 656 657 658 659
    $timestamp = $request instanceof OAuthRequest
        ? $request->get_parameter('oauth_timestamp')
        : NULL;
    $nonce = $request instanceof OAuthRequest
        ? $request->get_parameter('oauth_nonce')
        : NULL;
660 661 662 663 664 665

    $this->check_timestamp($timestamp);
    $this->check_nonce($consumer, $token, $nonce, $timestamp);

    $signature_method = $this->get_signature_method($request);

666
    $signature = $request->get_parameter('oauth_signature');
667
    $valid_sig = $signature_method->check_signature(
668 669 670
      $request,
      $consumer,
      $token,
671 672 673 674 675 676
      $signature
    );

    if (!$valid_sig) {
      throw new OAuthException("Invalid signature");
    }
677
  }
678 679 680 681

  /**
   * check that the timestamp is new enough
   */
682 683 684 685 686 687
  private function check_timestamp($timestamp) {
    if( ! $timestamp )
      throw new OAuthException(
        'Missing timestamp parameter. The parameter is required'
      );
    
688 689
    // verify that timestamp is recentish
    $now = time();
690 691 692 693
    if (abs($now - $timestamp) > $this->timestamp_threshold) {
      throw new OAuthException(
        "Expired timestamp, yours $timestamp, ours $now"
      );
694
    }
695
  }
696 697 698 699

  /**
   * check that the nonce is not repeated
   */
700 701 702 703 704 705
  private function check_nonce($consumer, $token, $nonce, $timestamp) {
    if( ! $nonce )
      throw new OAuthException(
        'Missing nonce parameter. The parameter is required'
      );

706
    // verify that the nonce is uniqueish
707 708 709 710 711 712
    $found = $this->data_store->lookup_nonce(
      $consumer,
      $token,
      $nonce,
      $timestamp
    );
713 714 715
    if ($found) {
      throw new OAuthException("Nonce already used: $nonce");
    }
716
  }
717

718
}
719

720 721
class OAuthDataStore {
  function lookup_consumer($consumer_key) {
722
    // implement me
723
  }
724

725
  function lookup_token($consumer, $token_type, $token) {
726
    // implement me
727
  }
728

729
  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
730
    // implement me
731
  }
732

733
  function new_request_token($consumer, $callback = null) {
734
    // return a new token attached to this consumer
735
  }
736

737
  function new_access_token($token, $consumer, $verifier = null) {
738 739 740 741
    // return a new access token attached to this consumer
    // for the user associated with this token if the request token
    // is authorized
    // should also invalidate the request token
742
  }
743

744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
}

class OAuthUtil {
  public static function urlencode_rfc3986($input) {
  if (is_array($input)) {
    return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
  } else if (is_scalar($input)) {
    return str_replace(
      '+',
      ' ',
      str_replace('%7E', '~', rawurlencode($input))
    );
  } else {
    return '';
  }
}
760 761


762 763 764 765 766 767
  // This decode function isn't taking into consideration the above
  // modifications to the encoding process. However, this method doesn't
  // seem to be used anywhere so leaving it as is.
  public static function urldecode_rfc3986($string) {
    return urldecode($string);
  }
768

769 770 771 772 773 774 775 776 777 778 779 780 781 782
  // Utility function for turning the Authorization: header into
  // parameters, has to do some unescaping
  // Can filter out any non-oauth parameters if needed (default behaviour)
  // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement.
  //                  see http://code.google.com/p/oauth/issues/detail?id=163
  public static function split_header($header, $only_allow_oauth_parameters = true) {
    $params = array();
    if (preg_match_all('/('.($only_allow_oauth_parameters ? 'oauth_' : '').'[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) {
      foreach ($matches[1] as $i => $h) {
        $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]);
      }
      if (isset($params['realm'])) {
        unset($params['realm']);
      }
783
    }
784 785
    return $params;
  }
786

787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
  // helper to try to sort out headers for people who aren't running apache
  public static function get_headers() {
    if (function_exists('apache_request_headers')) {
      // we need this to get the actual Authorization: header
      // because apache tends to tell us it doesn't exist
      $headers = apache_request_headers();

      // sanitize the output of apache_request_headers because
      // we always want the keys to be Cased-Like-This and arh()
      // returns the headers in the same case as they are in the
      // request
      $out = array();
      foreach ($headers AS $key => $value) {
        $key = str_replace(
            " ",
            "-",
            ucwords(strtolower(str_replace("-", " ", $key)))
          );
        $out[$key] = $value;
      }
807
    } else {
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
      // otherwise we don't have apache and are just going to have to hope
      // that $_SERVER actually contains what we need
      $out = array();
      if( isset($_SERVER['CONTENT_TYPE']) )
        $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
      if( isset($_ENV['CONTENT_TYPE']) )
        $out['Content-Type'] = $_ENV['CONTENT_TYPE'];

      foreach ($_SERVER as $key => $value) {
        if (substr($key, 0, 5) == "HTTP_") {
          // this is chaos, basically it is just there to capitalize the first
          // letter of every word that is not an initial HTTP and strip HTTP
          // code from przemek
          $key = str_replace(
            " ",
            "-",
            ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
          );
          $out[$key] = $value;
        }
      }
829
    }
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860
    return $out;
  }

  // This function takes a input like a=b&a=c&d=e and returns the parsed
  // parameters like this
  // array('a' => array('b','c'), 'd' => 'e')
  public static function parse_parameters( $input ) {
    if (!isset($input) || !$input) return array();

    $pairs = explode('&', $input);

    $parsed_parameters = array();
    foreach ($pairs as $pair) {
      $split = explode('=', $pair, 2);
      $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
      $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';

      if (isset($parsed_parameters[$parameter])) {
        // We have already recieved parameter(s) with this name, so add to the list
        // of parameters with this name

        if (is_scalar($parsed_parameters[$parameter])) {
          // This is the first duplicate, so transform scalar (string) into an array
          // so we can add the duplicates
          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
        }

        $parsed_parameters[$parameter][] = $value;
      } else {
        $parsed_parameters[$parameter] = $value;
      }
861
    }
862 863
    return $parsed_parameters;
  }
864

865 866
  public static function build_http_query($params) {
    if (!$params) return '';
867

868 869 870 871
    // Urlencode both keys and values
    $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
    $values = OAuthUtil::urlencode_rfc3986(array_values($params));
    $params = array_combine($keys, $values);
872

873 874 875
    // Parameters are sorted by name, using lexicographical byte value ordering.
    // Ref: Spec: 9.1.1 (1)
    uksort($params, 'strcmp');
876

877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
    $pairs = array();
    foreach ($params as $parameter => $value) {
      if (is_array($value)) {
        // If two or more parameters share the same name, they are sorted by their value
        // Ref: Spec: 9.1.1 (1)
        // June 12th, 2010 - changed to sort because of issue 164 by hidetaka
        sort($value, SORT_STRING);
        foreach ($value as $duplicate_value) {
          $pairs[] = $parameter . '=' . $duplicate_value;
        }
      } else {
        $pairs[] = $parameter . '=' . $value;
      }
    }
    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
    // Each name-value pair is separated by an '&' character (ASCII code 38)
    return implode('&', $pairs);
  }
}
896 897

?>