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', '3.0.3'); 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 * @param object|string $thing 121 * @return bool 122 */ 123 static function isFailure($thing) 124 { 125 return is_a($thing, 'Auth_OpenID_FailureResponse'); 126 } 127 128 /** 129 * Gets the query data from the server environment based on the 130 * request method used. If GET was used, this looks at 131 * $_SERVER['QUERY_STRING'] directly. If POST was used, this 132 * fetches data from the special php://input file stream. 133 * 134 * Returns an associative array of the query arguments. 135 * 136 * Skips invalid key/value pairs (i.e. keys with no '=value' 137 * portion). 138 * 139 * Returns an empty array if neither GET nor POST was used, or if 140 * POST was used but php://input cannot be opened. 141 * 142 * See background: 143 * http://lists.openidenabled.com/pipermail/dev/2007-March/000395.html 144 * 145 * @access private 146 * @param string|null $query_str 147 * @return array 148 */ 149 static function getQuery($query_str=null) 150 { 151 $data = []; 152 153 if ($query_str !== null) { 154 $data = Auth_OpenID::params_from_string($query_str); 155 } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) { 156 // Do nothing. 157 } else { 158 // XXX HACK FIXME HORRIBLE. 159 // 160 // POSTing to a URL with query parameters is acceptable, but 161 // we don't have a clean way to distinguish those parameters 162 // when we need to do things like return_to verification 163 // which only want to look at one kind of parameter. We're 164 // going to emulate the behavior of some other environments 165 // by defaulting to GET and overwriting with POST if POST 166 // data is available. 167 $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']); 168 169 if ($_SERVER['REQUEST_METHOD'] == 'POST') { 170 $str = file_get_contents('php://input'); 171 172 if ($str === false) { 173 $post = []; 174 } else { 175 $post = Auth_OpenID::params_from_string($str); 176 } 177 178 $data = array_merge($data, $post); 179 } 180 } 181 182 return $data; 183 } 184 185 static function params_from_string($str) 186 { 187 $chunks = explode("&", $str); 188 189 $data = []; 190 foreach ($chunks as $chunk) { 191 $parts = explode("=", $chunk, 2); 192 193 if (count($parts) != 2) { 194 continue; 195 } 196 197 list($k, $v) = $parts; 198 $data[urldecode($k)] = urldecode($v); 199 } 200 201 return $data; 202 } 203 204 /** 205 * Create dir_name as a directory if it does not exist. If it 206 * exists, make sure that it is, in fact, a directory. Returns 207 * true if the operation succeeded; false if not. 208 * 209 * @access private 210 * @param string $dir_name 211 * @return bool 212 */ 213 static function ensureDir($dir_name) 214 { 215 if (is_dir($dir_name) || @mkdir($dir_name)) { 216 return true; 217 } else { 218 $parent_dir = dirname($dir_name); 219 220 // Terminal case; there is no parent directory to create. 221 if ($parent_dir == $dir_name) { 222 return true; 223 } 224 225 return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name)); 226 } 227 } 228 229 /** 230 * Adds a string prefix to all values of an array. Returns a new 231 * array containing the prefixed values. 232 * 233 * @access private 234 * @param array $values 235 * @param string $prefix 236 * @return array 237 */ 238 static function addPrefix($values, $prefix) 239 { 240 $new_values = []; 241 foreach ($values as $s) { 242 $new_values[] = $prefix . $s; 243 } 244 return $new_values; 245 } 246 247 /** 248 * Convenience function for getting array values. Given an array 249 * $arr and a key $key, get the corresponding value from the array 250 * or return $default if the key is absent. 251 * 252 * @access private 253 * @param array $arr 254 * @param string $key 255 * @param mixed $fallback 256 * @return mixed 257 */ 258 static function arrayGet($arr, $key, $fallback = null) 259 { 260 if (is_array($arr)) { 261 if (array_key_exists($key, $arr)) { 262 return $arr[$key]; 263 } else { 264 return $fallback; 265 } 266 } else { 267 trigger_error("Auth_OpenID::arrayGet (key = ".$key.") expected " . 268 "array as first parameter, got " . 269 gettype($arr), E_USER_WARNING); 270 271 return false; 272 } 273 } 274 275 /** 276 * Replacement for PHP's broken parse_str. 277 * 278 * @param string|null $query 279 * @return array|null 280 */ 281 static function parse_str($query) 282 { 283 if ($query === null) { 284 return null; 285 } 286 287 $parts = explode('&', $query); 288 289 $new_parts = []; 290 for ($i = 0; $i < count($parts); $i++) { 291 $pair = explode('=', $parts[$i]); 292 293 if (count($pair) != 2) { 294 continue; 295 } 296 297 list($key, $value) = $pair; 298 $new_parts[urldecode($key)] = urldecode($value); 299 } 300 301 return $new_parts; 302 } 303 304 /** 305 * Implements the PHP 5 'http_build_query' functionality. 306 * 307 * @access private 308 * @param array $data Either an array key/value pairs or an array 309 * of arrays, each of which holding two values: a key and a value, 310 * sequentially. 311 * @return string $result The result of url-encoding the key/value 312 * pairs from $data into a URL query string 313 * (e.g. "username=bob&id=56"). 314 */ 315 static function httpBuildQuery($data) 316 { 317 $pairs = []; 318 foreach ($data as $key => $value) { 319 if (is_array($value)) { 320 $pairs[] = urlencode($value[0])."=".urlencode($value[1]); 321 } else { 322 $pairs[] = urlencode($key)."=".urlencode($value); 323 } 324 } 325 return implode("&", $pairs); 326 } 327 328 /** 329 * "Appends" query arguments onto a URL. The URL may or may not 330 * already have arguments (following a question mark). 331 * 332 * @access private 333 * @param string $url A URL, which may or may not already have 334 * arguments. 335 * @param array $args Either an array key/value pairs or an array of 336 * arrays, each of which holding two values: a key and a value, 337 * sequentially. If $args is an ordinary key/value array, the 338 * parameters will be added to the URL in sorted alphabetical order; 339 * if $args is an array of arrays, their order will be preserved. 340 * @return string $url The original URL with the new parameters added. 341 * 342 */ 343 static function appendArgs($url, $args) 344 { 345 if (count($args) == 0) { 346 return $url; 347 } 348 349 // Non-empty array; if it is an array of arrays, use 350 // multisort; otherwise use sort. 351 if (array_key_exists(0, $args) && 352 is_array($args[0])) { 353 // Do nothing here. 354 } else { 355 $keys = array_keys($args); 356 sort($keys); 357 $new_args = []; 358 foreach ($keys as $key) { 359 $new_args[] = [$key, $args[$key]]; 360 } 361 $args = $new_args; 362 } 363 364 $sep = '?'; 365 if (strpos($url, '?') !== false) { 366 $sep = '&'; 367 } 368 369 return $url . $sep . Auth_OpenID::httpBuildQuery($args); 370 } 371 372 /** 373 * Implements python's urlunparse, which is not available in PHP. 374 * Given the specified components of a URL, this function rebuilds 375 * and returns the URL. 376 * 377 * @access private 378 * @param string $scheme The scheme (e.g. 'http'). Defaults to 'http'. 379 * @param string $host The host. Required. 380 * @param string $port The port. 381 * @param string $path The path. 382 * @param string $query The query. 383 * @param string $fragment The fragment. 384 * @return string $url The URL resulting from assembling the 385 * specified components. 386 */ 387 static function urlunparse($scheme, $host, $port = null, $path = '/', 388 $query = '', $fragment = '') 389 { 390 391 if (!$scheme) { 392 $scheme = 'http'; 393 } 394 395 if (!$host) { 396 return false; 397 } 398 399 if (!$path) { 400 $path = ''; 401 } 402 403 $result = $scheme . "://" . $host; 404 405 if ($port) { 406 $result .= ":" . $port; 407 } 408 409 $result .= $path; 410 411 if ($query) { 412 $result .= "?" . $query; 413 } 414 415 if ($fragment) { 416 $result .= "#" . $fragment; 417 } 418 419 return $result; 420 } 421 422 /** 423 * Given a URL, this "normalizes" it by adding a trailing slash 424 * and / or a leading http:// scheme where necessary. Returns 425 * null if the original URL is malformed and cannot be normalized. 426 * 427 * @access private 428 * @param string $url The URL to be normalized. 429 * @return mixed $new_url The URL after normalization, or null if 430 * $url was malformed. 431 */ 432 static function normalizeUrl($url) 433 { 434 @$parsed = parse_url($url); 435 436 if (!$parsed) { 437 return null; 438 } 439 440 if (isset($parsed['scheme']) && 441 isset($parsed['host'])) { 442 $scheme = strtolower($parsed['scheme']); 443 if (!in_array($scheme, ['http', 'https'])) { 444 return null; 445 } 446 } else { 447 $url = 'http://' . $url; 448 } 449 450 $normalized = Auth_OpenID_urinorm($url); 451 if ($normalized === null) { 452 return null; 453 } 454 list($defragged) = Auth_OpenID::urldefrag($normalized); 455 return $defragged; 456 } 457 458 /** 459 * Replacement (wrapper) for PHP's intval() because it's broken. 460 * 461 * @access private 462 * @param string|int $value 463 * @return bool|int 464 */ 465 static function intval($value) 466 { 467 $re = "/^\\d+$/"; 468 469 if (!preg_match($re, $value)) { 470 return false; 471 } 472 473 return intval($value); 474 } 475 476 /** 477 * Count the number of bytes in a string independently of 478 * multibyte support conditions. 479 * 480 * @param string $str The string of bytes to count. 481 * @return int The number of bytes in $str. 482 */ 483 static function bytes($str) 484 { 485 return strlen(bin2hex($str)) / 2; 486 } 487 488 /** 489 * Get the bytes in a string independently of multibyte support 490 * conditions. 491 * 492 * @param string $str 493 * @return array 494 */ 495 static function toBytes($str) 496 { 497 $hex = bin2hex($str); 498 499 if (!$hex) { 500 return []; 501 } 502 503 $b = []; 504 for ($i = 0; $i < strlen($hex); $i += 2) { 505 $b[] = chr(base_convert(substr($hex, $i, 2), 16, 10)); 506 } 507 508 return $b; 509 } 510 511 static function urldefrag($url) 512 { 513 $parts = explode("#", $url, 2); 514 515 if (count($parts) == 1) { 516 return [$parts[0], ""]; 517 } else { 518 return $parts; 519 } 520 } 521 522 static function filter($callback, &$sequence) 523 { 524 $result = []; 525 526 foreach ($sequence as $item) { 527 if (call_user_func_array($callback, [$item])) { 528 $result[] = $item; 529 } 530 } 531 532 return $result; 533 } 534 535 static function update(&$dest, &$src) 536 { 537 foreach ($src as $k => $v) { 538 $dest[$k] = $v; 539 } 540 } 541 542 /** 543 * Wrap PHP's standard error_log functionality. Use this to 544 * perform all logging. It will interpolate any additional 545 * arguments into the format string before logging. 546 * 547 * @param string $format_string The sprintf format for the message 548 */ 549 static function log($format_string) 550 { 551 $args = func_get_args(); 552 $message = call_user_func_array('sprintf', $args); 553 error_log($message); 554 } 555 556 static function autoSubmitHTML($form, $title="OpenId transaction in progress") 557 { 558 return("<html>". 559 "<head><title>". 560 $title . 561 "</title></head>". 562 "<body onload='document.forms[0].submit();'>". 563 $form . 564 "<script>". 565 "var elements = document.forms[0].elements;". 566 "for (var i = 0; i < elements.length; i++) {". 567 " elements[i].style.display = \"none\";". 568 "}". 569 "</script>". 570 "</body>". 571 "</html>"); 572 } 573} 574 575/* 576 * Function to run when this file is included. 577 * Abstracted to a function to make life easier 578 * for some PHP optimizers. 579 */ 580function Auth_OpenID_include_init() { 581 if (Auth_OpenID_getMathLib() === null) { 582 Auth_OpenID_setNoMathSupport(); 583 } 584} 585