summaryrefslogtreecommitdiffstats
path: root/Tests/Auth
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/Auth')
-rw-r--r--Tests/Auth/OpenID/Association.php38
-rw-r--r--Tests/Auth/OpenID/Consumer.php308
-rw-r--r--Tests/Auth/OpenID/CryptUtil.php306
-rw-r--r--Tests/Auth/OpenID/DiffieHellman.php170
-rw-r--r--Tests/Auth/OpenID/HMACSHA1.php155
-rw-r--r--Tests/Auth/OpenID/KVForm.php259
-rw-r--r--Tests/Auth/OpenID/OIDUtil.php260
-rw-r--r--Tests/Auth/OpenID/Parse.php173
-rw-r--r--Tests/Auth/OpenID/StoreTest.php232
-rw-r--r--Tests/Auth/OpenID/dhexch25
-rw-r--r--Tests/Auth/OpenID/dhpriv29
-rw-r--r--Tests/Auth/OpenID/hmac.txt49
-rw-r--r--Tests/Auth/OpenID/linkparse.txt583
13 files changed, 2587 insertions, 0 deletions
diff --git a/Tests/Auth/OpenID/Association.php b/Tests/Auth/OpenID/Association.php
new file mode 100644
index 0000000..d344a21
--- /dev/null
+++ b/Tests/Auth/OpenID/Association.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Tests for the Association implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('PHPUnit.php');
+require_once('Auth/OpenID/Association.php');
+
+class Tests_Auth_OpenID_Association extends PHPUnit_TestCase {
+ function test_me()
+ {
+ $issued = time();
+ $lifetime = 600;
+ $assoc = new Auth_OpenID_Association('handle', 'secret', $issued,
+ $lifetime, 'HMAC-SHA1');
+ $s = $assoc->serialize();
+ $assoc2 = Auth_OpenID_Association::deserialize(
+ 'Auth_OpenID_Association', $s);
+
+ if ($assoc2 === null) {
+ $this->fail('deserialize returned null');
+ } else {
+ $this->assertTrue($assoc2->equal($assoc));
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/Consumer.php b/Tests/Auth/OpenID/Consumer.php
new file mode 100644
index 0000000..955558d
--- /dev/null
+++ b/Tests/Auth/OpenID/Consumer.php
@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * Tests for the OpenID consumer.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('Auth/OpenID/CryptUtil.php');
+require_once('Auth/OpenID/DiffieHellman.php');
+require_once('Auth/OpenID/Store/FileStore.php');
+require_once('Auth/OpenID/OIDUtil.php');
+require_once('Auth/OpenID/KVForm.php');
+require_once('Auth/OpenID/Consumer/Consumer.php');
+
+$_Auth_OpenID_assocs = array(
+ array('another 20-byte key.', 'Snarky'),
+ array(str_repeat("\x00", 20), 'Zeros'),
+ );
+
+$_Auth_OpenID_filestore_base_dir = "/tmp";
+
+function Auth_OpenID_parse($qs)
+{
+ $result = array();
+ $parts = explode("&", $qs);
+ foreach ($parts as $pair) {
+ list($key, $value) = explode("=", $pair, 2);
+ assert(!array_key_exists($key, $result));
+ $result[$key] = urldecode($value);
+ }
+ return $result;
+}
+
+function Auth_OpenID_associate($qs, $assoc_secret, $assoc_handle)
+{
+ $query_data = Auth_OpenID_parse($qs);
+
+ assert((count($query_data) == 6) || (count($query_data) == 4));
+ assert($query_data['openid.mode'] == 'associate');
+ assert($query_data['openid.assoc_type'] == 'HMAC-SHA1');
+ assert($query_data['openid.session_type'] == 'DH-SHA1');
+ $d = Auth_OpenID_DiffieHellman::fromBase64(
+ Auth_OpenID_array_get($query_data, 'openid.dh_modulus', null),
+ Auth_OpenID_array_get($query_data, 'openid.dh_gen', null));
+
+ $composite = Auth_OpenID_CryptUtil::base64ToLong(
+ $query_data['openid.dh_consumer_public']);
+
+ $enc_mac_key = Auth_OpenID_CryptUtil::toBase64(
+ $d->xorSecret($composite, $assoc_secret));
+
+ $reply_dict = array(
+ 'assoc_type' => 'HMAC-SHA1',
+ 'assoc_handle' => $assoc_handle,
+ 'expires_in' => '600',
+ 'session_type' => 'DH-SHA1',
+ 'dh_server_public' =>
+ Auth_OpenID_CryptUtil::longToBase64($d->public),
+ 'enc_mac_key' => $enc_mac_key,
+ );
+
+ return Auth_OpenID_KVForm::arrayToKV($reply_dict);
+}
+
+class Auth_OpenID_TestFetcher {
+ function Auth_OpenID_TestFetcher($user_url, $user_page,
+ $assoc_secret, $assoc_handle)
+ {
+ $this->get_responses = array($user_url => array(200,
+ $user_url,
+ $user_page));
+ $this->assoc_secret = $assoc_secret;
+ $this->assoc_handle = $assoc_handle;
+ $this->num_assocs = 0;
+ }
+
+ function response($url, $body)
+ {
+ if ($body === null) {
+ return array(404, $url, 'Not found');
+ } else {
+ return array(200, $url, $body);
+ }
+ }
+
+ function get($url)
+ {
+ if (array_key_exists($url, $this->get_responses)) {
+ return $this->get_responses[$url];
+ } else {
+ return $this->response($url, null);
+ }
+ }
+
+ function post($url, $body)
+ {
+ if (strpos($body, 'openid.mode=associate') !== false) {
+ $response = Auth_OpenID_associate($body, $this->assoc_secret,
+ $this->assoc_handle);
+ $this->num_assocs++;
+ return $this->response($url, $response);
+ } else {
+ return $this->response($url, null);
+ }
+ }
+}
+
+$_Auth_OpenID_user_page_pat = "<html>
+ <head>
+ <title>A user page</title>
+ %s
+ </head>
+ <body>
+ blah blah
+ </body>
+</html>";
+
+$_Auth_OpenID_server_url = "http://server.example.com/";
+$_Auth_OpenID_consumer_url = "http://consumer.example.com/";
+
+class Tests_Auth_OpenID_Consumer extends PHPUnit_TestCase {
+
+ function _run(&$consumer, $user_url, $mode, $delegate_url,
+ &$fetcher, &$store)
+ {
+ global $Auth_OpenID_SUCCESS,
+ $_Auth_OpenID_consumer_url,
+ $_Auth_OpenID_server_url;
+
+ list($status, $info) = $consumer->beginAuth($user_url);
+ $this->assertEquals($status, $Auth_OpenID_SUCCESS);
+
+ $return_to = $_Auth_OpenID_consumer_url;
+ $trust_root = $_Auth_OpenID_consumer_url;
+ $redirect_url = $consumer->constructRedirect($info, $return_to,
+ $trust_root);
+
+ $parsed = parse_url($redirect_url);
+ $qs = $parsed['query'];
+ $q = Auth_OpenID_parse($qs);
+
+ $this->assertEquals($q, array(
+ 'openid.mode' => $mode,
+ 'openid.identity' => $delegate_url,
+ 'openid.trust_root' => $trust_root,
+ 'openid.assoc_handle' =>
+ $fetcher->assoc_handle,
+ 'openid.return_to' => $return_to
+ ));
+
+ $this->assertEquals(strpos($redirect_url, $_Auth_OpenID_server_url),
+ 0);
+
+ $query = array(
+ 'openid.mode'=> 'id_res',
+ 'openid.return_to'=> $return_to,
+ 'openid.identity'=> $delegate_url,
+ 'openid.assoc_handle'=> $fetcher->assoc_handle,
+ );
+
+ $assoc = $store->getAssociation($_Auth_OpenID_server_url,
+ $fetcher->assoc_handle);
+
+ $assoc->addSignature(array('mode', 'return_to', 'identity'), $query);
+
+ list($status, $info) = $consumer->completeAuth($info->token, $query);
+
+ $this->assertEquals($status, $Auth_OpenID_SUCCESS);
+ $this->assertEquals($info, $user_url);
+ }
+
+ function _test_success($user_url, $delegate_url, $links, $immediate = false)
+ {
+ global $_Auth_OpenID_filestore_base_dir,
+ $_Auth_OpenID_server_url,
+ $_Auth_OpenID_user_page_pat,
+ $_Auth_OpenID_assocs;
+
+ $store = new Auth_OpenID_FileStore(
+ Auth_OpenID_mkdtemp($_Auth_OpenID_filestore_base_dir));
+
+ if ($immediate) {
+ $mode = 'checkid_immediate';
+ } else {
+ $mode = 'checkid_setup';
+ }
+
+ $user_page = sprintf($_Auth_OpenID_user_page_pat, $links);
+ $fetcher = new Auth_OpenID_TestFetcher($user_url, $user_page,
+ $_Auth_OpenID_assocs[0][0],
+ $_Auth_OpenID_assocs[0][1]);
+
+ $consumer = new Auth_OpenID_Consumer($store, &$fetcher, $immediate);
+
+ $this->assertEquals($fetcher->num_assocs, 0);
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store);
+
+ $this->assertEquals($fetcher->num_assocs, 1);
+
+ // Test that doing it again uses the existing association
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store);
+
+ $this->assertEquals($fetcher->num_assocs, 1);
+
+ // Another association is created if we remove the existing one
+ $store->removeAssociation($_Auth_OpenID_server_url,
+ $fetcher->assoc_handle);
+
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store);
+
+ $this->assertEquals($fetcher->num_assocs, 2);
+
+ // Test that doing it again uses the existing association
+ $this->_run($consumer, $user_url, $mode, $delegate_url,
+ $fetcher, $store);
+
+ $this->assertEquals($fetcher->num_assocs, 2);
+
+ $store->destroy();
+ }
+
+ function test_success()
+ {
+ global $_Auth_OpenID_server_url;
+
+ $user_url = 'http://www.example.com/user.html';
+ $links = sprintf('<link rel="openid.server" href="%s" />',
+ $_Auth_OpenID_server_url);
+
+ $delegate_url = 'http://consumer.example.com/user';
+ $delegate_links = sprintf('<link rel="openid.server" href="%s" />'.
+ '<link rel="openid.delegate" href="%s" />',
+ $_Auth_OpenID_server_url, $delegate_url);
+
+ $this->_test_success($user_url, $user_url, $links);
+ $this->_test_success($user_url, $user_url, $links, true);
+ $this->_test_success($user_url, $delegate_url, $delegate_links);
+ $this->_test_success($user_url, $delegate_url, $delegate_links, true);
+ }
+
+ function test_bad_fetch()
+ {
+ global $_Auth_OpenID_filestore_base_dir,
+ $Auth_OpenID_HTTP_FAILURE;
+
+ $store = new Auth_OpenID_FileStore(
+ Auth_OpenID_mkdtemp($_Auth_OpenID_filestore_base_dir));
+
+ $fetcher = new Auth_OpenID_TestFetcher(null, null, null, null);
+ $consumer = new Auth_OpenID_Consumer($store, &$fetcher);
+ $cases = array(
+ array(null, 'http://network.error/'),
+ array(404, 'http://not.found/'),
+ array(400, 'http://bad.request/'),
+ array(500, 'http://server.error/')
+ );
+
+ foreach ($cases as $case) {
+ list($error_code, $url) = $case;
+ $fetcher->get_responses[$url] = array($error_code, $url, null);
+ list($status, $info) = $consumer->beginAuth($url);
+ $this->assertEquals($status, $Auth_OpenID_HTTP_FAILURE);
+ $this->assertEquals($info, $error_code);
+ }
+
+ $store->destroy();
+ }
+
+ function test_bad_parse()
+ {
+ global $_Auth_OpenID_filestore_base_dir,
+ $Auth_OpenID_PARSE_ERROR;
+
+ $store = new Auth_OpenID_FileStore(
+ Auth_OpenID_mkdtemp($_Auth_OpenID_filestore_base_dir));
+
+ $user_url = 'http://user.example.com/';
+ $cases = array(
+ '',
+ "http://not.in.a.link.tag/",
+ '<link rel="openid.server" href="not.in.html.or.head" />'
+ );
+
+ foreach ($cases as $user_page) {
+ $fetcher = new Auth_OpenID_TestFetcher($user_url, $user_page,
+ null, null);
+ $consumer = new Auth_OpenID_Consumer($store, $fetcher);
+ list($status, $info) = $consumer->beginAuth($user_url);
+ $this->assertEquals($status, $Auth_OpenID_PARSE_ERROR);
+ $this->assertNull($info);
+ }
+
+ $store->destroy();
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/CryptUtil.php b/Tests/Auth/OpenID/CryptUtil.php
new file mode 100644
index 0000000..cb8dba1
--- /dev/null
+++ b/Tests/Auth/OpenID/CryptUtil.php
@@ -0,0 +1,306 @@
+<?php
+
+/**
+ * Tests for the CryptUtil functions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('PHPUnit.php');
+require_once('Auth/OpenID/CryptUtil.php');
+
+class Tests_Auth_OpenID_ByteOps extends PHPUnit_TestCase {
+ function test_length()
+ {
+ $cases = array(1, 10, 255);
+ foreach ($cases as $length) {
+ $data = Auth_OpenID_CryptUtil::getBytes($length);
+ $this->assertEquals(strlen($data), $length);
+ }
+ }
+
+ function test_different()
+ {
+ $num_iterations = 100;
+ $data_length = 20;
+
+ $data = Auth_OpenID_CryptUtil::getBytes($num_iterations);
+ for ($i = 0; $i < $num_iterations; $i++) {
+ $last = $data;
+ $data = Auth_OpenID_CryptUtil::getBytes($num_iterations);
+ $this->assertFalse($data == $last);
+ }
+ }
+
+ function test_cryptrand()
+ {
+ // It's possible, but HIGHLY unlikely that a correct
+ // implementation will fail by returning the same number twice
+
+ $s = Auth_OpenID_CryptUtil::getBytes(32);
+ $t = Auth_OpenID_CryptUtil::getBytes(32);
+ $this->assertEquals(strlen($s), 32);
+ $this->assertEquals(strlen($t), 32);
+ $this->assertFalse($s == $t);
+ }
+
+ function test_strxor()
+ {
+ $NUL = "\x00";
+
+ $cases = array(
+ array($NUL, $NUL, $NUL),
+ array("\x01", $NUL, "\x01"),
+ array("a", "a", $NUL),
+ array("a", $NUL, "a"),
+ array("abc", str_repeat($NUL, 3), "abc"),
+ array(str_repeat("x", 10),
+ str_repeat($NUL, 10),
+ str_repeat("x", 10)),
+ array("\x01", "\x02", "\x03"),
+ array("\xf0", "\x0f", "\xff"),
+ array("\xff", "\x0f", "\xf0"),
+ );
+
+ while (list($index, $values) = each($cases)) {
+ list($aa, $bb, $expected) = $values;
+ $actual = Auth_OpenID_CryptUtil::strxor($aa, $bb);
+ $this->assertEquals($actual, $expected);
+ }
+
+ $exc_cases = array(
+ array('', 'a'),
+ array('foo', 'ba'),
+ array(str_repeat($NUL, 3),
+ str_repeat($NUL, 4)),
+ array(implode('', array_map('chr',
+ range(0, 255))),
+ implode('', array_map('chr',
+ range(0, 127))))
+ );
+
+ while(list($index, $values) = each($exc_cases)) {
+ list($aa, $bb) = $values;
+ $unexpected = Auth_OpenID_CryptUtil::strxor($aa, $bb);
+ $this->assertNull($unexpected);
+ }
+ }
+
+ function test_reversed()
+ {
+ $cases = array(
+ array('', ''),
+ array('a', 'a'),
+ array('ab', 'ba'),
+ array('abc', 'cba'),
+ array('abcdefg', 'gfedcba'),
+ array(array(), array()),
+ array(array(1), array(1)),
+ array(array(1,2), array(2,1)),
+ array(array(1,2,3), array(3,2,1)),
+ array(range(0, 999), array_reverse(range(0, 999)))
+ );
+
+ while (list($index, $values) = each($cases)) {
+ list($case, $expected) = $values;
+ $actual = Auth_OpenID_CryptUtil::reversed($case);
+ $this->assertEquals($actual, $expected);
+ $twice = Auth_OpenID_CryptUtil::reversed($actual);
+ $this->assertEquals($twice, $case);
+ }
+ }
+}
+
+class Tests_Auth_OpenID_BinLongConvertRnd extends PHPUnit_TestCase {
+ var $lib;
+ var $max;
+
+ function Tests_Auth_OpenID_BinLongConvertRnd(&$lib, $max)
+ {
+ $this->lib =& $lib;
+ $this->max = $max;
+ }
+
+ function runTest()
+ {
+ $n = $this->lib->init(0);
+ foreach (range(0, 9) as $i) {
+ $rnd = Auth_OpenID_CryptUtil::randrange($this->max);
+ $n = $this->lib->add($n, $rnd);
+ }
+ $s = Auth_OpenID_CryptUtil::longToBinary($n);
+ $this->assertTrue(is_string($s));
+ $n_prime = Auth_OpenID_CryptUtil::binaryToLong($s);
+ $this->assertEquals($this->lib->cmp($n, $n_prime), 0);
+ }
+}
+
+class Tests_Auth_OpenID_BinLongConvert extends PHPUnit_TestCase {
+ var $lib;
+ var $bin;
+ var $lng;
+
+ function Tests_Auth_OpenID_BinLongConvert(&$lib, $bin, $lng)
+ {
+ $this->lib =& $lib;
+ $this->bin = $bin;
+ $this->lng = $lng;
+ }
+
+ function runTest()
+ {
+ $n_prime = Auth_OpenID_CryptUtil::binaryToLong($this->bin);
+ $s_prime = Auth_OpenID_CryptUtil::longToBinary($this->lng);
+ $this->assertEquals($this->lib->cmp($this->lng, $n_prime), 0);
+ $this->assertTrue($this->bin == $s_prime);
+ }
+}
+
+class Tests_Auth_OpenID_Base64ToLong extends PHPUnit_TestCase {
+ var $num;
+ var $b64;
+ var $lib;
+
+ function Tests_Auth_OpenID_Base64ToLong(&$lib, $b64, $num)
+ {
+ $this->lib = $lib;
+ $this->b64 = $b64;
+ $this->num = $num;
+ }
+
+ function runTest()
+ {
+ $actual = Auth_OpenID_CryptUtil::base64ToLong($this->b64);
+ $this->assertTrue($this->lib->cmp($this->num, $actual) == 0);
+ }
+}
+
+class Tests_Auth_OpenID_LongToBase64 extends Tests_Auth_OpenID_Base64ToLong {
+ function Tests_Auth_OpenID_LongToBase64(&$lib, $b64, $num)
+ {
+ $this->lib = $lib;
+ $this->b64 = $b64;
+ $this->num = $num;
+ }
+
+ function runTest()
+ {
+ $actual = Auth_OpenID_CryptUtil::longToBase64($this->num);
+ $this->assertEquals($this->b64, $actual);
+ }
+}
+
+class Tests_Auth_OpenID_RandRange extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_RandRange(&$lib)
+ {
+ $this->lib =& $lib;
+ }
+
+ function runTest()
+ {
+ $stop = $this->lib->pow(2, 128);
+ $a = Auth_OpenID_CryptUtil::randrange($stop);
+ $b = Auth_OpenID_CryptUtil::randrange($stop);
+
+ $this->assertFalse($this->lib->cmp($b, $a) == 0, "Same: $a $b");
+
+ $n = $this->lib->init(Auth_OpenID_CryptUtil::maxint());
+ $n = $this->lib->add($n, 1);
+
+ // Make sure that we can generate random numbers that are
+ // larger than platform int size
+ $result = Auth_OpenID_CryptUtil::randrange($n);
+
+ // What can we say about the result?
+ }
+}
+
+class Tests_Auth_OpenID_CryptUtil extends PHPUnit_TestSuite {
+ function _parseBase64Data()
+ {
+ $lines = file_get_contents('Tests/n2b64', true);
+ $lines = explode("\n", $lines);
+
+ $data = array();
+ foreach ($lines as $line) {
+ if (!$line) {
+ continue;
+ }
+ list($b64, $ascii) = explode(' ', $line);
+ $data[$b64] = $ascii;
+ }
+ return $data;
+ }
+
+ function Tests_Auth_OpenID_CryptUtil($name)
+ {
+ $this->setName($name);
+
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ $this->addTestSuite('Tests_Auth_OpenID_BigInt');
+
+ $lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+ $max = Auth_OpenID_CryptUtil::maxint();
+ $upper = defined('Tests_Auth_OpenID_thorough') ? 499 : 3;
+
+ foreach (range(0, $upper) as $iteration) {
+ $test = new Tests_Auth_OpenID_BinLongConvertRnd($lib, $max);
+ $test->setName("BinLongConvertRnd " . strval($iteration));
+ $this->addTest($test);
+ }
+
+ $cases = array(
+ "\x00" => 0,
+ "\x01" => 1,
+ "\x00\xFF" => 255,
+ "\x00\x80" => 128,
+ "\x00\x81" => 129,
+ "\x00\x80\x00" => 32768,
+ "OpenID is cool" => "1611215304203901150134421257416556"
+ );
+
+ foreach ($cases as $bin => $lng_m) {
+ $lng = $lib->init($lng_m);
+ $test = new Tests_Auth_OpenID_BinLongConvert($lib, $bin, $lng);
+ $test->setName('BinLongConvert ' . bin2hex($bin));
+ $this->addTest($test);
+ }
+
+ $count = defined('Tests_Auth_OpenID_thorough') ? -1 : 2;
+ $data = $this->_parseBase64Data();
+ foreach ($data as $b64 => $num_s) {
+ // Only test the first few unless thorough is defined
+ if (strlen($num_s) > 5) {
+ if ($count == 0) {
+ break;
+ } else {
+ $count -= 1;
+ }
+ }
+ $num = $lib->init($num_s);
+ $test = new Tests_Auth_OpenID_Base64ToLong($lib, $b64, $num);
+ $test->setName("B64->Long $num_s");
+ $this->addTest($test);
+
+ $test = new Tests_Auth_OpenID_LongToBase64($lib, $b64, $num);
+ $test->setName("Long->B64 $num_s");
+ $this->addTest($test);
+ }
+
+ $test = new Tests_Auth_OpenID_RandRange($lib);
+ $test->setName('Big number randrange');
+ $this->addTest($test);
+ }
+
+ $this->addTestSuite('Tests_Auth_OpenID_ByteOps');
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/DiffieHellman.php b/Tests/Auth/OpenID/DiffieHellman.php
new file mode 100644
index 0000000..9f6cd1a
--- /dev/null
+++ b/Tests/Auth/OpenID/DiffieHellman.php
@@ -0,0 +1,170 @@
+<?php
+
+/**
+ * Tests for the Diffie-Hellman key exchange implementation in the
+ * OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('PHPUnit.php');
+require_once('Auth/OpenID/DiffieHellman.php');
+
+class Tests_Auth_OpenID_DiffieHellman_CheckCases extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_DiffieHellman_CheckCases($cases, $n)
+ {
+ $this->cases = $cases;
+ $this->n = $n;
+ }
+
+ function runTest()
+ {
+ $this->assertEquals($this->n, count($this->cases));
+ }
+}
+
+class Tests_Auth_OpenID_DiffieHellman_Private extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_DiffieHellman_Private($name, $input, $expected)
+ {
+ $this->setName("$name");
+ $this->input = $input;
+ $this->expected = $expected;
+ }
+
+ function runTest()
+ {
+ $dh = new Auth_OpenID_DiffieHellman(null, null, $this->input);
+ $this->assertEquals($this->expected, $dh->getPublicKey());
+ }
+}
+
+class Tests_Auth_OpenID_DiffieHellman_Exch extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_DiffieHellman_Exch($name, $p1, $p2, $shared)
+ {
+ $this->setName("$name");
+ $this->p1 = $p1;
+ $this->p2 = $p2;
+ $this->shared = $shared;
+ }
+
+ function runTest()
+ {
+ $lib =& Auth_OpenID_MathLibrary::getLibWrapper();
+ $shared = $lib->init($this->shared);
+ $dh1 = new Auth_OpenID_DiffieHellman(null, null, $this->p1);
+ $dh2 = new Auth_OpenID_DiffieHellman(null, null, $this->p2);
+ $sh1 = $dh1->getSharedSecret($dh2->getPublicKey());
+ $sh2 = $dh2->getSharedSecret($dh1->getPublicKey());
+ $this->assertEquals($lib->cmp($shared, $sh1), 0);
+ $this->assertEquals($lib->cmp($shared, $sh2), 0);
+ }
+}
+
+class Tests_Auth_OpenID_DiffieHellman extends PHPUnit_TestSuite {
+ function _getLines($base)
+ {
+ $path = dirname(realpath(__FILE__));
+ $dh_test_data_file = $path . DIRECTORY_SEPARATOR . $base;
+ $lines = file($dh_test_data_file);
+ if ($lines === false) {
+ trigger_error("Failed to open data file: $dh_test_data_file",
+ E_USER_ERROR);
+ }
+ return $lines;
+ }
+
+ function _readPrivateTestCases()
+ {
+ $lines = Tests_Auth_OpenID_DiffieHellman::_getLines('dhpriv');
+ $cases = array();
+ foreach ($lines as $line) {
+ $case = array();
+ if (!preg_match('/^(\d+) (\d+)\n$/', $line, $case)) {
+ trigger_error("Bad test input: $line", E_USER_ERROR);
+ }
+
+ $c = count($case);
+ if ($c != 3) {
+ trigger_error("Wrong number of elements in parsed case: $c",
+ E_USER_ERROR);
+ }
+
+ array_shift($case);
+ $cases[] = $case;
+ }
+
+ return $cases;
+ }
+
+ function _readExchTestCases()
+ {
+ $lines = Tests_Auth_OpenID_DiffieHellman::_getLines('dhexch');
+ $cases = array();
+ foreach ($lines as $line) {
+ $case = array();
+ if (!preg_match('/^(\d+) (\d+) (\d+)\n$/', $line, $case)) {
+ trigger_error("Bad test input: $line", E_USER_ERROR);
+ }
+
+ $c = count($case);
+ if ($c != 4) {
+ trigger_error("Wrong number of elements in parsed case: $c",
+ E_USER_ERROR);
+ }
+
+ array_shift($case);
+ $cases[] = $case;
+ }
+ return $cases;
+ }
+
+ function Tests_Auth_OpenID_DiffieHellman($name)
+ {
+ $this->setName($name);
+
+ $priv_cases = Tests_Auth_OpenID_DiffieHellman::_readPrivateTestCases();
+ $sanity = new Tests_Auth_OpenID_DiffieHellman_CheckCases(
+ $priv_cases, 29);
+ $sanity->setName('Check parsing of priv test data');
+ $this->addTest($sanity);
+
+ $exch_cases = Tests_Auth_OpenID_DiffieHellman::_readExchTestCases();
+ $sanity = new Tests_Auth_OpenID_DiffieHellman_CheckCases(
+ $exch_cases, 25);
+ $sanity->setName('Check parsing of exch test data');
+ $this->addTest($sanity);
+
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ if (defined('Tests_Auth_OpenID_thorough')) {
+ $npriv = count($priv_cases);
+ $nexch = count($exch_cases);
+ } else {
+ $npriv = 1;
+ $nexch = 3;
+ }
+
+ for ($i = 0; $i < $npriv; $i++) {
+ $case = $priv_cases[$i];
+ $one = new Tests_Auth_OpenID_DiffieHellman_Private(
+ $i, $case[0], $case[1]);
+ $this->addTest($one);
+ }
+
+ for ($i = 0; $i < $nexch; $i++) {
+ $case = $exch_cases[$i];
+ $one = new Tests_Auth_OpenID_DiffieHellman_Exch(
+ $i, $case[0], $case[1], $case[2]);
+ $this->addTest($one);
+ }
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/HMACSHA1.php b/Tests/Auth/OpenID/HMACSHA1.php
new file mode 100644
index 0000000..00b6b6c
--- /dev/null
+++ b/Tests/Auth/OpenID/HMACSHA1.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * Tests for the HMAC-SHA1 utility functions used by the OpenID
+ * library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('PHPUnit.php');
+require_once('Auth/OpenID/HMACSHA1.php');
+
+class Tests_Auth_OpenID_HMACSHA1_TestCase extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_HMACSHA1_TestCase(
+ $name, $key, $data, $expected)
+ {
+
+ $this->setName($name);
+ $this->key = $key;
+ $this->data = $data;
+ $this->expected = $expected;
+ }
+
+ function runTest()
+ {
+ $actual = Auth_OpenID_HMACSHA1($this->key, $this->data);
+ $this->assertEquals($this->expected, $actual);
+ }
+}
+
+class Tests_Auth_OpenID_HMACSHA1 extends PHPUnit_TestSuite {
+ function _strConvert($s)
+ {
+ $repeat_pat = '/^0x([a-f0-9]{2}) repeated (\d+) times$/';
+ if (preg_match($repeat_pat, $s, $match)) {
+ $c = chr(hexdec($match[1]));
+ $n = $match[2];
+ $data = '';
+ for ($i = 0; $i < $n; $i++) {
+ $data .= $c;
+ }
+ } elseif (substr($s, 0, 2) == "0x") {
+ $data = pack('H*', substr($s, 2, strlen($s) - 1));
+ } elseif (preg_match('/^"(.*)"$/', $s, $match)) {
+ $data = $match[1];
+ } else {
+ trigger_error("Bad data format: $s", E_USER_ERROR);
+ }
+ return $data;
+ }
+
+ function _readTestCases()
+ {
+ $path = dirname(realpath(__FILE__));
+ $hmac_test_data_file = $path . DIRECTORY_SEPARATOR . 'hmac.txt';
+ $lines = file($hmac_test_data_file);
+ if ($lines === false) {
+ trigger_error("Failed to open data file: $dh_test_data_file",
+ E_USER_ERROR);
+ return false;
+ }
+
+ $cases = array();
+ $case = array();
+ foreach ($lines as $line) {
+ if ($line{0} == "#") {
+ continue;
+ }
+
+ // Blank line separates test cases
+ if ($line == "\n") {
+ $cases[] = $case;
+ $case = array();
+ } else {
+ $match = array();
+ $pat = '/^([a-z0-9_-]+) =\s+(.*?)\n$/';
+ if (!preg_match($pat, $line, $match)) {
+ trigger_error("Bad test input: $line", E_USER_ERROR);
+ }
+
+ $c = count($match);
+ if ($c != 3) {
+ trigger_error(
+ "Wrong number of elements in parsed case: $c",
+ E_USER_ERROR);
+ return false;
+ }
+
+ $key = $match[1];
+ $value = $match[2];
+ $case[$key] = $value;
+ }
+ }
+
+ if (count($case)) {
+ $cases[] = $case;
+ }
+
+ $final = array();
+
+ // Normalize strings and check data integrity
+ foreach ($cases as $case) {
+ $clean = array();
+ $clean["key"] =
+ Tests_Auth_OpenID_HMACSHA1::_strConvert($case["key"]);
+ if (strlen($clean["key"]) != $case["key_len"]) {
+ trigger_error("Bad key length", E_USER_ERROR);
+ }
+
+ $clean["data"] =
+ Tests_Auth_OpenID_HMACSHA1::_strConvert($case["data"]);
+ if (strlen($clean["data"]) != $case["data_len"]) {
+ trigger_error("Bad data length", E_USER_ERROR);
+ }
+
+ $clean["digest"] =
+ Tests_Auth_OpenID_HMACSHA1::_strConvert($case["digest"]);
+ if (strlen($clean["digest"]) != 20) {
+ $l = strlen($clean["digest"]);
+ trigger_error("Bad digest length: $l", E_USER_ERROR);
+ }
+
+ $clean['test_case'] = $case['test_case'];
+
+ $final[] = $clean;
+ }
+ return $final;
+ }
+
+ function Tests_Auth_OpenID_HMACSHA1($name)
+ {
+ $this->setName($name);
+ $cases = $this->_readTestCases();
+ foreach ($cases as $case) {
+ $test = new Tests_Auth_OpenID_HMACSHA1_TestCase(
+ $case['test_case'],
+ $case['key'],
+ $case['data'],
+ $case['digest']
+ );
+
+ $digest = $case['digest'];
+ $this->addTest($test);
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/KVForm.php b/Tests/Auth/OpenID/KVForm.php
new file mode 100644
index 0000000..f103c18
--- /dev/null
+++ b/Tests/Auth/OpenID/KVForm.php
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * Tests for the KVForm module.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('PHPUnit.php');
+require_once('Auth/OpenID/KVForm.php');
+
+$_Tests_Auth_OpenID_kverrors = null;
+/**
+ * Keep a list of the logged errors
+ */
+function Tests_Auth_OpenID_kvHandleError($errno, $errmsg)
+{
+ global $_Tests_Auth_OpenID_kverrors;
+ $_Tests_Auth_OpenID_kverrors[] = $errmsg;
+}
+
+
+class Tests_Auth_OpenID_KVForm_TestCase extends PHPUnit_TestCase {
+ var $errs;
+
+ function runTest()
+ {
+ // Re-set the number of logged errors
+ global $_Tests_Auth_OpenID_kverrors;
+ $_Tests_Auth_OpenID_kverrors = array();
+
+ set_error_handler("Tests_Auth_OpenID_kvHandleError");
+
+ $this->_runTest();
+
+ // Check to make sure we have the expected number of logged errors
+ //$this->assertEquals($this->errs, count($_Tests_Auth_OpenID_kverrors));
+
+ restore_error_handler();
+ }
+
+ function _runTest()
+ {
+ trigger_error('Must be overridden', E_USER_ERROR);
+ }
+}
+
+class Tests_Auth_OpenID_KVForm_TestCase_Parse
+extends Tests_Auth_OpenID_KVForm_TestCase {
+ function Tests_Auth_OpenID_KVForm_TestCase_Parse(
+ $arr, $str, $lossy, $errs)
+ {
+
+ $this->arr = $arr;
+ $this->str = $str;
+ $this->lossy = $lossy;
+ $this->errs = $errs;
+ }
+
+ function _runTest()
+ {
+ // Do one parse, after which arrayToKV and kvToArray should be
+ // inverses.
+ $parsed1 = Auth_OpenID_KVForm::kvToArray($this->str);
+ $serial1 = Auth_OpenID_KVForm::arrayToKV($this->arr);
+
+ if ($this->lossy == "neither" || $this->lossy == "str") {
+ $this->assertEquals($this->arr, $parsed1, "str was lossy");
+ }
+
+ if ($this->lossy == "neither" || $this->lossy == "arr") {
+ $this->assertEquals($this->str, $serial1, "array was lossy");
+ }
+
+ $parsed2 = Auth_OpenID_KVForm::kvToArray($serial1);
+ $serial2 = Auth_OpenID_KVForm::arrayToKV($parsed1);
+
+ // Round-trip both
+ $parsed3 = Auth_OpenID_KVForm::kvToArray($serial2);
+ $serial3 = Auth_OpenID_KVForm::arrayToKV($parsed2);
+
+ $this->assertEquals($serial2, $serial3, "serialized forms differ");
+
+ // Check to make sure that they're inverses.
+ $this->assertEquals($parsed2, $parsed3, "parsed forms differ");
+
+ }
+}
+
+class Tests_Auth_OpenID_KVForm_TestCase_Null
+extends Tests_Auth_OpenID_KVForm_TestCase {
+ function Tests_Auth_OpenID_KVForm_TestCase_Null($arr, $errs)
+ {
+ $this->arr = $arr;
+ $this->errs = $errs;
+ }
+
+ function _runTest()
+ {
+ $serialized = Auth_OpenID_KVForm::arrayToKV($this->arr);
+ $this->assertTrue($serialized === null,
+ 'serialization unexpectedly succeeded');
+ }
+}
+
+class Tests_Auth_OpenID_KVForm extends PHPUnit_TestSuite {
+ function Tests_Auth_OpenID_KVForm($name)
+ {
+ $this->setName($name);
+ $testdata_list = array(
+ array("name" => "simple",
+ "str" => "college:harvey mudd\n",
+ "arr" => array("college" => "harvey mudd"),
+ ),
+ array("name" => "empty",
+ "str" => "",
+ "arr" => array(),
+ ),
+ array("name" => "empty (just newline)",
+ "str" => "\n",
+ "arr" => array(),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "empty (double newline)",
+ "str" => "\n\n",
+ "arr" => array(),
+ "lossy" => "str",
+ "errors" => 2,
+ ),
+ array("name" => "empty (no colon)",
+ "str" => "East is least\n",
+ "arr" => array(),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "two keys",
+ "str" => "city:claremont\nstate:CA\n",
+ "arr" => array('city' => 'claremont',
+ 'state' => 'CA'),
+ ),
+ array("name" => "real life",
+ "str" => "is_valid:true\ninvalidate_handle:" .
+ "{HMAC-SHA1:2398410938412093}\n",
+ "arr" => array('is_valid' => 'true',
+ 'invalidate_handle' =>
+ '{HMAC-SHA1:2398410938412093}'),
+ ),
+ array("name" => "empty key and value",
+ "str" => ":\n",
+ "arr" => array(''=>''),
+ ),
+ array("name" => "empty key, not value",
+ "str" => ":missing key\n",
+ "arr" => array(''=>'missing key'),
+ ),
+ array("name" => "whitespace at front of key",
+ "str" => " street:foothill blvd\n",
+ "arr" => array('street'=>'foothill blvd'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "whitespace at front of value",
+ "str" => "major: computer science\n",
+ "arr" => array('major'=>'computer science'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "whitespace around key and value",
+ "str" => " dorm : east \n",
+ "arr" => array('dorm'=>'east'),
+ "lossy" => "str",
+ "errors" => 2,
+ ),
+ array("name" => "missing trailing newline",
+ "str" => "e^(i*pi)+1:0",
+ "arr" => array('e^(i*pi)+1'=>'0'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "missing trailing newline (two key)",
+ "str" => "east:west\nnorth:south",
+ "arr" => array('east'=>'west',
+ 'north'=>'south'),
+ "lossy" => "str",
+ "errors" => 1,
+ ),
+ array("name" => "colon in key",
+ "arr" => array("k:k" => 'v'),
+ "errors" => 1,
+ ),
+ array("name" => "newline in key",
+ "arr" => array("k\nk" => 'v'),
+ "errors" => 1,
+ ),
+ array("name" => "newline in value",
+ "arr" => array('k' => "v\nv"),
+ "errors" => 1,
+ ),
+ array("name" => "array whitespace",
+ "arr" => array(" k " => "v"),
+ "lossy" => "both",
+ "str" => " k :v\n",
+ "errors" => 2,
+ ),
+ array("name" => "array ordering 1",
+ "arr" => array("a" => "x",
+ "b" => "x",
+ "c" => "x"),
+ "str" => "a:x\nb:x\nc:x\n",
+ ),
+ array("name" => "array ordering 2",
+ "arr" => array("a" => "x",
+ "c" => "x",
+ "b" => "x"),
+ "str" => "a:x\nc:x\nb:x\n",
+ ),
+ );
+
+ foreach ($testdata_list as $testdata) {
+ if (isset($testdata['str'])) {
+ $str = $testdata['str'];
+ } else {
+ $str = null;
+ }
+
+ $arr = $testdata["arr"];
+
+ if (isset($testdata['errors'])) {
+ $errs = $testdata["errors"];
+ } else {
+ $errs = 0;
+ }
+
+ if (is_null($str)) {
+ $test = new Tests_Auth_OpenID_KVForm_TestCase_Null($arr, $errs);
+ } else {
+ if (isset($testdata['lossy'])) {
+ $lossy = $testdata["lossy"];
+ } else {
+ $lossy = 'neither';
+ }
+ $test = new Tests_Auth_OpenID_KVForm_TestCase(
+ $arr, $str, $lossy, $errs);
+ }
+ $test->setName($testdata["name"]);
+ $this->addTest($test);
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/OIDUtil.php b/Tests/Auth/OpenID/OIDUtil.php
new file mode 100644
index 0000000..f692eb4
--- /dev/null
+++ b/Tests/Auth/OpenID/OIDUtil.php
@@ -0,0 +1,260 @@
+<?php
+
+/**
+ * Tests for utility functions used by the OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('PHPUnit.php');
+require_once('Auth/OpenID/OIDUtil.php');
+
+class Tests_Auth_OpenID_OIDUtil extends PHPUnit_TestCase {
+ function test_base64()
+ {
+ // This is not good for international use, but PHP doesn't
+ // appear to provide access to the local alphabet.
+ $letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ $digits = "0123456789";
+ $extra = "+/=";
+ $allowed_s = $letters . $digits . $extra;
+ $allowed_d = array();
+
+ for ($i = 0; $i < strlen($allowed_s); $i++) {
+ $c = $allowed_s[$i];
+ $allowed_d[$c] = null;
+ }
+
+ function checkEncoded($obj, $str, $allowed_array)
+ {
+ for ($i = 0; $i < strlen($str); $i++) {
+ $obj->assertTrue(array_key_exists($str[$i],
+ $allowed_array));
+ }
+ }
+
+ $cases = array(
+ "",
+ "x",
+ "\x00",
+ "\x01",
+ str_repeat("\x00", 100),
+ implode("", array_map('chr', range(0, 255)))
+ );
+
+ foreach ($cases as $s) {
+ $b64 = Auth_OpenID_toBase64($s);
+ checkEncoded($this, $b64, $allowed_d);
+ $s_prime = Auth_OpenID_fromBase64($b64);
+ $this->assertEquals($s_prime, $s);
+ }
+
+ function random_ordinal($unused)
+ {
+ return rand(0, 255);
+ }
+
+ // Randomized test
+ foreach (range(0, 49) as $i) {
+ $n = rand(0, 2048);
+ $s = implode("", array_map('chr',
+ array_map('random_ordinal',
+ range(0, $n))));
+ $b64 = Auth_OpenID_toBase64($s);
+ checkEncoded($this, $b64, $allowed_d);
+ $s_prime = Auth_OpenID_fromBase64($b64);
+ $this->assertEquals($s_prime, $s);
+ }
+ }
+
+ function test_normalizeUrl()
+ {
+ $this->assertEquals("http://foo.com/",
+ Auth_OpenID_normalizeUrl("foo.com"));
+
+ $this->assertEquals("http://foo.com/",
+ Auth_OpenID_normalizeUrl("http://foo.com"));
+
+ $this->assertEquals("https://foo.com/",
+ Auth_OpenID_normalizeUrl("https://foo.com"));
+
+ $this->assertEquals("http://foo.com/bar",
+ Auth_OpenID_normalizeUrl("foo.com/bar"));
+
+ $this->assertEquals("http://foo.com/bar",
+ Auth_OpenID_normalizeUrl("http://foo.com/bar"));
+
+ $this->assertEquals("http://foo.com/",
+ Auth_OpenID_normalizeUrl("http://foo.com/"));
+
+ $this->assertEquals("https://foo.com/",
+ Auth_OpenID_normalizeUrl("https://foo.com/"));
+
+ $this->assertEquals("https://foo.com/bar" ,
+ Auth_OpenID_normalizeUrl("https://foo.com/bar"));
+
+ if (0) {
+ $this->assertEquals("http://foo.com/%E8%8D%89",
+ Auth_OpenID_normalizeUrl("foo.com/\u8349"));
+
+ $this->assertEquals("http://foo.com/%E8%8D%89",
+ Auth_OpenID_normalizeUrl("http://foo.com/\u8349"));
+ }
+
+ $non_ascii_domain_cases = array(
+ array("http://xn--vl1a.com/",
+ "\u8349.com"),
+
+ array("http://xn--vl1a.com/",
+ "http://\u8349.com"),
+
+ array("http://xn--vl1a.com/",
+ "\u8349.com/"),
+
+ array("http://xn--vl1a.com/",
+ "http://\u8349.com/"),
+
+ array("http://xn--vl1a.com/%E8%8D%89",
+ "\u8349.com/\u8349"),
+
+ array("http://xn--vl1a.com/%E8%8D%89",
+ "http://\u8349.com/\u8349"),
+ );
+
+ // XXX
+ /*
+ codecs.getencoder('idna')
+ except LookupError:
+ # If there is no idna codec, these cases with
+ # non-ascii-representable domain names should fail.
+ should_raise = True
+ else:
+ should_raise = False
+
+ for expected, case in non_ascii_domain_cases:
+try:
+actual = Auth_OpenID_normalizeUrl(case)
+ except UnicodeError:
+ assert should_raise
+ else:
+assert not should_raise and actual == expected, case
+ */
+
+ $this->assertNull(Auth_OpenID_normalizeUrl(null));
+ $this->assertNull(Auth_OpenID_normalizeUrl(''));
+ $this->assertNull(Auth_OpenID_normalizeUrl('http://'));
+ }
+
+ function test_appendArgs()
+ {
+
+ $simple = 'http://www.example.com/';
+
+ $cases = array(
+ array('empty list',
+ array($simple, array()),
+ $simple),
+
+ array('empty dict',
+ array($simple, array()),
+ $simple),
+
+ array('one list',
+ array($simple, array(array('a', 'b'))),
+ $simple . '?a=b'),
+
+ array('one dict',
+ array($simple, array('a' => 'b')),
+ $simple . '?a=b'),
+
+ array('two list (same)',
+ array($simple, array(array('a', 'b'),
+ array('a', 'c'))),
+ $simple . '?a=b&a=c'),
+
+ array('two list',
+ array($simple, array(array('a', 'b'),
+ array('b', 'c'))),
+ $simple . '?a=b&b=c'),
+
+ array('two list (order)',
+ array($simple, array(array('b', 'c'),
+ array('a', 'b'))),
+ $simple . '?b=c&a=b'),
+
+ array('two dict (order)',
+ array($simple, array('b' => 'c',
+ 'a' => 'b')),
+ $simple . '?a=b&b=c'),
+
+ array('escape',
+ array($simple, array(array('=', '='))),
+ $simple . '?%3D=%3D'),
+
+ array('escape (URL)',
+ array($simple, array(array('this_url',
+ $simple))),
+ $simple .
+ '?this_url=http%3A%2F%2Fwww.example.com%2F'),
+
+ array('use dots',
+ array($simple, array(array('openid.stuff',
+ 'bother'))),
+ $simple . '?openid.stuff=bother'),
+
+ array('args exist (empty)',
+ array($simple . '?stuff=bother', array()),
+ $simple . '?stuff=bother'),
+
+ array('args exist',
+ array($simple . '?stuff=bother',
+ array(array('ack', 'ack'))),
+ $simple . '?stuff=bother&ack=ack'),
+
+ array('args exist',
+ array($simple . '?stuff=bother',
+ array(array('ack', 'ack'))),
+ $simple . '?stuff=bother&ack=ack'),
+
+ array('args exist (dict)',
+ array($simple . '?stuff=bother',
+ array('ack' => 'ack')),
+ $simple . '?stuff=bother&ack=ack'),
+
+ array('args exist (dict 2)',
+ array($simple . '?stuff=bother',
+ array('ack' => 'ack', 'zebra' => 'lion')),
+ $simple . '?stuff=bother&ack=ack&zebra=lion'),
+
+ array('three args (dict)',
+ array($simple, array('stuff' => 'bother',
+ 'ack' => 'ack',
+ 'zebra' => 'lion')),
+ $simple . '?ack=ack&stuff=bother&zebra=lion'),
+
+ array('three args (list)',
+ array($simple, array(
+ array('stuff', 'bother'),
+ array('ack', 'ack'),
+ array('zebra', 'lion'))),
+ $simple . '?stuff=bother&ack=ack&zebra=lion'),
+ );
+
+ // Tests.
+ foreach ($cases as $case) {
+ list($desc, $data, $expected) = $case;
+ list($url, $query) = $data;
+ $this->assertEquals($expected,
+ Auth_OpenID_appendArgs($url, $query));
+ }
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/Parse.php b/Tests/Auth/OpenID/Parse.php
new file mode 100644
index 0000000..31cca3b
--- /dev/null
+++ b/Tests/Auth/OpenID/Parse.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Tests for the Consumer parsing functions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('Auth/OpenID/Consumer/Parse.php');
+
+class Tests_Auth_OpenID_Link extends PHPUnit_TestCase {
+ function Tests_Auth_OpenID_Link($case)
+ {
+ list($desc, $markup, $links, $case_text) = $case;
+ $this->desc = $desc;
+ $this->markup = $markup;
+ $this->expected_links = $links;
+ $this->case_text = $case_text;
+ }
+
+ function getName()
+ {
+ return $this->desc;
+ }
+
+ function runTest()
+ {
+ $parsed = Auth_OpenID_parseLinkAttrs($this->markup);
+ $i = 0;
+
+ foreach ($this->expected_links as $expected) {
+ list($is_optional_link, $expected_link) = $expected;
+ if ($is_optional_link &&
+ ($i >= count($parsed))) {
+ continue;
+ }
+
+ $act_link = $parsed[$i];
+
+ $increment = true;
+ foreach ($expected_link as $attr => $data) {
+ list($is_optional_attr, $value) = $data;
+
+ if ($is_optional_attr) {
+ $actual_value = null;
+ if (array_key_exists($attr, $act_link)) {
+ $actual_value = $act_link[$attr];
+ } else {
+ continue;
+ }
+ } else {
+ $actual_value = $act_link[$attr];
+ }
+
+ if ($is_optional_link &&
+ ($value != $actual_value)) {
+ $increment = false;
+ break;
+ }
+
+ $this->assertEquals($value, $actual_value);
+ }
+
+ if ($increment) {
+ $i++;
+ }
+ }
+
+ $this->assertEquals($i, count($parsed));
+ }
+}
+
+class NumTestCases extends PHPUnit_TestCase {
+ function NumTestCases($test_cases, $num_tests)
+ {
+ $this->test_cases = $test_cases;
+ $this->num_tests = $num_tests;
+ }
+
+ function runTest()
+ {
+ $this->assertEquals(count($this->test_cases),
+ $this->num_tests);
+ }
+}
+
+class Tests_Auth_OpenID_Parse extends PHPUnit_TestSuite {
+
+ function getName()
+ {
+ return "Tests_Auth_OpenID_Parse";
+ }
+
+ function parseLink($line)
+ {
+ $parts = explode(" ", $line);
+ $optional = intval($parts[0] == 'Link*:');
+ assert($optional || ($parts[0] == 'Link:'));
+
+ $attrs = array();
+ foreach (array_slice($parts, 1) as $attr) {
+ list($k, $v) = explode("=", $attr, 2);
+ if ($k[strlen($k) - 1] == '*') {
+ $attr_optional = 1;
+ $k = substr($k, 0, strlen($k) - 1);
+ } else {
+ $attr_optional = 0;
+ }
+
+ $attrs[$k] = array($attr_optional, $v);
+ }
+
+ return array($optional, $attrs);
+ }
+
+ function parseCase($s)
+ {
+ list($header, $markup) = explode("\n\n", $s, 2);
+ $lines = explode("\n", $header);
+ $name = array_shift($lines);
+ assert(strpos($name, 'Name: ') == 0);
+ $desc = substr($name, 6);
+ $parsed = array();
+ foreach ($lines as $line) {
+ $parsed[] = $this->parseLink($line);
+ }
+
+ return array($desc, $markup, $parsed);
+ }
+
+ function parseTests($s)
+ {
+ $tests = array();
+
+ $cases = explode("\n\n\n", $s);
+ $header = array_shift($cases);
+ list($tests_line, $unused) = explode("\n", $header, 2);
+ list($k, $v) = explode(": ", $tests_line);
+ assert($k == 'Num Tests');
+ $num_tests = intval($v);
+
+ foreach (array_slice($cases, 0, count($cases) - 1) as $case) {
+ list($desc, $markup, $links) = $this->parseCase($case);
+ $tests[] = array($desc, $markup, $links, $case);
+ }
+
+ return array($num_tests, $tests);
+ }
+
+ function Tests_Auth_OpenID_Parse()
+ {
+ $here = realpath(dirname(__FILE__));
+ $test_data_file_name = $here . DIRECTORY_SEPARATOR . 'linkparse.txt';
+ $test_data = file_get_contents($test_data_file_name);
+
+ list($num_tests, $test_cases) = $this->parseTests($test_data);
+
+ $this->addTest(new NumTestCases($test_cases, $num_tests));
+
+ foreach ($test_cases as $case) {
+ $this->addTest(new Tests_Auth_OpenID_Link($case));
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/Tests/Auth/OpenID/StoreTest.php b/Tests/Auth/OpenID/StoreTest.php
new file mode 100644
index 0000000..f87d9b4
--- /dev/null
+++ b/Tests/Auth/OpenID/StoreTest.php
@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * A test script for the OpenIDStore classes.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005 Janrain, Inc.
+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+require_once('Auth/OpenID/Association.php');
+require_once('Auth/OpenID/CryptUtil.php');
+require_once('Auth/OpenID/OIDUtil.php');
+
+class Tests_Auth_OpenID_StoreTest extends PHPUnit_TestCase {
+
+ function setUp()
+ {
+ global $_Auth_OpenID_letters, $_Auth_OpenID_digits,
+ $_Auth_OpenID_punct;
+
+ $this->letters = $_Auth_OpenID_letters;
+ $this->digits = $_Auth_OpenID_digits;
+ $this->punct = $_Auth_OpenID_punct;
+ $this->allowed_nonce = $this->letters . $this->digits;
+ $this->allowed_handle = $this->letters . $this->digits . $this->punct;
+ }
+
+ function generateNonce()
+ {
+ return Auth_OpenID_CryptUtil::randomString(8, $this->allowed_nonce);
+ }
+
+ function genAssoc($now, $issued = 0, $lifetime = 600)
+ {
+ $sec = call_user_func(array('Auth_OpenID_CryptUtil', 'randomString'),
+ 20);
+ $hdl = Auth_OpenID_CryptUtil::randomString(128, $this->allowed_handle);
+ return new Auth_OpenID_Association($hdl, $sec, $now + $issued,
+ $lifetime, 'HMAC-SHA1');
+ }
+
+ function _checkRetrieve(&$store, $url, $handle, $expected, $name=null)
+ {
+ $retrieved_assoc = $store->getAssociation($url, $handle);
+ if (($expected === null) || ($store->isDumb())) {
+ $this->assertNull($retrieved_assoc);
+ } else {
+ if ($retrieved_assoc === null) {
+ $this->fail("$name: Got null when expecting " .
+ $expected->serialize());
+ } else {
+ $this->assertEquals($retrieved_assoc->serialize(),
+ $expected->serialize(), $name);
+ }
+ }
+ }
+
+ function _checkRemove(&$store, $url, $handle, $expected)
+ {
+ $present = $store->removeAssociation($url, $handle);
+ $expectedPresent = (!$store->isDumb() && $expected);
+ $this->assertTrue((!$expectedPresent && !$present) ||
+ ($expectedPresent && $present));
+ }
+
+ /**
+ * Make sure a given store has a minimum of API compliance. Call
+ * this function with an empty store.
+ *
+ * Raises AssertionError if the store does not work as expected.
+ *
+ * OpenIDStore -> NoneType
+ */
+ function _testStore($store)
+ {
+
+ // Association functions
+ $now = time();
+
+ $server_url = 'http://www.myopenid.com/openid';
+
+ $assoc = $this->genAssoc($now);
+
+ $this->_checkRetrieve($store, $server_url, null, null,
+ 'Make sure that a missing association returns no result');
+
+ $store->storeAssociation($server_url, $assoc);
+ $this->_checkRetrieve($store, $server_url, null, $assoc,
+ 'Check that after storage, getting returns the same result');
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc,
+ 'more than once');
+
+ $store->storeAssociation($server_url, $assoc);
+ $this->_checkRetrieve($store, $server_url, null, $assoc,
+ 'Storing more than once has no ill effect');
+
+ // Removing an association that does not exist returns not present
+ $this->_checkRemove($store, $server_url, $assoc->handle . 'x', false);
+
+ // Removing an association that does not exist returns not present
+ $this->_checkRemove($store, $server_url . 'x', $assoc->handle, false);
+
+ // Removing an association that is present returns present
+ $this->_checkRemove($store, $server_url, $assoc->handle, true);
+
+ // but not present on subsequent calls
+ $this->_checkRemove($store, $server_url, $assoc->handle, false);
+
+ // Put assoc back in the store
+ $store->storeAssociation($server_url, $assoc);
+
+ // More recent and expires after assoc
+ $assoc2 = $this->genAssoc($now, $issued = 1);
+ $store->storeAssociation($server_url, $assoc2);
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc2,
+ 'After storing an association with a different handle, but the
+same $server_url, the handle with the later expiration is
+returned.');
+
+ $this->_checkRetrieve($store, $server_url, $assoc->handle, $assoc,
+ 'We can still retrieve the older association');
+
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, $assoc2,
+ 'Plus we can retrieve the association with the later expiration
+explicitly');
+
+ // More recent, but expires earlier than assoc2 or assoc
+ $assoc3 = $this->genAssoc($now, $issued = 2, $lifetime = 100);
+ $store->storeAssociation($server_url, $assoc3);
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc3);
+ $this->_checkRetrieve($store, $server_url, $assoc->handle, $assoc);
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, $assoc2);
+ $this->_checkRetrieve($store, $server_url, $assoc3->handle, $assoc3);
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle, true);
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc3);
+ $this->_checkRetrieve($store, $server_url, $assoc->handle, $assoc);
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, null);
+ $this->_checkRetrieve($store, $server_url, $assoc3->handle, $assoc3);
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle, false);
+ $this->_checkRemove($store, $server_url, $assoc3->handle, true);
+
+ $this->_checkRetrieve($store, $server_url, null, $assoc);
+ $this->_checkRetrieve($store, $server_url, $assoc->handle, $assoc);
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, null);
+ $this->_checkRetrieve($store, $server_url, $assoc3->handle, null);
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle, false);
+ $this->_checkRemove($store, $server_url, $assoc->handle, true);
+ $this->_checkRemove($store, $server_url, $assoc3->handle, false);
+ $this->_checkRetrieve($store, $server_url, null, null);
+ $this->_checkRetrieve($store, $server_url, $assoc->handle, null);
+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, null);
+ $this->_checkRetrieve($store, $server_url,$assoc3->handle, null);
+
+ $this->_checkRemove($store, $server_url, $assoc2->handle, false);
+ $this->_checkRemove($store, $server_url, $assoc->handle, false);
+ $this->_checkRemove($store, $server_url, $assoc3->handle, false);
+ }
+
+ function _checkUseNonce(&$store, $nonce, $expected)
+ {
+ $actual = $store->useNonce($nonce);
+ $expected = $store->isDumb() || $expected;
+ $this->assertTrue(($actual && $expected) || (!$actual && !$expected));
+ }
+
+ function _testNonce(&$store)
+ {
+ // Nonce functions
+
+ // Random nonce (not in store)
+ $nonce1 = $this->generateNonce();
+
+ // A nonce is not present by default
+ $this->_checkUseNonce($store, $nonce1, false);
+
+ // Storing once causes useNonce to return true the first, and
+ // only the first, time it is called after the $store->
+ $store->storeNonce($nonce1);
+ $this->_checkUseNonce($store, $nonce1, true);
+ $this->_checkUseNonce($store, $nonce1, false);
+
+ // Storing twice has the same effect as storing once.
+ $store->storeNonce($nonce1);
+ $store->storeNonce($nonce1);
+ $this->_checkUseNonce($store, $nonce1, true);
+ $this->_checkUseNonce($store, $nonce1, false);
+
+ // Auth key functions
+
+ // There is no key to start with, so generate a new key and
+ // return it.
+ $key = $store->getAuthKey();
+
+ // The second time around should return the same as last time.
+ $key2 = $store->getAuthKey();
+ $this->assertEquals($key, $key2);
+ $this->assertEquals(strlen($key), $store->AUTH_KEY_LEN);
+ }
+
+ function test_filestore()
+ {
+ require_once('Auth/OpenID/Store/FileStore.php');
+
+ $temp_dir = Auth_OpenID_mkdtemp('/tmp');
+
+ if (!$temp_dir) {
+ trigger_error('Could not create temporary directory ' .
+ 'with Auth_OpenID_mkdtemp', E_USER_WARNING);
+ return null;
+ }
+
+ $store = new Auth_OpenID_FileStore($temp_dir);
+ $this->_testStore($store);
+ $this->_testNonce($store);
+ $store->destroy();
+ }
+}
+
+?>
diff --git a/Tests/Auth/OpenID/dhexch b/Tests/Auth/OpenID/dhexch
new file mode 100644
index 0000000..7a8be07
--- /dev/null
+++ b/Tests/Auth/OpenID/dhexch
@@ -0,0 +1,25 @@
+2126517416 1503105722 107994742031567165457540274858809652167995244913839787589743578103625285208352685332617657612917013907706708046204110421143061814711041523408378123857885283119340960531860106377561159933747570456852792031929014142490977574671361334052940195520731561436107832724927338342871107723160776334443535305873323500256
+1726325708 97982531 120276049194830643562108817809662062106546923236483207874835831104433621725428405166395533625121884325430201809382464614237831903082554679507511419420917850293136497504021313018779166242697363853538302653639224106865664333535348491338293646599577557630201300627170167862878616413985927131488374258664616392092
+7191572 1822336109 122056637146069839485450157659463602136773435897258591538866604089577736061707581662949502933519623787237101424065925246227311788026398025228549571401681364284397681558495127293422405005519207011429758784679359026564663723906503072635741603815702201571462971153413211041068663899032199555395016545688330586044
+228786056 1675584603 117701151389970889003978379762267752133865163176557074701079147801046451813688145274964215828365957142552336288689654120601548113748929766366564174096775189852190965077394508456052823068488823291767811224538103381867631753777200820579564678159674117155563703553000884461777658090456283332220216734612556688513
+804908984 2135801843 64993745755656098807721600357147815184322152457013249228399624894951891365784451431980303960531348451816046446458097670030866575746640795583720668211568084611960590087205609635406810868042628237527172170079769791670793545166757565720288711565725471066283638538704293790079806106677373567711048953361333211210
+1122280790 1614259350 3970244390792364343312138669070883638127263175075582263096706481850448381703964455173288740017450440943500797144425033043394229962284552755667989452357422108026327690718801740503572676309934059296211509079412739861064178751353597300902277808636740745335474263237517162977595705012834985895341553367459792583
+554314515 998420920 123643361743671701825618173162391028658772950477687990712748798605914570427945055208640384218469111438408345424338893652923941552893606133066783001743721804859308011765507616515353882559991735865794891472781955621601467016660304188272123202130977476560852093272543539966026915472638392018462692756028817601255
+719945347 612820861 103551249110130410018728043034553056272870332237608830696804104933749752848182147616875273399120950956495972830177071656956289995248469136767050516935071277259217339496102844766065836859503647533631994664364053659677674596380230790347281302210808329346735637394258018381272973124437749621859047096780954428763
+1030625109 1519412381 15696617275088442746387785148060623054817901281945029743077460769180096631404983383910114661025034557230747207080210469011273591184527303007260363112037932265980126744700626024259985586947347501172863220738584453809309462129610346067407238209251289710742647393829531889303218223237301268534338307710560528439
+1711254768 1710707291 57671766580359527580383190392835992822988951359063099518333951473557157636791373831886967456320589708220219137556141104065094734173377513568373511612097050435132426608350879787688784646390242899955969071718348216328885834450215105058725433533776719158074043390881257587614495125963197168525883771897032429145
+350065369 319208735 44521456496863698728911963510653524876630475042295240074435222668882607096381437705850621136342821688618111659046306438623837465097724847737566157513351593063095577843263064035230462006868686576892037899378382406468120801597507834123281075093108794208153836881908434178839513489161646768450411118658866064760
+2060218994 1946043223 56312469398022909670236635086334904553302665496424591277087996113064393075310693844995965663947160222486178761741935506327835516277624460430181450292802834360724976701838361338230904004764511115279873515265325364671729600765057941485718305803874368460265173324375012707038078949514720443784809672434739391394
+348859478 486047271 63578529904279717544096579659734885121575283886072626718230632949600891106018451131129915865157675764791879663149288069798959505461654979937263948081560345222746334083402817181164255208790802816536212306902000509334761465449621953806270899950736579351124776383450108496252367170418747525612150563944975123906
+1012847796 1311216437 107243486601777772242638374457577339776317528440551074937135087437181884726459082109032187432358497015564158022857522392034581988349463322793155498885898276448910563934149930379053835676169014345745737841013305105289515557002942278171260275549569040668192618881235525671100756834785472005323827534045854021808
+1108188659 73002956 151810407586486667408500610375120927048683500513617431444670840241105574837701928593342245869128797827409414723271900961746330458448581633550115101600384307415146567797051023727766743006773542272526168313129133103058023736384944187039543948615045687254043611794926502253898300807539332514119558228945387167129
+1367891527 957164137 106888874248390844568539366153235739322099571679913873665077300044384432133087328354115380340807163549209282323027334598550750155549975114208460003069900549945106924101337174939911191001778776920788324219234143471273850920009578258747072782983631129326451246547584416492036977756842649955247390532642313031673
+1109319782 312705549 68015190517529836158634537187194145539209089151286211608940739126031607591236786876471227901028349157506546942329120582174574792522929377067808957872180985535362179866434055727341930725758279092655515659945664689974113139170079360543337269872976473433045950679959300152245802435127006127508284128226462808242
+77629902 1442208847 80002290434058357698705037287975366182731667140415670086832039653095542318592553515737200055739316505804591069679885064388918352070456242079053263046801427045473800954002156858264359891351667747947419377687560365442620710551484084591458509139700723197713694698034159851521977928611736821392017020477832037627
+1876665427 42392232 94638321177007894302596716790742601595610267055803051893624262442254201910985079684859058170169970420428917385076321338591735591117741499259022286688741758915429225227910702796255294628145448897362890044237980198958757175908654342104958253051607028469935368687936664986431151922321609065078691893879183189566
+559635525 1782490275 71365295096950865667427967092027725943054589808884646377867956234326896501650860934260905567087143525158504721587985301638221372965891635800328428504369765880956526297788284176796001565732103141822914955442743632126166019769189834699258461912602048002960149362882398622111007162709444738907309082349930416022
+743575094 32085276 110453584602878746921666429659148701413696049424461554973529870857842263810553552249241246313332783204009612578170167391820378603413812193480492843470042238103670641705732755583940134259794253885826115668806244330875357074130716388274584300227667628005544555311079499447940768673150499033922449576383527638186
+129818206 137481306 140835473021876998102027624369898079740454145360699735493249477450544517213808389172240396819192163023303266715591396745357472463341356969319556862236385556442904650009255138279232984377682804793224148996875324569988553808409865099882747099149617352970774643108291836908871124753511856578160489828404865664010
+570689556 1980693879 108353275894436996626884805554770441694866167783124178905252902978286824751598925059178987939656961896173921225105217325495780672752694383439806863122466053616930970271706866769895033633709670957150865005763659847698663978549871624628298476651867451354816053985969930735100533712902146229305011837945607699037
+2103057616 691067770 27024056452709622585420653808400360576905666723601175215091499609471301967744143329187436673102391151329548614036436716051750524604202541651425479133617399916946398092715923340837394931898418514658066714568415577105694330058750941172815095999450748361179045856199026312487393802486505084466623313733605784416
+481705774 1641440342 117722260864906582664053412535574009960206959347375143271559843536103545468155917636456429488071536410856812908716077868452921005581676036410474437330482920141777150620686622782118823530416466223519936589968714322291361670902315520017103387742935706013660879451297004924070011539277017717095949755265539759012
diff --git a/Tests/Auth/OpenID/dhpriv b/Tests/Auth/OpenID/dhpriv
new file mode 100644
index 0000000..0fa5231
--- /dev/null
+++ b/Tests/Auth/OpenID/dhpriv
@@ -0,0 +1,29 @@
+130706940119084053627151828062879423433929180135817317038378606310097533503449582079984816816837125851552273641820339909167103200910805078308128174143174269944095368580519322913514764528012639683546377014716235962867583443566164615728897857285824741767070432119909660645255499710701356135207437699643611094585 139808169914464096465921128085565621767096724855516655439365028496569658038844954238931647642811548254956660405394116677296461848124300258439895306367561416289126854788101396379292925819850897858045772500578222021901631436550118958972312221974009238050517034542286574826081826542722270952769078386418682059418
+91966407878983240112417790733941098492087186469785726449910011271065622315680646030230288265496017310433513856308693810812043160919214636748486185212617634222158204354206411031403206076739932806412551605172319515223573351072757800448643935018534945933808900467686115619932664888581913179496050117713298715475 88086484332488517006277516020842172054013692832175783214603951240851750819999098631851571207693874357651112736088114133607400684776234181681933311972926752846692615822043533641407510569745606256772455614745111122033229877596984718963046218854103292937700694160593653595134512369959987897086639788909618660591
+94633950701209990078055218830969910271587805983595045023718108184189787131629772007048606080263109446462048743696369276578815611098215686598630889831104860221067872883514840819381234786050098278403321905311637820524177879167250981289318356078312300538871435101338967079907049912435983871847334104247675360099 136836393035803488129856151345450008294260680733328546556640578838845312279198933806383329293483852515700876505956362639881210101974254765087350842271260064592406308509078284840473735904755203614987286456952991025347168970462354352741159076541157478949094536405618626397435745496863324654768971213730622037771
+24685127248019769965088146297942173464487677364928435784091685260262292485380918213538979925891771204729738138857126454465630594391449913947358655368215901119137728648638547728497517587701248406019427282237279437409508871300675355166059811431191200555457304463617727969228965042729205402243355816702436970430 103488011917988946858248200111251786178288940265978921633592888293430082248387786443813155999158786903216094876295371112716734481877806417714913656921169196196571699893360825510307056269738593971532017994987406325068886420548597161498019372380511676314312298122272401348856314619382867707981701472607230523868
+116791045850880292989786005885944774698035781824784400772676299590038746153860847252706167458966356897309533614849402276819438194497464696186624618374179812548893947178936305721131565012344462048549467883494038577857638815386798694225798517783768606048713198211730870155881426709644960689953998714045816205549 25767875422998856261320430397505398614439586659207416236135894343577952114994718158163212134503751463610021489053571733974769536157057815413209619147486931502025658987681202196476489081257777148377685478756033509708349637895740799542063593586769082830323796978935454479273531157121440998804334199442003857410
+75582226959658406842894734694860761896800153014775231713388264961517169436476322183886891849966756849783437334069692683523296295601533803799559985845105706728538458624387103621364117548643541824878550074680443708148686601108223917493525070861593238005735446708555769966855130921562955491250908613793521520082 51100990616369611694975829054222013346248289055987940844427061856603230021472379888102172458517294080775792439385531234808129302064303666640376750139242970123503857186428797403843206765926798353022284672682073397573130625177187185114726049347844460311761033584101482859992951420083621362870301150543916815123
+22852401165908224137274273646590366934616265607879280260563022941455466297431255072303172649495519837876946233272420969249841381161312477263365567831938496555136366981954001163034914812189448922853839616662859772087929140818377228980710884492996109434435597500854043325062122184466315338260530734979159890875 35017410720028595029711778101507729481023945551700945988329114663345341120595162378885287946069695772429641825579528116641336456773227542256911497084242947904528367986325800537695079726856460817606404224094336361853766354225558025931211551975334149258299477750615397616908655079967952372222383056221992235704
+37364490883518159794654045194678325635036705086417851509136183713863262621334636905291385255662750747808690129471989906644041585863034419130023070856805511017402434123099100618568335168939301014148587149578150068910141065808373976114927339040964292334109797421173369274978107389084873550233108940239410902552 40916262212189137562350357241447034318002130016858244002788189310078477605649010031339865625243230798681216437501833540185827501244378529230150467789369234869122179247196276164931090039290879808162629109742198951942358028123056268054775108592325500609335947248599688175189333996086475013450537086042387719925
+42030470670714872936404499074069849778147578537708230270030877866700844337372497704027708080369726758812896818567830863540507961487472657570488625639077418109017434494794778542739932765561706796300920251933107517954265066804108669800167526425723377411855061131982689717887180411017924173629124764378241885274 124652439272864857598747946875599560379786580730218192165733924418687522301721706620565030507816884907589477351553268146177293719586287258662025940181301472851649975563004543250656807255226609296537922304346339513054316391667044301386950180277940536542183725690479451746977789001659540839582630251935163344393
+33176766914206542084736303652243484580303865879984981189372762326078776390896986743451688462101732968104375838228070296418541745483112261133079756514082093269959937647525005374035326747696591842313517634077723301677759648869372517403529488493581781546743147639937580084065663597330159470577639629864369972900 67485835091897238609131069363014775606263390149204621594445803179810038685760826651889895397414961195533694176706808504447269558421955735607423135937153901140512527504198912146656610630396284977496295289999655140295415981288181545277299615922576281262872097567020980675200178329219970170480653040350512964539
+131497983897702298481056962402569646971797912524360547236788650961059980711719600424210346263081838703940277066368168874781981151411096949736205282734026497995296147418292226818536168555712128736975034272678008697869326747592750850184857659420541708058277866000692785617873742438060271311159568468507825422571 5400380840349873337222394910303409203226429752629134721503171858543984393161548520471799318518954232197106728096866840965784563043721652790856860155702760027304915133166173298206604451826182024471262142046935060360564569939062438160049193241369468208458085699995573492688298015026628427440418009025072261296
+83265103005695640943261961853521077357830295830250157593141844209296716788437615940096402365505416686459260302419338241462783388722843946886845478224048360927114533590583464979009731440049610985062455108831881153988321298531365779084012803908832525921630534096740755274371500276660832724874701671184539131864 141285570207910287798371174771658911045525474449663877845558585668334618068814605961306961485855329182957174312715910923324965889174835444049526313968571611940626279733302104955951067959291852710640374412577070764165811275030632465290729619533330733368808295932659463215921521905553936914975786500018720073003
+68435028583616495789148116911096163791710022987677894923742899873596891423986951658100606742052014161171185231735413902875605720814417622409817842932759492013585936536452615480700628719795872201528559780249210820284350401473564919576289210869896327937002173624497942136329576506818749730506884927872345019446 134655528287263100540003157571441260698452262106680191153945271167894435782028803135774578949200580551016388918860856991026082917835209212892423567114480975540305860034439015788120390011692862968771136814777768281366591257663821495720134621172848947971117885754539770645621669309650476331439675400544167728223
+97765390064836080322590528352647421920257073063706996347334558390461274981996865736612531330863478931481491964338380362350271734683183807511097331539820133036984271653285063355715726806139083282458695728902452215405696318402583540317419929113959816258829534543044153959951908676300847164682178008704099351835 92552521881196975294401505656851872247567784546370503402756239533783651371688190302773864319828182042605239246779598629409815474038541272600580320815319709309111399294952620375093803971373108792300726524826209329889463854451846561437729676142864421966497641824498079067929811613947148353921163336822026640804
+145767094672933012300753301037546647564595762930138884463767054235112032706630891961371504668013023047595721138624016493638510710257541241706724342585654715468628355455898091951826598092812212209834746162089753649871544789379424903025374228231365026585872808685759231756517703720396301355299998059523896918448 116669462839999965355861187716880953863237226719689755457884414384663576662696981997535568446560375442532084973721539944428004043491468494548231348032618218312515409944970197902589794303562379864012797605284844016184274353252071642511293089390472576498394410829972525726474727579603392265177009323768966538608
+34172517877854802711907683049441723730724885305592620486269966708379625109832852005775048584124451699198484092407720344962116726808090368739361658889584507734617844212547181476646725256303630128954338675520938806905779837227983648887192531356390902975904503218654196581612781227843742951241442641220856414232 126013077261793777773236390821108423367648447987653714614732477073177878509574051196587476846560696305938891953527959347566502332765820074506907037627115954790645652211088723122982633069089920979477728376746424256704724173255656757918995039125823421607024407307091796807227896314403153380323770001854211384322
+9979624731056222925878866378063961280844793874828281622845276060532093809300121084179730782833657205171434732875093693074415298975346410131191865198158876447591891117577190438695367929923494177555818480377241891190442070100052523008290671797937772993634966511431668500154258765510857129203107386972819651767 76559085024395996164590986654274454741199399364851956129137304209855150918182685643729981600389513229011956888957763987167398150792454613751473654448162776379362213885827651020309844507723069713820393068520302223477225569348080362344052033711960892643036147232270133731530049660264526964146237693063093765111
+18162696663677410793062235946366423954875282212790518677684260521370996677183041664345920941714064628111537529793170736292618705900247450994864220481135611781148410617609559050220262121494712903009168783279356915189941268264177631458029177102542745167475619936272581126346266816618866806564180995726437177435 63244550218824945129624987597134280916829928261688093445040235408899092619821698537312158783367974202557699994650667088974727356690181336666077506063310290098995215324552449858513870629176838494348632073938023916155113126203791709810160925798130199717340478393420816876665127594623142175853115698049952126277
+4817943161362708117912118300716778687157593557807116683477307391846133734701449509121209661982298574607233039490570567781316652698287671086985501523197566560479906850423709894582834963398034434055472063156147829131181965140631257939036683622084290629927807369457311894970308590034407761706800045378158588657 61612160237840981966750225147965256022861527286827877531373888434780789812764688703260066154973576040405676432586962624922734102370509771313805122788566405984830112657060375568510809122230960988304085950306616401218206390412815884549481965750553137717475620505076144744211331973240555181377832337912951699135
+36363324947629373144612372870171042343590861026293829791335153646774927623889458346817049419803031378037141773848560341251355283891019532059644644509836766167835557471311319194033709837770615526356168418160386395260066262292757953919140150454538786106958252854181965875293629955562111756775391296856504912587 86831561031659073326747216166881733513938228972332631084118628692228329095617884068498116676787029033973607066377816508795286358748076949738854520048303930186595481606562375516134920902325649683618195251332651685732712539073110524182134321873838204219194459231650917098791250048469346563303077080880339797744
+26406869969418301728540993821409753036653370247174689204659006239823766914991146853283367848649039747728229875444327879875275718711878211919734397349994000106499628652960403076186651083084423734034070082770589453774926850920776427074440483233447839259180467805375782600203654373428926653730090468535611335253 100139935381469543084506312717977196291289016554846164338908226931204624582010530255955411615528804421371905642197394534614355186795223905217732992497673429554618838376065777445760355552020655667172127543653684405493978325270279321013143828897100500212200358450649158287605846102419527584313353072518101626851
+92613116984760565837109105383781193800503303131143575169488835702472221039082994091847595094556327985517286288659598094631489552181233202387028607421487026032402972597880028640156629614572656967808446397456622178472130864873587747608262139844319805074476178618930354824943672367046477408898479503054125369731 30023391082615178562263328892343821010986429338255434046051061316154579824472412477397496718186615690433045030046315908170615910505869972621853946234911296439134838951047107272129711854649412919542407760508235711897489847951451200722151978578883748353566191421685659370090024401368356823252748749449302536931
+31485815361342085113278193504381994806529237123359718043079410511224607873725611862217941085749929342777366642477711445011074784469367917758629403998067347054115844421430072631339788256386509261291675080191633908849638316409182455648806133048549359800886124554879661473112614246869101243501787363247762961784 114503770698890543429251666713050844656853278831559195214556474458830029271801818536133531843456707474500106283648085144619097572354066554819887152106174400667929098257361286338795493838820850475790977445807435511982704395422526800272723708548541616513134676140304653112325071112865020365664833601046215694089
+76882090884790547431641385530818076533805072109483843307806375918023300052767710853172670987385376253156912268523505310624133905633437815297307463917718596711590885553760690350221265675690787249135345226947453988081566088302642706234126002514517416493192624887800567412565527886687096028028124049522890448168 15056463217273240496622619354104573042767532856243223052125822509781815362480522535564283485059790932505429110157271454207173426525345813426696743168079246510944969446574354255284952839036431873039487144279164893710061580467579842173706653409487110282515691099753380094215805485573768509475850463001549608836
+52345178981230648108672997265819959243255047568833938156267924185186047373470984278294897653277996726416846430969793375429223610099546622112048283560483136389901514170116723365811871938630317974150540909650396429631704968748113009366339718498979597226137532343384889080245796447593572468846438769413505393967 32148494517199936472358017244372701214529606506776255341152991328091526865643069587953759877295255050519124541457805199596762210567333445908166076384465183589342153762720515477404466193879418014196727238972417616122646440870364200208488239778452378059236162633837824948613596114768455832408342040970780086
+41095268619128788015767564971105114602454449306041732792746397800275041704886345704294273937217484580365505320134717320083763349380629342859670693445658118959823430378844830923452105707338162448974869312012791385772125813291388247857971218575518319578818336960572244046567099555399203328678654466958536663208 92166550199033418923713824997841892577149715275633481076285269142670107687867024550593869464613175882141630640739938334001211714884975032600306279287443909448541179109981755796752132502127330056736913454039526413284519137059580845856736918773597087836203497066909257930043736166431682872083389105176299181629
+40049143661018504441607875135884755310012910557581028447435354354754245291878800571089144452035026644953322330676651798951447670184106450649737772686119714700743396359069052813433030118630105307022867200053964644574786137276428546712005171080129190959914708907200288299169344380390093918556722227705114244981 108159089972386282154772900619022507336076619354549601813179459338897131937353741544606392560724999980281424266891537298473163753022749859939445293926707568015958367188089915420630082556748668489756475027008449860889202622698060097015044886961901650857610841562477736791450080980702347705778074391774667412741
+69905259478181995876884927656894491893594530150260951315109404530530357998889589977208787140430938039028941393673520799460431992051993157468616168400324834880926190141581037597526917869362292931957289043707855837933490285814769110495657056206391880865972389421774822461752702336812585852278453803972600333734 71821415380277072313878763768684432371552628204186742842154591000123020597011744840460964835414360968627162765288463383113375595799297552681618876474019263288277398833725479226930770694271622605114061622753165584075733358178384410640349907375170170910499615355511313349300918885560131539570707695789106185664
+26945345439378873515011714350080059082081595419023056538696949766471272811362104837806324694947413603019863785876836706911406330379274553386254346050697348395574746891556054334903838949157798006141473389066020212044825140294048709654273698482867946522782450500680195477050110145664069582549935651920545151500 80313315938584480048642653013876614091607852535582224914294013785054094052454758327935781971746329853786568549510067442145637007308960551652864942042189241081946607011847245280773379099020221884296226818685556430275385068764313042226925852500883894269809033380734632866477789520106865758504064806906234130588
diff --git a/Tests/Auth/OpenID/hmac.txt b/Tests/Auth/OpenID/hmac.txt
new file mode 100644
index 0000000..4299a96
--- /dev/null
+++ b/Tests/Auth/OpenID/hmac.txt
@@ -0,0 +1,49 @@
+test_case = 1
+key = 0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
+key_len = 20
+data = "Hi There"
+data_len = 8
+digest = 0xb617318655057264e28bc0b6fb378c8ef146be00
+
+test_case = 2
+key = "Jefe"
+key_len = 4
+data = "what do ya want for nothing?"
+data_len = 28
+digest = 0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79
+
+test_case = 3
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+key_len = 20
+data = 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
+data_len = 50
+digest = 0x125d7342b9ac11cd91a39af48aa17b4f63f175d3
+
+test_case = 4
+key = 0x0102030405060708090a0b0c0d0e0f10111213141516171819
+key_len = 25
+data = 0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
+data_len = 50
+digest = 0x4c9007f4026250c6bc8414f9bf50c86c2d7235da
+
+test_case = 5
+key = 0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c
+key_len = 20
+data = "Test With Truncation"
+data_len = 20
+digest = 0x4c1a03424b55e07fe7f27be1d58bb9324a9a5a04
+digest-96 = 0x4c1a03424b55e07fe7f27be1
+
+test_case = 6
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+key_len = 80
+data = "Test Using Larger Than Block-Size Key - Hash Key First"
+data_len = 54
+digest = 0xaa4ae5e15272d00e95705637ce8a3b55ed402112
+
+test_case = 7
+key = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+key_len = 80
+data = "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"
+data_len = 73
+digest = 0xe8e99d0f45237d786d6bbaa7965c7808bbff1a91
diff --git a/Tests/Auth/OpenID/linkparse.txt b/Tests/Auth/OpenID/linkparse.txt
new file mode 100644
index 0000000..2206395
--- /dev/null
+++ b/Tests/Auth/OpenID/linkparse.txt
@@ -0,0 +1,583 @@
+Num Tests: 72
+
+OpenID link parsing test cases
+Copyright (C) 2005-2006, JanRain, Inc.
+See COPYING for license information.
+
+File format
+-----------
+
+All text before the first triple-newline (this chunk) should be ignored.
+
+This file may be interpreted as Latin-1 or UTF-8.
+
+Test cases separated by three line separators (`\n\n\n'). The test
+cases consist of a headers section followed by a data block. These are
+separated by a double newline. The headers consist of the header name,
+followed by a colon, a space, the value, and a newline. There must be
+one, and only one, `Name' header for a test case. There may be zero or
+more link headers. The `Link' header consists of whitespace-separated
+attribute pairs. A link header with an empty string as a value
+indicates an empty but present link tag. The attribute pairs are `='
+separated and not quoted.
+
+Optional Links and attributes have a trailing `*'. A compilant
+implementation may produce this as output or may not. A compliant
+implementation will not produce any output that is absent from this
+file.
+
+
+Name: No link tag at all
+
+<html>
+<head>
+</head>
+</html>
+
+
+Name: Link element first
+
+<link>
+
+
+Name: Link inside HTML, not head
+
+<html>
+<link>
+
+
+Name: Link inside head, not html
+
+<head>
+<link>
+
+
+Name: Link inside html, after head
+
+<html>
+<head>
+</head>
+<link>
+
+
+Name: Link inside html, before head
+
+<html>
+<link>
+<head>
+
+
+Name: Link before html and head
+
+<link>
+<html>
+<head>
+
+
+Name: Link after html document with head
+
+<html>
+<head>
+</head>
+</html>
+<link>
+
+
+Name: Link inside html inside head, inside another html
+
+<html>
+<head>
+<html>
+<link>
+
+
+Name: Link inside html inside head
+
+<head>
+<html>
+<link>
+
+
+Name: link inside body inside head inside html
+
+<html>
+<head>
+<body>
+<link>
+
+
+Name: Link inside head inside head inside html
+
+<html>
+<head>
+<head>
+<link>
+
+
+Name: Link inside script inside head inside html
+
+<html>
+<head>
+<script>
+<link>
+</script>
+
+
+Name: Link inside comment inside head inside html
+
+<html>
+<head/>
+<link>
+
+
+Name: Link inside of head after short head
+
+<html>
+<head/>
+<head>
+<link>
+
+
+Name: Plain vanilla
+Link:
+
+<html>
+<head>
+<link>
+
+
+Name: Ignore tags in the <script:... > namespace
+Link*:
+
+<html>
+<head>
+<script:paddypan>
+<link>
+</script:paddypan>
+
+
+Name: Short link tag
+Link:
+
+<html>
+<head>
+<link/>
+
+
+Name: Spaces in the HTML tag
+Link:
+
+<html >
+<head>
+<link>
+
+
+Name: Spaces in the head tag
+Link:
+
+<html>
+<head >
+<link>
+
+
+Name: Spaces in the link tag
+Link:
+
+<html>
+<head>
+<link >
+
+
+Name: No whitespace
+Link:
+
+<html><head><link>
+
+
+Name: Closed head tag
+Link:
+
+<html>
+<head>
+<link>
+</head>
+
+
+Name: One good, one bad (after close head)
+Link:
+
+<html>
+<head>
+<link>
+</head>
+<link>
+
+
+Name: One good, one bad (after open body)
+Link:
+
+<html>
+<head>
+<link>
+<body>
+<link>
+
+
+Name: ill formed (missing close head)
+Link:
+
+<html>
+<head>
+<link>
+</html>
+
+
+Name: Ill formed (no close head, link after </html>)
+Link:
+
+<html>
+<head>
+<link>
+</html>
+<link>
+
+
+Name: Ignore random tags inside of html
+Link:
+
+<html>
+<delicata>
+<head>
+<title>
+<link>
+
+
+Name: case-folding
+Link*:
+
+<HtMl>
+<hEaD>
+<LiNk>
+
+
+Name: unexpected tags
+Link:
+
+<butternut>
+<html>
+<summer>
+<head>
+<turban>
+<link>
+
+
+Name: un-closed script tags
+Link*:
+
+<html>
+<head>
+<script>
+<link>
+
+
+Name: un-closed script tags (no whitespace)
+Link*:
+
+<html><head><script><link>
+
+
+Name: un-closed comment
+Link*:
+
+<html>
+<head>
+<!--
+<link>
+
+
+Name: un-closed CDATA
+Link*:
+
+<html>
+<head>
+<![CDATA[
+<link>
+
+
+Name: cdata-like
+Link*:
+
+<html>
+<head>
+<![ACORN[
+<link>
+]]>
+
+
+Name: comment close only
+Link:
+
+<html>
+<head>
+<link>
+-->
+
+
+Name: Vanilla, two links
+Link:
+Link:
+
+<html>
+<head>
+<link>
+<link>
+
+
+Name: extra tag, two links
+Link:
+Link:
+
+<html>
+<gold nugget>
+<head>
+<link>
+<link>
+
+
+Name: case-fold, body ends, two links
+Link:
+Link*:
+
+<html>
+<head>
+<link>
+<LiNk>
+<body>
+<link>
+
+
+Name: simple, non-quoted rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server>
+
+
+Name: short tag has rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server/>
+
+
+Name: short tag w/space has rel
+Link: rel=openid.server
+
+<html><head><link rel=openid.server />
+
+
+Name: extra non-attribute, has rel
+Link: rel=openid.server
+
+<html><head><link hubbard rel=openid.server>
+
+
+Name: non-attr, has rel, short
+Link: rel=openid.server
+
+<html><head><link hubbard rel=openid.server/>
+
+
+Name: non-attr, has rel, short, space
+Link: rel=openid.server
+
+<html><head><link hubbard rel=openid.server />
+
+
+Name: misplaced slash has rel
+Link: rel=openid.server
+
+<html><head><link / rel=openid.server>
+
+
+Name: quoted rel
+Link: rel=openid.server
+
+<html><head><link rel="openid.server">
+
+
+Name: single-quoted rel
+Link: rel=openid.server
+
+<html><head><link rel='openid.server'>
+
+
+Name: two links w/ rel
+Link: x=y
+Link: a=b
+
+<html><head><link x=y><link a=b>
+
+
+Name: non-entity
+Link: x=&y
+
+<html><head><link x=&y>
+
+
+Name: quoted non-entity
+Link: x=&y
+
+<html><head><link x="&y">
+
+
+Name: quoted entity
+Link: x=&
+
+<html><head><link x="&amp;">
+
+
+Name: entity not processed
+Link: x=&#26;
+
+<html><head><link x="&#26;">
+
+
+Name: &lt;
+Link: x=<
+
+<html><head><link x="&lt;">
+
+
+Name: &gt;
+Link: x=>
+
+<html><head><link x="&gt;">
+
+
+Name: &quot;
+Link: x="
+
+<html><head><link x="&quot;">
+
+
+Name: &amp;&quot;
+Link: x=&"
+
+<html><head><link x="&amp;&quot;">
+
+
+Name: mixed entity and non-entity
+Link: x=&"&hellip;>
+
+<html><head><link x="&amp;&quot;&hellip;&gt;">
+
+
+Name: mixed entity and non-entity (w/normal chars)
+Link: x=x&"&hellip;>x
+
+<html><head><link x="x&amp;&quot;&hellip;&gt;x">
+
+
+Name: broken tags
+Link*: x=y
+
+<html><head><link x=y<>
+
+
+Name: missing close pointy
+Link: z=y
+
+<html><head><link x=y<link z=y />
+
+
+Name: missing attribute value
+Link: x=y y*=
+Link: x=y
+
+<html><head><link x=y y=><link x=y />
+
+
+Name: Missing close pointy (no following)
+Link*: x=y
+
+<html><head><link x=y
+
+
+Name: Should be quoted
+Link: x*=<
+
+<html><head><link x="<">
+
+
+Name: Should be quoted (2)
+Link: x*=>
+
+<html><head><link x=">">
+
+
+Name: Repeated attribute
+Link: x=y
+
+<html><head><link x=z x=y>
+
+
+Name: Repeated attribute (2)
+Link: x=y
+
+<html><head><link x=y x=y>
+
+
+Name: Two attributes
+Link: x=y y=z
+
+<html><head><link x=y y=z>
+
+
+Name: Well-formed link rel="openid.server"
+Link: rel=openid.server href=http://www.myopenid.com/server
+
+<html>
+ <head>
+ <link rel="openid.server"
+ href="http://www.myopenid.com/server" />
+ </head>
+</html>
+
+
+Name: Well-formed link rel="openid.server" and "openid.delegate"
+Link: rel=openid.server href=http://www.myopenid.com/server
+Link: rel=openid.delegate href=http://example.myopenid.com/
+
+<html><head><link rel="openid.server"
+ href="http://www.myopenid.com/server" />
+ <link rel="openid.delegate" href="http://example.myopenid.com/" />
+</head></html>
+
+
+Name: from brian's livejournal page
+Link: rel=stylesheet href=http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711 type=text/css
+Link: rel=openid.server href=http://www.livejournal.com/openid/server.bml
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet"
+ href="http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711"
+ type="text/css" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="foaf:maker"
+ content="foaf:mbox_sha1sum '12f8abdacb5b1a806711e23249da592c0d316260'" />
+ <meta name="robots" content="noindex, nofollow, noarchive" />
+ <meta name="googlebot" content="nosnippet" />
+ <link rel="openid.server"
+ href="http://www.livejournal.com/openid/server.bml" />
+ <title>Brian</title>
+ </head>
+
+
+Name: non-ascii (Latin-1 or UTF8)
+Link: x=®
+
+<html><head><link x="®">
+
+