You are here

OAuthRequest.php in Lingotek Translation 7.3

Same filename and directory in other branches
  1. 7.2 lib/oauth-php/library/OAuthRequest.php

File

lib/oauth-php/library/OAuthRequest.php
View source
<?php

/**
 * Request wrapper class.  Prepares a request for consumption by the OAuth routines
 * 
 * @version $Id: OAuthRequest.php 174 2010-11-24 15:15:41Z brunobg@corollarium.com $
 * @author Marc Worrell <marcw@pobox.com>
 * @date  Nov 16, 2007 12:20:31 PM
 * 
 * The MIT License
 * 
 * Copyright (c) 2007-2008 Mediamatic Lab
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
require_once dirname(__FILE__) . '/OAuthException2.php';

/**
 * Object to parse an incoming OAuth request or prepare an outgoing OAuth request
 */
class OAuthRequest {

  /* the realm for this request */
  protected $realm;

  /* all the parameters, RFC3986 encoded name/value pairs */
  protected $param = array();

  /* the parsed request uri */
  protected $uri_parts;

  /* the raw request uri */
  protected $uri;

  /* the request headers */
  protected $headers;

  /* the request method */
  protected $method;

  /* the body of the OAuth request */
  protected $body;

  /**
   * Construct from the current request. Useful for checking the signature of a request.
   * When not supplied with any parameters this will use the current request.
   *
   * @param string	uri				might include parameters
   * @param string	method			GET, PUT, POST etc.
   * @param string	parameters		additional post parameters, urlencoded (RFC1738)
   * @param array		headers			headers for request
   * @param string	body			optional body of the OAuth request (POST or PUT)
   */
  function __construct($uri = null, $method = null, $parameters = '', $headers = array(), $body = null) {
    if (is_object($_SERVER)) {

      // Tainted arrays - the normal stuff in anyMeta
      if (!$method) {
        $method = $_SERVER->REQUEST_METHOD
          ->getRawUnsafe();
      }
      if (empty($uri)) {
        $uri = $_SERVER->REQUEST_URI
          ->getRawUnsafe();
      }
    }
    else {

      // non anyMeta systems
      if (!$method) {
        if (isset($_SERVER['REQUEST_METHOD'])) {
          $method = $_SERVER['REQUEST_METHOD'];
        }
        else {
          $method = 'GET';
        }
      }
      $proto = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
      if (empty($uri)) {
        if (strpos($_SERVER['REQUEST_URI'], "://") !== false) {
          $uri = $_SERVER['REQUEST_URI'];
        }
        else {
          $uri = sprintf('%s://%s%s', $proto, $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']);
        }
      }
    }
    $headers = OAuthRequestLogger::getAllHeaders();
    $this->method = strtoupper($method);

    // If this is a post then also check the posted variables
    if (strcasecmp($method, 'POST') == 0) {

      // TODO: what to do with 'multipart/form-data'?
      if ($this
        ->getRequestContentType() == 'multipart/form-data') {

        // Get the posted body (when available)
        if (!isset($headers['X-OAuth-Test'])) {
          $parameters .= $this
            ->getRequestBodyOfMultipart();
        }
      }
      if ($this
        ->getRequestContentType() == 'application/x-www-form-urlencoded') {

        // Get the posted body (when available)
        if (!isset($headers['X-OAuth-Test'])) {
          $parameters .= $this
            ->getRequestBody();
        }
      }
      else {
        $body = $this
          ->getRequestBody();
      }
    }
    else {
      if (strcasecmp($method, 'PUT') == 0) {
        $body = $this
          ->getRequestBody();
      }
    }
    $this->method = strtoupper($method);
    $this->headers = $headers;

    // Store the values, prepare for oauth
    $this->uri = $uri;
    $this->body = $body;
    $this
      ->parseUri($parameters);
    $this
      ->parseHeaders();
    $this
      ->transcodeParams();
  }

  /**
   * Return the signature base string.
   * Note that we can't use rawurlencode due to specified use of RFC3986.
   *
   * @return string
   */
  function signatureBaseString() {
    $sig = array();
    $sig[] = $this->method;
    $sig[] = $this
      ->getRequestUrl();
    $sig[] = $this
      ->getNormalizedParams();
    return implode('&', array_map(array(
      $this,
      'urlencode',
    ), $sig));
  }

  /**
   * Calculate the signature of the request, using the method in oauth_signature_method.
   * The signature is returned encoded in the form as used in the url.  So the base64 and
   * urlencoding has been done.
   *
   * @param string consumer_secret
   * @param string token_secret
   * @param string token_type
   * @exception when not all parts available
   * @return string
   */
  function calculateSignature($consumer_secret, $token_secret, $token_type = 'access') {
    $required = array(
      'oauth_consumer_key',
      'oauth_signature_method',
      'oauth_timestamp',
      'oauth_nonce',
    );
    if ($token_type != 'requestToken') {
      $required[] = 'oauth_token';
    }
    foreach ($required as $req) {
      if (!isset($this->param[$req])) {
        throw new OAuthException2('Can\'t sign request, missing parameter "' . $req . '"');
      }
    }
    $this
      ->checks();
    $base = $this
      ->signatureBaseString();
    $signature = $this
      ->calculateDataSignature($base, $consumer_secret, $token_secret, $this->param['oauth_signature_method']);
    return $signature;
  }

  /**
   * Calculate the signature of a string.
   * Uses the signature method from the current parameters.
   *
   * @param string 	data
   * @param string	consumer_secret
   * @param string	token_secret
   * @param string 	signature_method
   * @exception OAuthException2 thrown when the signature method is unknown
   * @return string signature
   */
  function calculateDataSignature($data, $consumer_secret, $token_secret, $signature_method) {
    if (is_null($data)) {
      $data = '';
    }
    $sig = $this
      ->getSignatureMethod($signature_method);
    return $sig
      ->signature($this, $data, $consumer_secret, $token_secret);
  }

  /**
   * Select a signature method from the list of available methods.
   * We try to check the most secure methods first.
   *
   * @todo Let the signature method tell us how secure it is
   * @param array methods
   * @exception OAuthException2 when we don't support any method in the list
   * @return string
   */
  public function selectSignatureMethod($methods) {
    if (in_array('HMAC-SHA1', $methods)) {
      $method = 'HMAC-SHA1';
    }
    else {
      if (in_array('MD5', $methods)) {
        $method = 'MD5';
      }
      else {
        $method = false;
        foreach ($methods as $m) {
          $m = strtoupper($m);
          $m2 = preg_replace('/[^A-Z0-9]/', '_', $m);
          if (file_exists(dirname(__FILE__) . '/signature_method/OAuthSignatureMethod_' . $m2 . '.php')) {
            $method = $m;
            break;
          }
        }
        if (empty($method)) {
          throw new OAuthException2('None of the signing methods is supported.');
        }
      }
    }
    return $method;
  }

  /**
   * Fetch the signature object used for calculating and checking the signature base string
   *
   * @param string method
   * @return OAuthSignatureMethod object
   */
  function getSignatureMethod($method) {
    $m = strtoupper($method);
    $m = preg_replace('/[^A-Z0-9]/', '_', $m);
    $class = 'OAuthSignatureMethod_' . $m;
    if (file_exists(dirname(__FILE__) . '/signature_method/' . $class . '.php')) {
      require_once dirname(__FILE__) . '/signature_method/' . $class . '.php';
      $sig = new $class();
    }
    else {
      throw new OAuthException2('Unsupported signature method "' . $m . '".');
    }
    return $sig;
  }

  /**
   * Perform some sanity checks.
   *
   * @exception OAuthException2 thrown when sanity checks failed
   */
  function checks() {
    if (isset($this->param['oauth_version'])) {
      $version = $this
        ->urldecode($this->param['oauth_version']);
      if ($version != '1.0') {
        throw new OAuthException2('Expected OAuth version 1.0, got "' . $this->param['oauth_version'] . '"');
      }
    }
  }

  /**
   * Return the request method
   *
   * @return string
   */
  function getMethod() {
    return $this->method;
  }

  /**
   * Return the complete parameter string for the signature check.
   * All parameters are correctly urlencoded and sorted on name and value
   *
   * @return string
   */
  function getNormalizedParams() {

    /*
    		// sort by name, then by value
    		// (needed when we start allowing multiple values with the same name)
    		$keys   = array_keys($this->param);
    		$values = array_values($this->param);
    		array_multisort($keys, SORT_ASC, $values, SORT_ASC);
    */
    $params = $this->param;
    $normalized = array();
    ksort($params);
    foreach ($params as $key => $value) {

      // all names and values are already urlencoded, exclude the oauth signature
      if ($key != 'oauth_signature') {
        if (is_array($value)) {
          $value_sort = $value;
          sort($value_sort, SORT_STRING);

          // multi-parameters require the values to be sorted lexigraphically
          foreach ($value_sort as $v) {
            $normalized[] = $key . '=' . $v;
          }
        }
        else {
          $normalized[] = $key . '=' . $value;
        }
      }
    }
    return implode('&', $normalized);
  }

  /**
   * Return the normalised url for signature checks
   */
  function getRequestUrl() {
    $url = $this->uri_parts['scheme'] . '://' . $this->uri_parts['user'] . (!empty($this->uri_parts['pass']) ? ':' : '') . $this->uri_parts['pass'] . (!empty($this->uri_parts['user']) ? '@' : '') . $this->uri_parts['host'];
    if ($this->uri_parts['port'] && $this->uri_parts['port'] != $this
      ->defaultPortForScheme($this->uri_parts['scheme'])) {
      $url .= ':' . $this->uri_parts['port'];
    }
    if (!empty($this->uri_parts['path'])) {
      $url .= $this->uri_parts['path'];
    }
    return $url;
  }

  /**
   * Get a parameter, value is always urlencoded
   *
   * @param string	name
   * @param boolean	urldecode	set to true to decode the value upon return
   * @return string value		false when not found
   */
  function getParam($name, $urldecode = false) {
    if (isset($this->param[$name])) {
      $s = $this->param[$name];
    }
    else {
      if (isset($this->param[$this
        ->urlencode($name)])) {
        $s = $this->param[$this
          ->urlencode($name)];
      }
      else {
        $s = false;
      }
    }
    if (!empty($s) && $urldecode) {
      if (is_array($s)) {
        $s = array_map(array(
          $this,
          'urldecode',
        ), $s);
      }
      else {
        $s = $this
          ->urldecode($s);
      }
    }
    return $s;
  }

  /**
   * Set a parameter
   *
   * @param string	name
   * @param string	value
   * @param boolean	encoded	set to true when the values are already encoded
   */
  function setParam($name, $value, $encoded = false) {
    if (!$encoded) {
      $name_encoded = $this
        ->urlencode($name);
      if (is_array($value)) {
        foreach ($value as $v) {
          $this->param[$name_encoded][] = $this
            ->urlencode($v);
        }
      }
      else {
        $this->param[$name_encoded] = $this
          ->urlencode($value);
      }
    }
    else {
      $this->param[$name] = $value;
    }
  }

  /**
   * Re-encode all parameters so that they are encoded using RFC3986.
   * Updates the $this->param attribute.
   */
  protected function transcodeParams() {
    $params = $this->param;
    $this->param = array();
    foreach ($params as $name => $value) {
      if (is_array($value)) {
        $this->param[$this
          ->urltranscode($name)] = array_map(array(
          $this,
          'urltranscode',
        ), $value);
      }
      else {
        $this->param[$this
          ->urltranscode($name)] = $this
          ->urltranscode($value);
      }
    }
  }

  /**
   * Return the body of the OAuth request.
   *
   * @return string		null when no body
   */
  function getBody() {
    return $this->body;
  }

  /**
   * Return the body of the OAuth request.
   *
   * @return string		null when no body
   */
  function setBody($body) {
    $this->body = $body;
  }

  /**
   * Parse the uri into its parts.  Fill in the missing parts.
   *
   * @param string $parameters  optional extra parameters (from eg the http post)
   */
  protected function parseUri($parameters) {
    $ps = @parse_url($this->uri);

    // Get the current/requested method
    $ps['scheme'] = strtolower($ps['scheme']);

    // Get the current/requested host
    if (function_exists('mb_strtolower')) {
      $ps['host'] = mb_strtolower($ps['host']);
    }
    else {
      $ps['host'] = strtolower($ps['host']);
    }
    if (!preg_match('/^[a-z0-9\\.\\-]+$/', $ps['host'])) {
      throw new OAuthException2('Unsupported characters in host name');
    }

    // Get the port we are talking on
    if (empty($ps['port'])) {
      $ps['port'] = $this
        ->defaultPortForScheme($ps['scheme']);
    }
    if (empty($ps['user'])) {
      $ps['user'] = '';
    }
    if (empty($ps['pass'])) {
      $ps['pass'] = '';
    }
    if (empty($ps['path'])) {
      $ps['path'] = '/';
    }
    if (empty($ps['query'])) {
      $ps['query'] = '';
    }
    if (empty($ps['fragment'])) {
      $ps['fragment'] = '';
    }

    // Now all is complete - parse all parameters
    foreach (array(
      $ps['query'],
      $parameters,
    ) as $params) {
      if (strlen($params) > 0) {
        $params = explode('&', $params);
        foreach ($params as $p) {
          @(list($name, $value) = explode('=', $p, 2));
          if (!strlen($name)) {
            continue;
          }
          if (array_key_exists($name, $this->param)) {
            if (is_array($this->param[$name])) {
              $this->param[$name][] = $value;
            }
            else {
              $this->param[$name] = array(
                $this->param[$name],
                $value,
              );
            }
          }
          else {
            $this->param[$name] = $value;
          }
        }
      }
    }
    $this->uri_parts = $ps;
  }

  /**
   * Return the default port for a scheme
   *
   * @param string scheme
   * @return int
   */
  protected function defaultPortForScheme($scheme) {
    switch ($scheme) {
      case 'http':
        return 80;
      case 'https':
        return 443;
      default:
        throw new OAuthException2('Unsupported scheme type, expected http or https, got "' . $scheme . '"');
        break;
    }
  }

  /**
   * Encode a string according to the RFC3986
   *
   * @param string s
   * @return string
   */
  function urlencode($s) {
    if ($s === false) {
      return $s;
    }
    else {
      return str_replace('%7E', '~', rawurlencode($s));
    }
  }

  /**
   * Decode a string according to RFC3986.
   * Also correctly decodes RFC1738 urls.
   *
   * @param string s
   * @return string
   */
  function urldecode($s) {
    if ($s === false) {
      return $s;
    }
    else {
      return rawurldecode($s);
    }
  }

  /**
   * urltranscode - make sure that a value is encoded using RFC3986.
   * We use a basic urldecode() function so that any use of '+' as the
   * encoding of the space character is correctly handled.
   *
   * @param string s
   * @return string
   */
  function urltranscode($s) {
    if ($s === false) {
      return $s;
    }
    else {
      return $this
        ->urlencode(rawurldecode($s));

      // return $this->urlencode(urldecode($s));
    }
  }

  /**
   * Parse the oauth parameters from the request headers
   * Looks for something like:
   *
   * Authorization: OAuth realm="http://photos.example.net/authorize",
   *           oauth_consumer_key="dpf43f3p2l4k3l03",
   *           oauth_token="nnch734d00sl2jdk",
   *           oauth_signature_method="HMAC-SHA1",
   *           oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",
   *           oauth_timestamp="1191242096",
   *           oauth_nonce="kllo9940pd9333jh",
   *           oauth_version="1.0"
   */
  private function parseHeaders() {

    /*
    		$this->headers['Authorization'] = 'OAuth realm="http://photos.example.net/authorize",
                    oauth_consumer_key="dpf43f3p2l4k3l03",
                    oauth_token="nnch734d00sl2jdk",
                    oauth_signature_method="HMAC-SHA1",
                    oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",
                    oauth_timestamp="1191242096",
                    oauth_nonce="kllo9940pd9333jh",
                    oauth_version="1.0"';
    */
    if (isset($this->headers['Authorization'])) {
      $auth = trim($this->headers['Authorization']);
      if (strncasecmp($auth, 'OAuth', 4) == 0) {
        $vs = explode(',', substr($auth, 6));
        foreach ($vs as $v) {
          if (strpos($v, '=')) {
            $v = trim($v);
            list($name, $value) = explode('=', $v, 2);
            if (!empty($value) && $value[0] == '"' && substr($value, -1) == '"') {
              $value = substr(substr($value, 1), 0, -1);
            }
            if (strcasecmp($name, 'realm') == 0) {
              $this->realm = $value;
            }
            else {
              $this->param[$name] = $value;
            }
          }
        }
      }
    }
  }

  /**
   * Fetch the content type of the current request
   *
   * @return string
   */
  private function getRequestContentType() {
    $content_type = 'application/octet-stream';
    if (!empty($_SERVER) && array_key_exists('CONTENT_TYPE', $_SERVER)) {
      list($content_type) = explode(';', $_SERVER['CONTENT_TYPE']);
    }
    return trim($content_type);
  }

  /**
   * Get the body of a POST or PUT.
   *
   * Used for fetching the post parameters and to calculate the body signature.
   *
   * @return string		null when no body present (or wrong content type for body)
   */
  private function getRequestBody() {
    $body = null;
    if ($this->method == 'POST' || $this->method == 'PUT') {
      $body = '';
      $fh = @fopen('php://input', 'r');
      if ($fh) {
        while (!feof($fh)) {
          $s = fread($fh, 1024);
          if (is_string($s)) {
            $body .= $s;
          }
        }
        fclose($fh);
      }
    }
    return $body;
  }

  /**
   * Get the body of a POST with multipart/form-data by Edison tsai on 16:52 2010/09/16
   *
   * Used for fetching the post parameters and to calculate the body signature.
   *
   * @return string               null when no body present (or wrong content type for body)
   */
  private function getRequestBodyOfMultipart() {
    $body = null;
    if ($this->method == 'POST') {
      $body = '';
      if (is_array($_POST) && count($_POST) > 1) {
        foreach ($_POST as $k => $v) {
          $body .= $k . '=' . $this
            ->urlencode($v) . '&';
        }

        #end foreach
        if (substr($body, -1) == '&') {
          $body = substr($body, 0, strlen($body) - 1);
        }

        #end if
      }

      #end if
    }

    #end if
    return $body;
  }

  /**
   * Simple function to perform a redirect (GET).
   * Redirects the User-Agent, does not return.
   *
   * @param string uri
   * @param array params		parameters, urlencoded
   * @exception OAuthException2 when redirect uri is illegal
   */
  public function redirect($uri, $params) {
    if (!empty($params)) {
      $q = array();
      foreach ($params as $name => $value) {
        $q[] = $name . '=' . $value;
      }
      $q_s = implode('&', $q);
      if (strpos($uri, '?')) {
        $uri .= '&' . $q_s;
      }
      else {
        $uri .= '?' . $q_s;
      }
    }

    // simple security - multiline location headers can inject all kinds of extras
    $uri = preg_replace('/\\s/', '%20', $uri);
    if (strncasecmp($uri, 'http://', 7) && strncasecmp($uri, 'https://', 8)) {
      if (strpos($uri, '://')) {
        throw new OAuthException2('Illegal protocol in redirect uri ' . $uri);
      }
      $uri = 'http://' . $uri;
    }
    header('HTTP/1.1 302 Found');
    header('Location: ' . $uri);
    echo '';
    exit;
  }

}

/* vi:set ts=4 sts=4 sw=4 binary noeol: */

Classes

Namesort descending Description
OAuthRequest Object to parse an incoming OAuth request or prepare an outgoing OAuth request