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