1<?php 2 3/** 4 * This is the PHP OpenID library by JanRain, Inc. 5 * 6 * This module contains core utility functionality used by the 7 * library. See Consumer.php and Server.php for the consumer and 8 * server implementations. 9 * 10 * PHP versions 4 and 5 11 * 12 * LICENSE: See the COPYING file included in this distribution. 13 * 14 * @package OpenID 15 * @author JanRain, Inc. <openid@janrain.com> 16 * @copyright 2005-2008 Janrain, Inc. 17 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 18 */ 19 20/** 21 * The library version string 22 */ 23define('Auth_OpenID_VERSION', '2.2.2'); 24 25/** 26 * Require the fetcher code. 27 */ 28require_once "Auth/Yadis/PlainHTTPFetcher.php"; 29require_once "Auth/Yadis/ParanoidHTTPFetcher.php"; 30require_once "Auth/OpenID/BigMath.php"; 31require_once "Auth/OpenID/URINorm.php"; 32 33/** 34 * Status code returned by the server when the only option is to show 35 * an error page, since we do not have enough information to redirect 36 * back to the consumer. The associated value is an error message that 37 * should be displayed on an HTML error page. 38 * 39 * @see Auth_OpenID_Server 40 */ 41define('Auth_OpenID_LOCAL_ERROR', 'local_error'); 42 43/** 44 * Status code returned when there is an error to return in key-value 45 * form to the consumer. The caller should return a 400 Bad Request 46 * response with content-type text/plain and the value as the body. 47 * 48 * @see Auth_OpenID_Server 49 */ 50define('Auth_OpenID_REMOTE_ERROR', 'remote_error'); 51 52/** 53 * Status code returned when there is a key-value form OK response to 54 * the consumer. The value associated with this code is the 55 * response. The caller should return a 200 OK response with 56 * content-type text/plain and the value as the body. 57 * 58 * @see Auth_OpenID_Server 59 */ 60define('Auth_OpenID_REMOTE_OK', 'remote_ok'); 61 62/** 63 * Status code returned when there is a redirect back to the 64 * consumer. The value is the URL to redirect back to. The caller 65 * should return a 302 Found redirect with a Location: header 66 * containing the URL. 67 * 68 * @see Auth_OpenID_Server 69 */ 70define('Auth_OpenID_REDIRECT', 'redirect'); 71 72/** 73 * Status code returned when the caller needs to authenticate the 74 * user. The associated value is a {@link Auth_OpenID_ServerRequest} 75 * object that can be used to complete the authentication. If the user 76 * has taken some authentication action, use the retry() method of the 77 * {@link Auth_OpenID_ServerRequest} object to complete the request. 78 * 79 * @see Auth_OpenID_Server 80 */ 81define('Auth_OpenID_DO_AUTH', 'do_auth'); 82 83/** 84 * Status code returned when there were no OpenID arguments 85 * passed. This code indicates that the caller should return a 200 OK 86 * response and display an HTML page that says that this is an OpenID 87 * server endpoint. 88 * 89 * @see Auth_OpenID_Server 90 */ 91define('Auth_OpenID_DO_ABOUT', 'do_about'); 92 93/** 94 * Defines for regexes and format checking. 95 */ 96define('Auth_OpenID_letters', 97 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); 98 99define('Auth_OpenID_digits', 100 "0123456789"); 101 102define('Auth_OpenID_punct', 103 "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"); 104 105Auth_OpenID_include_init(); 106 107/** 108 * The OpenID utility function class. 109 * 110 * @package OpenID 111 * @access private 112 */ 113class Auth_OpenID { 114 115 /** 116 * Return true if $thing is an Auth_OpenID_FailureResponse object; 117 * false if not. 118 * 119 * @access private 120 */ 121 static function isFailure($thing) 122 { 123 return is_a($thing, 'Auth_OpenID_FailureResponse'); 124 } 125 126 /** 127 * Gets the query data from the server environment based on the 128 * request method used. If GET was used, this looks at 129 * $_SERVER['QUERY_STRING'] directly. If POST was used, this 130 * fetches data from the special php://input file stream. 131 * 132 * Returns an associative array of the query arguments. 133 * 134 * Skips invalid key/value pairs (i.e. keys with no '=value' 135 * portion). 136 * 137 * Returns an empty array if neither GET nor POST was used, or if 138 * POST was used but php://input cannot be opened. 139 * 140 * See background: 141 * http://lists.openidenabled.com/pipermail/dev/2007-March/000395.html 142 * 143 * @access private 144 */ 145 static function getQuery($query_str=null) 146 { 147 $data = array(); 148 149 if ($query_str !== null) { 150 $data = Auth_OpenID::params_from_string($query_str); 151 } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) { 152 // Do nothing. 153 } else { 154 // XXX HACK FIXME HORRIBLE. 155 // 156 // POSTing to a URL with query parameters is acceptable, but 157 // we don't have a clean way to distinguish those parameters 158 // when we need to do things like return_to verification 159 // which only want to look at one kind of parameter. We're 160 // going to emulate the behavior of some other environments 161 // by defaulting to GET and overwriting with POST if POST 162 // data is available. 163 $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']); 164 165 if ($_SERVER['REQUEST_METHOD'] == 'POST') { 166 $str = file_get_contents('php://input'); 167 168 if ($str === false) { 169 $post = array(); 170 } else { 171 $post = Auth_OpenID::params_from_string($str); 172 } 173 174 $data = array_merge($data, $post); 175 } 176 } 177 178 return $data; 179 } 180 181 static function params_from_string($str) 182 { 183 $chunks = explode("&", $str); 184 185 $data = array(); 186 foreach ($chunks as $chunk) { 187 $parts = explode("=", $chunk, 2); 188 189 if (count($parts) != 2) { 190 continue; 191 } 192 193 list($k, $v) = $parts; 194 $data[urldecode($k)] = urldecode($v); 195 } 196 197 return $data; 198 } 199 200 /** 201 * Create dir_name as a directory if it does not exist. If it 202 * exists, make sure that it is, in fact, a directory. Returns 203 * true if the operation succeeded; false if not. 204 * 205 * @access private 206 */ 207 static function ensureDir($dir_name) 208 { 209 if (is_dir($dir_name) || @mkdir($dir_name)) { 210 return true; 211 } else { 212 $parent_dir = dirname($dir_name); 213 214 // Terminal case; there is no parent directory to create. 215 if ($parent_dir == $dir_name) { 216 return true; 217 } 218 219 return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name)); 220 } 221 } 222 223 /** 224 * Adds a string prefix to all values of an array. Returns a new 225 * array containing the prefixed values. 226 * 227 * @access private 228 */ 229 static function addPrefix($values, $prefix) 230 { 231 $new_values = array(); 232 foreach ($values as $s) { 233 $new_values[] = $prefix . $s; 234 } 235 return $new_values; 236 } 237 238 /** 239 * Convenience function for getting array values. Given an array 240 * $arr and a key $key, get the corresponding value from the array 241 * or return $default if the key is absent. 242 * 243 * @access private 244 */ 245 static function arrayGet($arr, $key, $fallback = null) 246 { 247 if (is_array($arr)) { 248 if (array_key_exists($key, $arr)) { 249 return $arr[$key]; 250 } else { 251 return $fallback; 252 } 253 } else { 254 trigger_error("Auth_OpenID::arrayGet (key = ".$key.") expected " . 255 "array as first parameter, got " . 256 gettype($arr), E_USER_WARNING); 257 258 return false; 259 } 260 } 261 262 /** 263 * Replacement for PHP's broken parse_str. 264 */ 265 static function parse_str($query) 266 { 267 if ($query === null) { 268 return null; 269 } 270 271 $parts = explode('&', $query); 272 273 $new_parts = array(); 274 for ($i = 0; $i < count($parts); $i++) { 275 $pair = explode('=', $parts[$i]); 276 277 if (count($pair) != 2) { 278 continue; 279 } 280 281 list($key, $value) = $pair; 282 $new_parts[urldecode($key)] = urldecode($value); 283 } 284 285 return $new_parts; 286 } 287 288 /** 289 * Implements the PHP 5 'http_build_query' functionality. 290 * 291 * @access private 292 * @param array $data Either an array key/value pairs or an array 293 * of arrays, each of which holding two values: a key and a value, 294 * sequentially. 295 * @return string $result The result of url-encoding the key/value 296 * pairs from $data into a URL query string 297 * (e.g. "username=bob&id=56"). 298 */ 299 static function httpBuildQuery($data) 300 { 301 $pairs = array(); 302 foreach ($data as $key => $value) { 303 if (is_array($value)) { 304 $pairs[] = urlencode($value[0])."=".urlencode($value[1]); 305 } else { 306 $pairs[] = urlencode($key)."=".urlencode($value); 307 } 308 } 309 return implode("&", $pairs); 310 } 311 312 /** 313 * "Appends" query arguments onto a URL. The URL may or may not 314 * already have arguments (following a question mark). 315 * 316 * @access private 317 * @param string $url A URL, which may or may not already have 318 * arguments. 319 * @param array $args Either an array key/value pairs or an array of 320 * arrays, each of which holding two values: a key and a value, 321 * sequentially. If $args is an ordinary key/value array, the 322 * parameters will be added to the URL in sorted alphabetical order; 323 * if $args is an array of arrays, their order will be preserved. 324 * @return string $url The original URL with the new parameters added. 325 * 326 */ 327 static function appendArgs($url, $args) 328 { 329 if (count($args) == 0) { 330 return $url; 331 } 332 333 // Non-empty array; if it is an array of arrays, use 334 // multisort; otherwise use sort. 335 if (array_key_exists(0, $args) && 336 is_array($args[0])) { 337 // Do nothing here. 338 } else { 339 $keys = array_keys($args); 340 sort($keys); 341 $new_args = array(); 342 foreach ($keys as $key) { 343 $new_args[] = array($key, $args[$key]); 344 } 345 $args = $new_args; 346 } 347 348 $sep = '?'; 349 if (strpos($url, '?') !== false) { 350 $sep = '&'; 351 } 352 353 return $url . $sep . Auth_OpenID::httpBuildQuery($args); 354 } 355 356 /** 357 * Implements python's urlunparse, which is not available in PHP. 358 * Given the specified components of a URL, this function rebuilds 359 * and returns the URL. 360 * 361 * @access private 362 * @param string $scheme The scheme (e.g. 'http'). Defaults to 'http'. 363 * @param string $host The host. Required. 364 * @param string $port The port. 365 * @param string $path The path. 366 * @param string $query The query. 367 * @param string $fragment The fragment. 368 * @return string $url The URL resulting from assembling the 369 * specified components. 370 */ 371 static function urlunparse($scheme, $host, $port = null, $path = '/', 372 $query = '', $fragment = '') 373 { 374 375 if (!$scheme) { 376 $scheme = 'http'; 377 } 378 379 if (!$host) { 380 return false; 381 } 382 383 if (!$path) { 384 $path = ''; 385 } 386 387 $result = $scheme . "://" . $host; 388 389 if ($port) { 390 $result .= ":" . $port; 391 } 392 393 $result .= $path; 394 395 if ($query) { 396 $result .= "?" . $query; 397 } 398 399 if ($fragment) { 400 $result .= "#" . $fragment; 401 } 402 403 return $result; 404 } 405 406 /** 407 * Given a URL, this "normalizes" it by adding a trailing slash 408 * and / or a leading http:// scheme where necessary. Returns 409 * null if the original URL is malformed and cannot be normalized. 410 * 411 * @access private 412 * @param string $url The URL to be normalized. 413 * @return mixed $new_url The URL after normalization, or null if 414 * $url was malformed. 415 */ 416 static function normalizeUrl($url) 417 { 418 @$parsed = parse_url($url); 419 420 if (!$parsed) { 421 return null; 422 } 423 424 if (isset($parsed['scheme']) && 425 isset($parsed['host'])) { 426 $scheme = strtolower($parsed['scheme']); 427 if (!in_array($scheme, array('http', 'https'))) { 428 return null; 429 } 430 } else { 431 $url = 'http://' . $url; 432 } 433 434 $normalized = Auth_OpenID_urinorm($url); 435 if ($normalized === null) { 436 return null; 437 } 438 list($defragged, $frag) = Auth_OpenID::urldefrag($normalized); 439 return $defragged; 440 } 441 442 /** 443 * Replacement (wrapper) for PHP's intval() because it's broken. 444 * 445 * @access private 446 */ 447 static function intval($value) 448 { 449 $re = "/^\\d+$/"; 450 451 if (!preg_match($re, $value)) { 452 return false; 453 } 454 455 return intval($value); 456 } 457 458 /** 459 * Count the number of bytes in a string independently of 460 * multibyte support conditions. 461 * 462 * @param string $str The string of bytes to count. 463 * @return int The number of bytes in $str. 464 */ 465 static function bytes($str) 466 { 467 return strlen(bin2hex($str)) / 2; 468 } 469 470 /** 471 * Get the bytes in a string independently of multibyte support 472 * conditions. 473 */ 474 static function toBytes($str) 475 { 476 $hex = bin2hex($str); 477 478 if (!$hex) { 479 return array(); 480 } 481 482 $b = array(); 483 for ($i = 0; $i < strlen($hex); $i += 2) { 484 $b[] = chr(base_convert(substr($hex, $i, 2), 16, 10)); 485 } 486 487 return $b; 488 } 489 490 static function urldefrag($url) 491 { 492 $parts = explode("#", $url, 2); 493 494 if (count($parts) == 1) { 495 return array($parts[0], ""); 496 } else { 497 return $parts; 498 } 499 } 500 501 static function filter($callback, &$sequence) 502 { 503 $result = array(); 504 505 foreach ($sequence as $item) { 506 if (call_user_func_array($callback, array($item))) { 507 $result[] = $item; 508 } 509 } 510 511 return $result; 512 } 513 514 static function update(&$dest, &$src) 515 { 516 foreach ($src as $k => $v) { 517 $dest[$k] = $v; 518 } 519 } 520 521 /** 522 * Wrap PHP's standard error_log functionality. Use this to 523 * perform all logging. It will interpolate any additional 524 * arguments into the format string before logging. 525 * 526 * @param string $format_string The sprintf format for the message 527 */ 528 static function log($format_string) 529 { 530 $args = func_get_args(); 531 $message = call_user_func_array('sprintf', $args); 532 error_log($message); 533 } 534 535 static function autoSubmitHTML($form, $title="OpenId transaction in progress") 536 { 537 return("<html>". 538 "<head><title>". 539 $title . 540 "</title></head>". 541 "<body onload='document.forms[0].submit();'>". 542 $form . 543 "<script>". 544 "var elements = document.forms[0].elements;". 545 "for (var i = 0; i < elements.length; i++) {". 546 " elements[i].style.display = \"none\";". 547 "}". 548 "</script>". 549 "</body>". 550 "</html>"); 551 } 552} 553 554/* 555 * Function to run when this file is included. 556 * Abstracted to a function to make life easier 557 * for some PHP optimizers. 558 */ 559function Auth_OpenID_include_init() { 560 if (Auth_OpenID_getMathLib() === null) { 561 Auth_OpenID_setNoMathSupport(); 562 } 563} 564