[
  {
    "path": ".gitignore",
    "content": "*.gem\n*.rbc\n.bundle\n.config\n.yardoc\nGemfile.lock\nInstalledFiles\n_yardoc\ncoverage\ndoc/\nlib/bundler/man\npkg\nrdoc\nspec/reports\ntest/tmp\ntest/version_tmp\ntmp\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: ruby\nsudo: required\ndist: trusty\nscript: rake\nrvm:\n  - \"1.9\"\n  - \"2.0\"\n  - \"2.1\"\n  - \"2.2\"\n  - \"2.3\"\n  - \"2.4\"\n  - \"2.5\"\n  - \"2.6\"\n  - ruby-head\n  - jruby\n  - jruby-head\n  - jruby-19mode\n  - rubinius-3\n\nbefore_install:\n  - \"gem install bundler || gem install bundler --version '< 2'\"\n\nmatrix:\n  allow_failures:\n    - rvm: \"ruby-head\"\n    - rvm: \"jruby-head\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 2.9.2\n\n* Perform all checks before verifying endpoints.\n  [#126](https://github.com/openid/ruby-openid/pull/126)\n\n## 2.9.1\n\n* Updated CHANGELOG.md\n\n## 2.9.0\n\n* Remove deprecated `autorequire` from gemspec.\n  [#123](https://github.com/openid/ruby-openid/pull/123)\n* Rescue from `Yadis::XRI::XRIHTTPError` on discovery.\n  [#106](https://github.com/openid/ruby-openid/pull/106)\n* Avoid SSRF for claimed_id request.\n  [#121](https://github.com/openid/ruby-openid/pull/121)\n* Updated documentation.\n  [#115](https://github.com/openid/ruby-openid/pull/115), [#116](https://github.com/openid/ruby-openid/pull/116), [#117](https://github.com/openid/ruby-openid/pull/117), [#118](https://github.com/openid/ruby-openid/pull/118)\n* Reduce warnings output in test runs.\n  [#119](https://github.com/openid/ruby-openid/pull/119)\n* Drop deprecated option from gemspec.\n  [#120](https://github.com/openid/ruby-openid/pull/120)\n* Remove circular require.\n  [#113](https://github.com/openid/ruby-openid/pull/113)\n* Updated Travis CI config with Ruby 2.6\n  [#114](https://github.com/openid/ruby-openid/pull/114)\n* Simplify Bundler require; remove need for extra `:require`.\n  [#112](https://github.com/openid/ruby-openid/pull/112)\n\n## 2.8.0\n\n* Fix `admin/mkassoc` script.\n  See https://github.com/openid/ruby-openid/pull/103\n* Allow specifying timeout for `OpenID::StandardFetcher` in environment variables.\n  See https://github.com/openid/ruby-openid/pull/109\n* Fixed some documentation.\n  See https://github.com/openid/ruby-openid/pull/111\n* Fixed example server.\n  See https://github.com/openid/ruby-openid/pull/91\n* Fixed tests.\n  See https://github.com/openid/ruby-openid/pull/86\n* Misc. changes to the CI setup.\n  See\n  - https://github.com/openid/ruby-openid/pull/110\n  - https://github.com/openid/ruby-openid/pull/108\n  - https://github.com/openid/ruby-openid/pull/107\n\n## 2.7.0\n\n* Use RFC 2396 compatible URI parser for trustroot - 7c84ec9ced3ccbdad575e02dbfa81e53b52f909e\n  See https://github.com/openid/ruby-openid/pull/85\n* Use HMAC from OpenSSL rather than Digest - ce2e30d7ff3308f17ef7d8c19d6f4752f76c9c40\n  See https://github.com/openid/ruby-openid/pull/84\n* Check if OpenSSL is loaded - 751e55820d958ee781f5abb466a576d83ddde6fd\n\n## 2.6.0\n\n* More safely build filenames - 1c4a90630b183e7572b8ab5f2e3a3e0c0fecd2c7\n  See https://github.com/openid/ruby-openid/pull/80\n* The session serializer of Rails4.1 is json - b44a1eb511dec3be25a07930121bc80cacec0f1c\n* Handle boolean value to fix signature issue - d65076269b77754da7db6e4b189edeeb9201600d\n  See https://github.com/openid/ruby-openid/pull/76\n\n## 2.5.0\n\n* Revert json serialization - 8dc60e553369df2300ebb4b83a29618aff643c2c\n  See https://github.com/openid/ruby-openid/pull/73\n\n## 2.4.0\n\n* Allow expecting a parameter to be nil during return_to verification - 708e992ab3e6c26d478283fc11faa6a0a74bfec0\n* Serialize to objects that can be stored as json - db1d8f7b171a333dec4e861fe0fa53ac1d98b188\n* Fixed missing XRDS HTTP header in sample provider - dc15fa07fd59fdcf46d659cce34c6ef7a6768fde\n\n## 2.3.0\n\n* Deprecated Ruby 1.8 support - 0694bebc83de0313cfef73a5d0ffd9a293ae71a0\n* Fixed encoding errors in test suite - 7ac8e3978f9c733bd5ee8d6b742b515b5427ded2\n* Be aware when using Hash or Array as default value for unknown Hash keys - #58\n* Stop overwriting String#starts_with? and String#ends_with? if defined - #55\n* Ignore Associations For OpenID2 (Google's Security Bug Fix) - #53\n* Change \"oauth\" to \"ui\" in variable name in the UI extension - #52\n* Eliminating runtime warnings - #50 #56\n* Upgrade example Rails provider/consumer app to Rails 3 - #49\n\n## 2.2.3\n\n* Fixed 'invalid byte sequence in UTF-8' error in parse_link_attrs - 0f46921a97677b83b106366c805063105c5e9f20\n* Fixed license information in gemspec - f032e949e1ca9078ab7508d9629398ca2c36980a\n* Update starts/ends_with? to handle nil prefix - beee5e8d1dc24ad55725cfcc720eefba6bdbd279\n\n## 2.2.2\n\n* Limit fetching file size & disable XML entity expansion - be2bab5c21f04735045e071411b349afb790078f\n\n  Avoid DoS attack to RPs using large XRDS / too many XML entity expansion in XRDS.\n\n## 2.2.1\n\n* Make bundle exec rake work - 2100f281172427d1557ebe76afbd24072a22d04f\n* State license in gemspec for automated tools / rubygems.org page - 2d5c3cd8f2476b28d60609822120c79d71919b7b\n* Use default-external encoding instead of ascii for badly encoded pages - a68d2591ac350459c874da10108e6ff5a8c08750\n* Colorize output and reveal tests that never ran - 4b0143f0a3b10060d5f52346954219bba3375039\n\n## 2.2.0\n\n* Bundler compatibility and bundler gem tasks - 72d551945f9577bf5d0e516c673c648791b0e795\n* register_namespace_alias for AX message - aeaf050d21aeb681a220758f1cc61b9086f73152\n* Fixed JRuby (1.9 mode) incompatibilty - 40baed6cf7326025058a131c2b76047345618539\n* Added UI extension support - a276a63d68639e985c1f327cf817489ccc5f9a17\n* Add attr_reader for setup_url on SetupNeededResponse - 75a7e98005542ede6db3fc7f1fc551e0a2ca044a\n* Encode form inputs - c9e9b5b52f8a23df3159c2387b6330d5df40f35b\n* Fixed cleanup AR associations whose expiry is past, not upcoming - 2265179a6d5c8b51ccc741180db46b618dd3caf9\n* Fixed issue with Memcache store and Dalli - ef84bf73da9c99c67b0632252bf0349e2360cbc7\n* Improvements to ActiveRecordStore's gc rake task - 847e19bf60a6b8163c1e0d2e96dbd805c64e2880\n"
  },
  {
    "path": "CHANGES-2.0.0",
    "content": "\n* API Changes\n  * PAPE (Provider Authentication Policy Extension) module\n      * Updated extension for specification draft 2\n      * PAPE::Request::from_success_response returns nil if PAPE\n        response arguments were not signed\n  * Added functions to generate request/response HTML forms with\n    auto-submission javascript\n      * Consumer (relying party) API:\n        Auth_OpenID_AuthRequest::htmlMarkup\n      * Server API: Auth_OpenID_OpenIDResponse::toHTML\n  * Removed Rails login generator\n  * SReg::Response::from_success_response returns nil when no signed\n    arguments were found\n\n* New Features\n  * Fetchers now only read/request first megabyte of response\n\n* Bug fixes\n  * NOT NULL constraints to tables created by ActiveRecordStore\n  * check_authentication requests: copy entire response, not just\n    signed fields.  Fixes missing namespace in check_authentication\n    requests\n  * OpenID 1 association requests no longer explicitly set\n    no-encryption session type\n  * Improved HTML parsing\n  * AssociationRequest::answer: include session_type in\n    no-encryption assoc responses\n  * normalize return_to URL before performing return_to verification\n  * OpenID::Consumer::IdResHandler.verify_discovery_results_openid1:\n    fall back to OpenID 1.0 type if 1.1 endpoint cannot be found\n  * StandardFetcher now includes a timeout setting\n  * Handle blank content types in\n    OpenID::Yadis::DiscoveryResult.where_is_yadis?\n  * Properly convert timestamps to ints before storing in DB, and vise\n    versa\n"
  },
  {
    "path": "CHANGES-2.1.0",
    "content": "\n* API Changes\n  * PAPE (Provider Authentication Policy Extension) module\n      * Updated extension for specification draft 2\n      * PAPE::Request::from_success_response returns nil if PAPE\n        response arguments were not signed\n  * Added functions to generate request/response HTML forms with\n    auto-submission javascript\n      * Consumer (relying party) API:\n        Auth_OpenID_AuthRequest::htmlMarkup\n      * Server API: Auth_OpenID_OpenIDResponse::toHTML\n  * Removed Rails login generator\n  * SReg::Response::from_success_response returns nil when no signed\n    arguments were found\n\n* New Features\n  * Fetchers now only read/request first megabyte of response\n\n* Bug fixes\n  * NOT NULL constraints to tables created by ActiveRecordStore\n  * check_authentication requests: copy entire response, not just\n    signed fields.  Fixes missing namespace in check_authentication\n    requests\n  * OpenID 1 association requests no longer explicitly set\n    no-encryption session type\n  * Improved HTML parsing\n  * AssociationRequest::answer: include session_type in\n    no-encryption assoc responses\n  * normalize return_to URL before performing return_to verification\n  * OpenID::Consumer::IdResHandler.verify_discovery_results_openid1:\n    fall back to OpenID 1.0 type if 1.1 endpoint cannot be found\n  * StandardFetcher now includes a timeout setting\n  * Handle blank content types in\n    OpenID::Yadis::DiscoveryResult.where_is_yadis?\n  * Properly convert timestamps to ints before storing in DB, and vise\n    versa\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in ruby-openid.gemspec\ngemspec\n\ngem 'rake'\n"
  },
  {
    "path": "INSTALL.md",
    "content": "# Ruby OpenID Library Installation\n\n## Install as a gem\n\n`ruby-openid` is distributed on [RubyGems](https://rubygems.org/).\nInstall it:\n\n    gem install ruby-openid\n\nThis is probably what you need.\n\n## Manual Installation\n\nUnpack the archive and run `setup.rb` to install:\n\n    ruby setup.rb\n\n`setup.rb` installs the library into your system ruby. If don't want to\nadd openid to you system ruby, you may instead add the `lib` directory of\nthe extracted tarball to your `RUBYLIB` environment variable:\n\n    $ export RUBYLIB=${RUBYLIB}:/path/to/ruby-openid/lib\n\n## Testing the Installation\n\nMake sure everything installed ok:\n\n    $> irb\n    irb$> require \"openid\"\n    => true\n\n## Run the test suite\n\nGo into the test directory and execute the `runtests.rb` script.\n\n## Next steps\n\n* Run `consumer.rb` in the `examples/` directory.\n* Get started writing your own consumer using `OpenID::Consumer`\n* Write your own server with `OpenID::Server`\n* Use the `OpenIDLoginGenerator`! Read `examples/README.md` for more info.\n"
  },
  {
    "path": "LICENSE",
    "content": "The code in lib/hmac/ is Copyright 2001 by Daiki Ueno, and distributed under\nthe terms of the Ruby license.  See http://www.ruby-lang.org/en/LICENSE.txt\n\nlib/openid/yadis/htmltokenizer.rb is Copyright 2004 by Ben Giddings and\ndistributed under the terms of the Ruby license.\n\nThe remainder of this package is Copyright 2006-2008 by JanRain, Inc. and\ndistributed under the terms of license below:\n\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "NOTICE",
    "content": "This product includes software developed by JanRain,\navailable from http://github.com/openid/ruby-openid\n"
  },
  {
    "path": "README.md",
    "content": "# Ruby OpenID\n\nA Ruby library for verifying and serving OpenID identities.\n\n[![Build Status](https://secure.travis-ci.org/openid/ruby-openid.svg)](http://travis-ci.org/openid/ruby-openid)\n\n## Features\n\n  * Easy to use API for verifying OpenID identites - OpenID::Consumer\n  * Support for serving OpenID identites - OpenID::Server\n  * Does not depend on underlying web framework\n  * Supports multiple storage mechanisms (Filesystem, ActiveRecord, Memory)\n  * Example code to help you get started, including:\n    * Ruby on Rails based consumer and server\n    * OpenIDLoginGenerator for quickly getting creating a rails app that uses\n      OpenID for authentication\n    * ActiveRecordOpenIDStore plugin\n  * Comprehensive test suite\n  * Supports both OpenID 1 and OpenID 2 transparently\n\n## Installing\n\nBefore running the examples or writing your own code you'll need to install\nthe library.  See the INSTALL file or use rubygems:\n\n    gem install ruby-openid\n\nCheck the installation:\n\n    $ irb\n    irb> require 'rubygems'\n    => false\n    irb> gem 'ruby-openid'\n    => true\n\nThe library is known to work with Ruby 1.9.2 and above on Unix, Max OS X and Win32.\n\n## Getting Started\n\nThe best way to start is to look at the rails_openid example.\nYou can run it with:\n\n    cd examples/rails_openid\n    script/server\n\nIf you are writing an OpenID Relying Party, a good place to start is:\n`examples/rails_openid/app/controllers/consumer_controller.rb`\n\nAnd if you are writing an OpenID provider:\n`examples/rails_openid/app/controllers/server_controller.rb`\n\nThe library code is quite well documented, so don't be squeamish, and\nlook at the library itself if there's anything you don't understand in\nthe examples.\n\n## Homepage\n\n  * GitHub repository: [openid/ruby-openid](http://github.com/openid/ruby-openid)\n  * Homepage: [OpenID.net](http://openid.net/)\n\n## Community\n\nDiscussion regarding the Ruby OpenID library and other JanRain OpenID\nlibraries takes place on the [OpenID mailing list](http://openid.net/developers/dev-mailing-lists/).\n\nPlease join this list to discuss, ask implementation questions, report\nbugs, etc. Also check out the openid channel on the freenode IRC\nnetwork.\n\nIf you have a bugfix or feature you'd like to contribute, don't\nhesitate to send it to us: [How to contribute](http://openidenabled.com/contribute/).\n\n## Author\n\nCopyright 2006-2012, JanRain, Inc.\n\nContact openid@janrain.com.\n\n## License\n\nApache Software License.  For more information see the LICENSE file.\n"
  },
  {
    "path": "Rakefile",
    "content": "#!/usr/bin/env rake\nrequire 'bundler/gem_tasks'\n\nrequire 'rake/testtask'\n\ndesc \"Run tests\"\nRake::TestTask.new('test') do |t|\n  t.libs << 'lib'\n  t.libs << 'test'\n  t.test_files = FileList[\"test/**/test_*.rb\"]\n  t.verbose = false\nend\n\ntask :default => :test\n"
  },
  {
    "path": "UPGRADE.md",
    "content": "# Upgrading from the OpenID 1.x series library\n\n## Consumer Upgrade\n\nThe flow is largely the same, however there are a number of significant\nchanges. The consumer example is helpful to look at:\n`examples/rails_openid/app/controllers/consumer_controller.rb`\n\n### Stores\n\nYou will need to require the file for the store that you are using.\nFor the filesystem store, this is 'openid/stores/filesystem'\nThey are also now in modules. The filesystem store is\n  `OpenID::Store::Filesystem`\nThe format has changed, and you should remove your old store directory.\n\nThe ActiveRecord store (`examples/active_record_openid_store`) still needs\nto be put in a plugin directory for your rails app.  There's a migration\nthat needs to be run; examine the `README` in that directory.\n\nAlso, note that the stores now can be garbage collected with the method\n  `store.cleanup`\n\n### Starting the OpenID transaction\n\nThe OpenIDRequest object no longer has status codes.  Instead,\nconsumer.begin raises an OpenID::OpenIDError if there is a problem\ninitiating the transaction, so you'll want something along the lines of:\n\n    begin\n      openid_request = consumer.begin(params[:openid_identifier])\n    rescue OpenID::OpenIDError => e\n      # display error e\n      return\n    end\n    #success case\n\nData regarding the OpenID server once lived in\n  `openid_request.service`\n\nThe corresponding object in the 2.0 lib can be retrieved with\n  `openid_request.endpoint`\n\nGetting the unverified identifier: Where you once had\n  `openid_request.identity_url`\nyou will now want\n  `openid_request.endpoint.claimed_id`\nwhich might be different from what you get at the end of the transaction,\nsince it is now possible for users to enter their server's url directly.\n\nArguments on the return_to URL are now verified, so if you want to add\nadditional arguments to the return_to url, use\n  `openid_request.return_to_args['param'] = value`\n\nGenerating the redirect is the same as before, but add any extensions\nfirst.\n\nIf you need to set up an SSL certificate authority list for the fetcher,\nuse the 'ca_file' attr_accessor on the `OpenID::StandardFetcher`. This has\nchanged from 'ca_path' in the 1.x.x series library. That is, set\n`OpenID.fetcher.ca_file = '/path/to/ca.list'`\nbefore calling consumer.begin.\n\n### Requesting Simple Registration Data\n\nYou'll need to require the code for the extension\n\n    require 'openid/extensions/sreg'\n\nThe new code for adding an SReg request now looks like:\n\n    sreg_request = OpenID::SReg::Request.new\n    sreg_request.request_fields(['email', 'dob'], true) # required\n    sreg_request.request_fields(['nickname', 'fullname'], false) # optional\n    sreg_request.policy_url = policy_url\n    openid_request.add_extension(sreg_request)\n\nThe code for adding other extensions is similar.  Code for the Attribute\nExchange (AX) and Provider Authentication Policy Extension (PAPE) are\nincluded with the library, and additional extensions can be implemented\nsubclassing `OpenID::Extension`.\n\n### Completing the transaction\n\nThe return_to and its arguments are verified, so you need to pass in\nthe base URL and the arguments.  With Rails, the params method mashes\ntogether parameters from GET, POST, and the path, so you'll need to pull\noff the path \"parameters\" with something like\n\n    return_to = url_for(:only_path => false,\n                        :controller => 'openid',\n                        :action => 'complete')\n    parameters = params.reject{|k,v| request.path_parameters[k] }\n    openid_response = consumer.complete(parameters, return_to)\n\nThe response still uses the status codes, but they are now namespaced\nslightly differently, for example `OpenID::Consumer::SUCCESS`\n\nIn the case of failure, the error message is now found in\n  `openid_response.message`\n\nThe identifier to display to the user can be found in\n  `openid_response.endpoint.display_identifier`\n\nThe Simple Registration response can be read from the OpenID response\nwith\n\n    sreg_response = OpenID::SReg::Response.from_success_response(openid_response)\n    nickname = sreg_response['nickname']\n    # etc.\n\n## Server Upgrade\n\nThe server code is mostly the same as before, with the exception of\nextensions. Also, you must pass in the endpoint URL to the server\nconstructor:\n\n    @server = OpenID::Server.new(store, server_url)\n\nI recommend looking at\n`examples/rails_openid/app/controllers/server_controller.rb`\nfor an example of the new way of doing extensions.\n\n--\nDag Arneson, JanRain Inc.\nPlease direct questions to openid@janrain.com\n"
  },
  {
    "path": "admin/build-docs",
    "content": "#!/usr/bin/env bash\n#\n# Build the HTML documentation for the JanRain PHP OpenID library\n#\n# Usage:\n#  build-docs\n#\n# Must be run from the base of the repository\n\nRDOC_FILES=\"README INSTALL LICENSE UPGRADE lib/openid examples/README\"\nMAIN=README\nrdoc --main=\"$MAIN\" $RDOC_FILES"
  },
  {
    "path": "admin/fixperms",
    "content": "#!/usr/bin/env bash\ncat <<EOF | xargs chmod +x\nadmin/prepare-release\nadmin/build-docs\nadmin/fixperms\nadmin/runtests\nadmin/graph-require.sh\nexamples/discover\nEOF\n\nfind \"examples/rails_openid/script/\" -type f | xargs chmod +x\n"
  },
  {
    "path": "admin/gettlds.py",
    "content": "\"\"\"\nFetch the current TLD list from the IANA Web site, parse it, and print\nan expression suitable for direct insertion into each library's trust\nroot validation module\n\nUsage:\n  python gettlds.py (php|python|ruby)\n\nThen cut-n-paste.\n\"\"\"\n\nimport urllib2\n\nimport sys\n\nlangs = {\n    'php': (r\"'/\\.(\",\n            \"'\", \"|\", \"|' .\",\n            r\")\\.?$/'\"),\n    'python': (\"['\",\n               \"'\", \"', '\", \"',\",\n               \"']\"),\n    'ruby': (\"%w'\",\n             \"\", \" \", \"\",\n             \"'\"),\n    }\n\nlang = sys.argv[1]\nprefix, line_prefix, separator, line_suffix, suffix = langs[lang]\n\nf = urllib2.urlopen('http://data.iana.org/TLD/tlds-alpha-by-domain.txt')\ntlds = []\noutput_line = \"\"\nfor input_line in f:\n    if input_line.startswith('#'):\n        continue\n\n    tld = input_line.strip().lower()\n    new_output_line = output_line + prefix + tld\n    if len(new_output_line) > 60:\n        print output_line + line_suffix\n        output_line = line_prefix + tld\n    else:\n        output_line = new_output_line\n    prefix = separator\n\nprint output_line + suffix\n"
  },
  {
    "path": "admin/graph-require.sh",
    "content": "#!/usr/bin/env bash\n\nOUTPUT_FILE=\"deps.png\"\n\nif [ ! \"$1\" ] ; then\n  echo \"Usage: graph-require.sh <lib_directory> [output_filename]\"\n  exit 1\nfi\n\nif [ \"$2\" ] ; then\n  OUTPUT_FILE=$2\nfi\n\ngrep -r '^ *require ['\"'\"'\"]' $1 > require.txt\n\npython <<EOF\nimport re\nimport pydot\nimport sys\n\nparse_require = re.compile(\n    '\\\\\\\\blib/([^:]+).rb: *require [\"\\\\']([^\"\\\\']+)[\\\\'\"]\\$',\n    re.MULTILINE)\nmatches = [(file, dep) for (file, dep)\n           in parse_require.findall(file('require.txt').read())\n           if re.match('(yadis|openid)($|/)', dep)\n          ]\ng = pydot.graph_from_edges(matches, directed=True)\ng.write_png('$OUTPUT_FILE')\nEOF\n"
  },
  {
    "path": "admin/library-name",
    "content": "ruby-openid"
  },
  {
    "path": "admin/mkassoc",
    "content": "#!/usr/bin/env ruby\n\nrequire \"openid/consumer/associationmanager\"\nrequire \"openid/store/memory\"\n\nstore = OpenID::Store::Memory.new\nARGV.each do |server_url|\n  unless URI::regexp =~ server_url\n    puts \"`#{server_url}` will be skipped for invalid URI format.\"\n    next\n  end\n\n  mgr = OpenID::Consumer::AssociationManager.new(store, server_url)\n  puts '=' * 50\n  puts \"Server: #{server_url}\"\n  puts mgr.get_association.serialize\n  puts '-' * 50\nend\n"
  },
  {
    "path": "admin/prepare-release",
    "content": "#!/usr/bin/env bash\n#\n# Prepare this repository for release\n#\n# required tools:\n#  rdoc\n#  darcs\n\nset -e\n\nHERE=$(readlink --canonicalize $(dirname \"$0\"))\nROOT=$(dirname \"$HERE\")\n\ncd \"$ROOT\"\n\n# set permissions\nbash ./admin/fixperms\n\n# build documentation\n./admin/build-docs\n\n# build changelog\ndarcs changes --from-tag . --summary > CHANGELOG\n\n"
  },
  {
    "path": "admin/runtests",
    "content": "#!/usr/bin/env bash\n\ncase \"$1\" in\n    --coverage)\n        shift\n\tRUBY=\"rcov --exclude=^lib/hmac/,^admin/ --sort=coverage\"\n\t;;\n    *)\n\tRUBY=\"ruby\"\n\t;;\nesac\n\nHERE=$(dirname $(readlink --canonicalize \"$0\"))\nREPOROOT=$(dirname \"$HERE\")\nTESTING_MEMCACHE=\"localhost:11211\" RUBYLIB=\"$REPOROOT/lib\" $RUBY \"$@\" \"$REPOROOT/admin/runtests.rb\"\n"
  },
  {
    "path": "examples/README.md",
    "content": "This directory contains several examples that demonstrate use of the\nOpenID library.  Make sure you have properly installed the library\nbefore running the examples.  These examples are a great place to\nstart in integrating OpenID into your application.\n\n## Rails example\n\nThe `rails_openid` directory contains a fully functional OpenID server and relying\nparty, and acts as a starting point for implementing your own\nproduction rails server.  You'll need the latest version of Ruby on\nRails installed, and then:\n\n```shell\ncd rails_openid\n./script/server\n```\n\nOpen a web browser to http://localhost:3000/ and follow the instructions.\n\nThe relevant code to work from when writing your Rails OpenID Relying\nParty is: \n\n  rails_openid/app/controllers/consumer_controller.rb\n\nIf you are working on an OpenID provider, check out\n\n  rails_openid/app/controllers/server_controller.rb\n\nSince the library and examples are Apache-licensed, don't be shy about \ncopy-and-paste.\n\n## Rails ActiveRecord OpenIDStore plugin\n\nFor various reasons you may want or need to deploy your ruby openid\nconsumer/server using an SQL based store.  The `active_record_openid_store` \nis a plugin that makes using an SQL based store simple.  Follow the\nREADME inside the plugin's dir for usage.\n"
  },
  {
    "path": "examples/active_record_openid_store/README",
    "content": "=Active Record OpenID Store Plugin\n\nA store is required by an OpenID server and optionally by the consumer\nto store associations, nonces, and auth key information across\nrequests and processes.  If rails is distributed across several\nmachines, they must must all have access to the same OpenID store\ndata, so the FilesystemStore won't do.\n\nThis directory contains a plugin for connecting your\nOpenID enabled rails app to an ActiveRecord based OpenID store.\n\n==Install\n\n1) Copy this directory and all it's contents into your\nRAILS_ROOT/vendor/plugins directory.  You structure should look like\nthis:\n\n  RAILS_ROOT/vendor/plugins/active_record_openid_store/\n\n2) Copy the migration, XXX_add_open_id_store_to_db.rb to your\n   RAILS_ROOT/db/migrate directory.  Rename the XXX portion of the\n   file to next sequential migration number.\n\n3) Run the migration:\n\n  rake migrate\n\n4) Change your app to use the ActiveRecordOpenIDStore:\n\n  store = ActiveRecordOpenIDStore.new\n  consumer = OpenID::Consumer.new(session, store)\n\n5) That's it! All your OpenID state will now be stored in the database.\n\n==Upgrade\n\nIf you are upgrading from the 1.x ActiveRecord store, replace your old\nRAILS_ROOT/vendor/plugins/active_record_openid_store/ directory with\nthe new one and run the migration XXX_upgrade_open_id_store.rb.\n\n==What about garbage collection? \n\nYou may garbage collect unused nonces and expired associations using\nthe gc instance method of ActiveRecordOpenIDStore.  Hook it up to a\ntask in your app's Rakefile like so:\n\n  desc 'GC OpenID store, deleting expired nonces and associations'\n  task :gc_openid_store => :environment do\n    require 'openid_ar_store'\n    nonces, associations = ActiveRecordStore.new.cleanup\n    puts \"Deleted #{nonces} nonces, #{associations} associations\"\n  end\n\nRun it by typing:\n\n  rake gc_openid_store\n\n\n==Questions?\nContact Dag Arneson: dag at janrain dot com\n"
  },
  {
    "path": "examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb",
    "content": "# Use this migration to create the tables for the ActiveRecord store\nclass AddOpenIdStoreToDb < ActiveRecord::Migration\n  def self.up\n    create_table \"open_id_associations\", :force => true do |t|\n      t.column \"server_url\", :string, :null => false\n      t.column \"handle\", :string, :null => false\n      t.column \"secret\", :binary, :null => false\n      t.column \"issued\", :integer, :null => false\n      t.column \"lifetime\", :integer, :null => false\n      t.column \"assoc_type\", :string, :null => false\n    end\n\n    create_table \"open_id_nonces\", :force => true do |t|\n      t.column :server_url, :string, :null => false\n      t.column :timestamp, :integer, :null => false\n      t.column :salt, :string, :null => false\n    end\n  end\n\n  def self.down\n    drop_table \"open_id_associations\"\n    drop_table \"open_id_nonces\"\n  end\nend\n"
  },
  {
    "path": "examples/active_record_openid_store/XXX_upgrade_open_id_store.rb",
    "content": "# Use this migration to upgrade the old 1.1 ActiveRecord store schema\n# to the new 2.0 schema.\nclass UpgradeOpenIdStore < ActiveRecord::Migration\n  def self.up\n    drop_table \"open_id_settings\"\n    drop_table \"open_id_nonces\"\n    create_table \"open_id_nonces\", :force => true do |t|\n      t.column :server_url, :string, :null => false\n      t.column :timestamp, :integer, :null => false\n      t.column :salt, :string, :null => false\n    end\n  end\n\n  def self.down\n    drop_table \"open_id_nonces\"\n    create_table \"open_id_nonces\", :force => true do |t|\n      t.column \"nonce\", :string\n      t.column \"created\", :integer\n    end\n\n    create_table \"open_id_settings\", :force => true do |t|\n      t.column \"setting\", :string\n      t.column \"value\", :binary\n    end\n  end\nend\n"
  },
  {
    "path": "examples/active_record_openid_store/init.rb",
    "content": "# might using the ruby-openid gem\nbegin\n  require 'rubygems'\nrescue LoadError\n  nil\nend\nrequire 'openid'\nrequire 'openid_ar_store'\n"
  },
  {
    "path": "examples/active_record_openid_store/lib/association.rb",
    "content": "require 'openid/association'\nrequire 'time'\n\nclass Association < ActiveRecord::Base\n  set_table_name 'open_id_associations'\n  def from_record\n    OpenID::Association.new(handle, secret, Time.at(issued), lifetime, assoc_type)\n  end\nend\n\n"
  },
  {
    "path": "examples/active_record_openid_store/lib/nonce.rb",
    "content": "class Nonce < ActiveRecord::Base\n  set_table_name 'open_id_nonces'\nend\n"
  },
  {
    "path": "examples/active_record_openid_store/lib/open_id_setting.rb",
    "content": "class OpenIdSetting < ActiveRecord::Base\n  \n  validates_uniqueness_of :setting\nend\n"
  },
  {
    "path": "examples/active_record_openid_store/lib/openid_ar_store.rb",
    "content": "require 'association'\nrequire 'nonce'\nrequire 'openid/store/interface'\n\n# not in OpenID module to avoid namespace conflict\nclass ActiveRecordStore < OpenID::Store::Interface\n  def store_association(server_url, assoc)\n    remove_association(server_url, assoc.handle)    \n    Association.create!(:server_url => server_url,\n                       :handle     => assoc.handle,\n                       :secret     => assoc.secret,\n                       :issued     => assoc.issued.to_i,\n                       :lifetime   => assoc.lifetime,\n                       :assoc_type => assoc.assoc_type)\n  end\n\n  def get_association(server_url, handle=nil)\n    assocs = if handle.blank?\n        Association.find_all_by_server_url(server_url)\n      else\n        Association.find_all_by_server_url_and_handle(server_url, handle)\n      end\n\n    assocs.reverse.each do |assoc|\n      a = assoc.from_record    \n      if a.expires_in == 0\n        assoc.destroy\n      else\n        return a\n      end\n    end if assocs.any?\n    \n    return nil\n  end\n  \n  def remove_association(server_url, handle)\n    Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0\n  end\n  \n  def use_nonce(server_url, timestamp, salt)\n    return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)\n    return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew\n    Nonce.create!(:server_url => server_url, :timestamp => timestamp, :salt => salt)\n    return true\n  end\n  \n  def cleanup_nonces\n    now = Time.now.to_i\n    Nonce.delete_all([\"timestamp > ? OR timestamp < ?\", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])\n  end\n\n  def cleanup_associations\n    now = Time.now.to_i\n    Association.delete_all(['issued + lifetime < ?',now])\n  end\n\nend\n"
  },
  {
    "path": "examples/active_record_openid_store/test/store_test.rb",
    "content": "$:.unshift(File.dirname(__FILE__) + '/../lib')\nrequire 'test/unit'\nRAILS_ENV = \"test\"\nrequire File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))\n\nmodule StoreTestCase\n  @@allowed_handle = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'()*+,-./:;<=>?@[\\\\]^_`{|}~'\n  @@allowed_nonce = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n  \n  def _gen_nonce\n    OpenID::CryptUtil.random_string(8, @@allowed_nonce)\n  end\n\n  def _gen_handle(n)\n    OpenID::CryptUtil.random_string(n, @@allowed_handle)\n  end\n\n  def _gen_secret(n, chars=nil)\n    OpenID::CryptUtil.random_string(n, chars)\n  end\n\n  def _gen_assoc(issued, lifetime=600)\n    secret = _gen_secret(20)\n    handle = _gen_handle(128)\n    OpenID::Association.new(handle, secret, Time.now + issued, lifetime,\n                            'HMAC-SHA1') \n  end\n  \n  def _check_retrieve(url, handle=nil, expected=nil)\n    ret_assoc = @store.get_association(url, handle)\n\n    if expected.nil?\n      assert_nil(ret_assoc)\n    else\n      assert_equal(expected, ret_assoc)\n      assert_equal(expected.handle, ret_assoc.handle)\n      assert_equal(expected.secret, ret_assoc.secret)\n    end\n  end\n\n  def _check_remove(url, handle, expected)\n    present = @store.remove_association(url, handle)\n    assert_equal(expected, present)\n  end\n\n  def test_store\n    server_url = \"http://www.myopenid.com/openid\"\n    assoc = _gen_assoc(issued=0)\n\n    # Make sure that a missing association returns no result\n    _check_retrieve(server_url)\n\n    # Check that after storage, getting returns the same result\n    @store.store_association(server_url, assoc)\n    _check_retrieve(server_url, nil, assoc)\n\n    # more than once\n    _check_retrieve(server_url, nil, assoc)\n\n    # Storing more than once has no ill effect\n    @store.store_association(server_url, assoc)\n    _check_retrieve(server_url, nil, assoc)\n\n    # Removing an association that does not exist returns not present\n    _check_remove(server_url, assoc.handle + 'x', false)\n\n    # Removing an association that does not exist returns not present\n    _check_remove(server_url + 'x', assoc.handle, false)\n\n    # Removing an association that is present returns present\n    _check_remove(server_url, assoc.handle, true)\n\n    # but not present on subsequent calls\n    _check_remove(server_url, assoc.handle, false)\n\n    # Put assoc back in the store\n    @store.store_association(server_url, assoc)\n\n    # More recent and expires after assoc\n    assoc2 = _gen_assoc(issued=1)\n    @store.store_association(server_url, assoc2)\n\n    # After storing an association with a different handle, but the\n    # same server_url, the handle with the later expiration is returned.\n    _check_retrieve(server_url, nil, assoc2)\n\n    # We can still retrieve the older association\n    _check_retrieve(server_url, assoc.handle, assoc)\n\n    # Plus we can retrieve the association with the later expiration\n    # explicitly\n    _check_retrieve(server_url, assoc2.handle, assoc2)\n\n    # More recent, and expires earlier than assoc2 or assoc. Make sure\n    # that we're picking the one with the latest issued date and not\n    # taking into account the expiration.\n    assoc3 = _gen_assoc(issued=2, lifetime=100)\n    @store.store_association(server_url, assoc3)\n\n    _check_retrieve(server_url, nil, assoc3)\n    _check_retrieve(server_url, assoc.handle, assoc)\n    _check_retrieve(server_url, assoc2.handle, assoc2)\n    _check_retrieve(server_url, assoc3.handle, assoc3)\n\n    _check_remove(server_url, assoc2.handle, true)\n\n    _check_retrieve(server_url, nil, assoc3)\n    _check_retrieve(server_url, assoc.handle, assoc)\n    _check_retrieve(server_url, assoc2.handle, nil)\n    _check_retrieve(server_url, assoc3.handle, assoc3)\n\n    _check_remove(server_url, assoc2.handle, false)\n    _check_remove(server_url, assoc3.handle, true)\n\n    _check_retrieve(server_url, nil, assoc)\n    _check_retrieve(server_url, assoc.handle, assoc)\n    _check_retrieve(server_url, assoc2.handle, nil)\n    _check_retrieve(server_url, assoc3.handle, nil)\n\n    _check_remove(server_url, assoc2.handle, false)\n    _check_remove(server_url, assoc.handle, true)\n    _check_remove(server_url, assoc3.handle, false)\n\n    _check_retrieve(server_url, nil, nil)\n    _check_retrieve(server_url, assoc.handle, nil)\n    _check_retrieve(server_url, assoc2.handle, nil)\n    _check_retrieve(server_url, assoc3.handle, nil)\n\n    _check_remove(server_url, assoc2.handle, false)\n    _check_remove(server_url, assoc.handle, false)\n    _check_remove(server_url, assoc3.handle, false)\n\n    assocValid1 = _gen_assoc(-3600, 7200)\n    assocValid2 = _gen_assoc(-5)\n    assocExpired1 = _gen_assoc(-7200, 3600)\n    assocExpired2 = _gen_assoc(-7200, 3600)\n\n    @store.cleanup_associations\n    @store.store_association(server_url + '1', assocValid1)\n    @store.store_association(server_url + '1', assocExpired1)\n    @store.store_association(server_url + '2', assocExpired2)\n    @store.store_association(server_url + '3', assocValid2)\n\n    cleaned = @store.cleanup_associations()\n    assert_equal(2, cleaned, \"cleaned up associations\")\n  end\n\n  def _check_use_nonce(nonce, expected, server_url, msg='')\n    stamp, salt = OpenID::Nonce::split_nonce(nonce)\n    actual = @store.use_nonce(server_url, stamp, salt)\n    assert_equal(expected, actual, msg)\n  end\n\n  def test_nonce\n    server_url = \"http://www.myopenid.com/openid\"\n    [server_url, ''].each{|url|\n      nonce1 = OpenID::Nonce::mk_nonce\n\n      _check_use_nonce(nonce1, true, url, \"#{url}: nonce allowed by default\") \n      _check_use_nonce(nonce1, false, url, \"#{url}: nonce not allowed twice\") \n      _check_use_nonce(nonce1, false, url, \"#{url}: nonce not allowed third time\")\n      \n      # old nonces shouldn't pass\n      old_nonce = OpenID::Nonce::mk_nonce(3600)\n      _check_use_nonce(old_nonce, false, url, \"Old nonce #{old_nonce.inspect} passed\")\n\n    }\n\n    now = Time.now.to_i\n    old_nonce1 = OpenID::Nonce::mk_nonce(now - 20000)\n    old_nonce2 = OpenID::Nonce::mk_nonce(now - 10000)\n    recent_nonce = OpenID::Nonce::mk_nonce(now - 600)\n\n    orig_skew = OpenID::Nonce.skew\n    OpenID::Nonce.skew = 0\n    count = @store.cleanup_nonces\n    OpenID::Nonce.skew = 1000000\n    ts, salt = OpenID::Nonce::split_nonce(old_nonce1)\n    assert(@store.use_nonce(server_url, ts, salt), \"oldnonce1\")\n    ts, salt = OpenID::Nonce::split_nonce(old_nonce2)\n    assert(@store.use_nonce(server_url, ts, salt), \"oldnonce2\")\n    ts, salt = OpenID::Nonce::split_nonce(recent_nonce)\n    assert(@store.use_nonce(server_url, ts, salt), \"recent_nonce\")\n\n    \n    OpenID::Nonce.skew = 1000\n    cleaned = @store.cleanup_nonces\n    assert_equal(2, cleaned, \"Cleaned #{cleaned} nonces\")\n\n    OpenID::Nonce.skew = 100000\n    ts, salt = OpenID::Nonce::split_nonce(old_nonce1)\n    assert(@store.use_nonce(server_url, ts, salt), \"oldnonce1 after cleanup\")\n    ts, salt = OpenID::Nonce::split_nonce(old_nonce2)\n    assert(@store.use_nonce(server_url, ts, salt), \"oldnonce2 after cleanup\")\n    ts, salt = OpenID::Nonce::split_nonce(recent_nonce)\n    assert(!@store.use_nonce(server_url, ts, salt), \"recent_nonce after cleanup\")\n\n    OpenID::Nonce.skew = orig_skew\n\n  end\nend\n\n\nclass TestARStore < Test::Unit::TestCase\n  include StoreTestCase\n  \n  def setup\n    @store = ActiveRecordStore.new\n  end\n\nend\n\n"
  },
  {
    "path": "examples/discover",
    "content": "#!/usr/bin/env ruby\nrequire \"openid/consumer/discovery\"\nrequire 'openid/fetchers'\n\nOpenID::fetcher_use_env_http_proxy\n\n$names = [[:server_url,   \"Server URL  \"],\n          [:local_id,     \"Local ID    \"],\n          [:canonical_id, \"Canonical ID\"],\n         ]\n\ndef show_services(user_input, normalized, services)\n  puts \" Claimed identifier: #{normalized}\"\n  if services.empty?\n    puts \" No OpenID services found\"\n    puts\n  else\n    puts \" Discovered services:\"\n    n = 0\n    services.each do |service|\n      n += 1\n      puts \"  #{n}.\"\n      $names.each do |meth, name|\n        val = service.send(meth)\n        if val\n          printf(\"     %s: %s\\n\", name, val)\n        end\n      end\n      puts \"     Type URIs:\"\n      for type_uri in service.type_uris\n        puts \"       * #{type_uri}\"\n      end\n      puts\n    end\n  end\nend\n\nARGV.each do |openid_identifier|\n  puts \"=\" * 50\n  puts \"Running discovery on #{openid_identifier}\"\n  begin\n    normalized_identifier, services = OpenID.discover(openid_identifier)\n  rescue OpenID::DiscoveryFailure => why\n    puts \"Discovery failed: #{why.message}\"\n    puts\n  else\n    show_services(openid_identifier, normalized_identifier, services)\n  end\nend\n"
  },
  {
    "path": "examples/rails_openid/Gemfile",
    "content": "source 'https://rubygems.org'\n\ngem 'rails', '3.2.13'\n\n# Bundle edge Rails instead:\n# gem 'rails', :git => 'git://github.com/rails/rails.git'\n\ngem 'sqlite3'\n\ngem 'json'\n\n# Gems used only for assets and not required\n# in production environments by default.\ngroup :assets do\n  gem 'sass-rails',   '~> 3.2.3'\n  gem 'coffee-rails', '~> 3.2.1'\n\n  # See https://github.com/sstephenson/execjs#readme for more supported runtimes\n  # gem 'therubyracer', :platforms => :ruby\n\n  gem 'uglifier', '>= 1.0.3'\nend\n\ngem 'jquery-rails'\n\n# To use ActiveModel has_secure_password\n# gem 'bcrypt-ruby', '~> 3.0.0'\n\n# To use Jbuilder templates for JSON\n# gem 'jbuilder'\n\n# Use unicorn as the app server\n# gem 'unicorn'\n\n# Deploy with Capistrano\n# gem 'capistrano'\n\n# To use debugger\n# gem 'ruby-debug'\n\ngem 'ruby-openid', :require => 'openid'\n"
  },
  {
    "path": "examples/rails_openid/README",
    "content": "== Welcome to Rails\n\nRails is a web-application and persistence framework that includes everything\nneeded to create database-backed web-applications according to the\nModel-View-Control pattern of separation. This pattern splits the view (also\ncalled the presentation) into \"dumb\" templates that are primarily responsible\nfor inserting pre-built data in between HTML tags. The model contains the\n\"smart\" domain objects (such as Account, Product, Person, Post) that holds all\nthe business logic and knows how to persist themselves to a database. The\ncontroller handles the incoming requests (such as Save New Account, Update\nProduct, Show Post) by manipulating the model and directing data to the view.\n\nIn Rails, the model is handled by what's called an object-relational mapping\nlayer entitled Active Record. This layer allows you to present the data from\ndatabase rows as objects and embellish these data objects with business logic\nmethods. You can read more about Active Record in \nlink:files/vendor/rails/activerecord/README.html.\n\nThe controller and view are handled by the Action Pack, which handles both\nlayers by its two parts: Action View and Action Controller. These two layers\nare bundled in a single package due to their heavy interdependence. This is\nunlike the relationship between the Active Record and Action Pack that is much\nmore separate. Each of these packages can be used independently outside of\nRails.  You can read more about Action Pack in \nlink:files/vendor/rails/actionpack/README.html.\n\n\n== Getting started\n\n1. Run the WEBrick servlet: <tt>ruby script/server</tt> (run with --help for options)\n   ...or if you have lighttpd installed: <tt>ruby script/lighttpd</tt> (it's faster)\n2. Go to http://localhost:3000/ and get \"Congratulations, you've put Ruby on Rails!\"\n3. Follow the guidelines on the \"Congratulations, you've put Ruby on Rails!\" screen\n\n\n== Example for Apache conf\n\n  <VirtualHost *:80>\n    ServerName rails\n    DocumentRoot /path/application/public/\n    ErrorLog /path/application/log/server.log\n  \n    <Directory /path/application/public/>\n      Options ExecCGI FollowSymLinks\n      AllowOverride all\n      Allow from all\n      Order allow,deny\n    </Directory>\n  </VirtualHost>\n\nNOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI\nshould be on and \".cgi\" should respond. All requests from 127.0.0.1 go\nthrough CGI, so no Apache restart is necessary for changes. All other requests\ngo through FCGI (or mod_ruby), which requires a restart to show changes.\n\n\n== Debugging Rails\n\nHave \"tail -f\" commands running on both the server.log, production.log, and\ntest.log files. Rails will automatically display debugging and runtime\ninformation to these files. Debugging info will also be shown in the browser\non requests from 127.0.0.1.\n\n\n== Breakpoints\n\nBreakpoint support is available through the script/breakpointer client. This\nmeans that you can break out of execution at any point in the code, investigate\nand change the model, AND then resume execution! Example:\n\n  class WeblogController < ActionController::Base\n    def index\n      @posts = Post.find_all\n      breakpoint \"Breaking out from the list\"\n    end\n  end\n  \nSo the controller will accept the action, run the first line, then present you\nwith a IRB prompt in the breakpointer window. Here you can do things like:\n\nExecuting breakpoint \"Breaking out from the list\" at .../webrick_server.rb:16 in 'breakpoint'\n\n  >> @posts.inspect\n  => \"[#<Post:0x14a6be8 @attributes={\\\"title\\\"=>nil, \\\"body\\\"=>nil, \\\"id\\\"=>\\\"1\\\"}>, \n       #<Post:0x14a6620 @attributes={\\\"title\\\"=>\\\"Rails you know!\\\", \\\"body\\\"=>\\\"Only ten..\\\", \\\"id\\\"=>\\\"2\\\"}>]\"\n  >> @posts.first.title = \"hello from a breakpoint\"\n  => \"hello from a breakpoint\"\n\n...and even better is that you can examine how your runtime objects actually work:\n\n  >> f = @posts.first \n  => #<Post:0x13630c4 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>\n  >> f.\n  Display all 152 possibilities? (y or n)\n\nFinally, when you're ready to resume execution, you press CTRL-D\n\n\n== Console\n\nYou can interact with the domain model by starting the console through script/console. \nHere you'll have all parts of the application configured, just like it is when the\napplication is running. You can inspect domain models, change values, and save to the\ndatabase. Starting the script without arguments will launch it in the development environment.\nPassing an argument will specify a different environment, like <tt>console production</tt>.\n\n\n== Description of contents\n\napp\n  Holds all the code that's specific to this particular application.\n\napp/controllers\n  Holds controllers that should be named like weblog_controller.rb for\n  automated URL mapping. All controllers should descend from\n  ActionController::Base.\n\napp/models\n  Holds models that should be named like post.rb.\n  Most models will descend from ActiveRecord::Base.\n  \napp/views\n  Holds the template files for the view that should be named like\n  weblog/index.rhtml for the WeblogController#index action. All views use eRuby\n  syntax. This directory can also be used to keep stylesheets, images, and so on\n  that can be symlinked to public.\n  \napp/helpers\n  Holds view helpers that should be named like weblog_helper.rb.\n\nconfig\n  Configuration files for the Rails environment, the routing map, the database, and other dependencies.\n\ncomponents\n  Self-contained mini-applications that can bundle together controllers, models, and views.\n\nlib\n  Application specific libraries. Basically, any kind of custom code that doesn't\n  belong under controllers, models, or helpers. This directory is in the load path.\n    \npublic\n  The directory available for the web server. Contains subdirectories for images, stylesheets,\n  and javascripts. Also contains the dispatchers and the default HTML files.\n\nscript\n  Helper scripts for automation and generation.\n\ntest\n  Unit and functional tests along with fixtures.\n\nvendor\n  External libraries that the application depends on. Also includes the plugins subdirectory.\n  This directory is in the load path.\n"
  },
  {
    "path": "examples/rails_openid/README.rdoc",
    "content": "== Welcome to Rails\n\nRails is a web-application framework that includes everything needed to create\ndatabase-backed web applications according to the Model-View-Control pattern.\n\nThis pattern splits the view (also called the presentation) into \"dumb\"\ntemplates that are primarily responsible for inserting pre-built data in between\nHTML tags. The model contains the \"smart\" domain objects (such as Account,\nProduct, Person, Post) that holds all the business logic and knows how to\npersist themselves to a database. The controller handles the incoming requests\n(such as Save New Account, Update Product, Show Post) by manipulating the model\nand directing data to the view.\n\nIn Rails, the model is handled by what's called an object-relational mapping\nlayer entitled Active Record. This layer allows you to present the data from\ndatabase rows as objects and embellish these data objects with business logic\nmethods. You can read more about Active Record in\nlink:files/vendor/rails/activerecord/README.html.\n\nThe controller and view are handled by the Action Pack, which handles both\nlayers by its two parts: Action View and Action Controller. These two layers\nare bundled in a single package due to their heavy interdependence. This is\nunlike the relationship between the Active Record and Action Pack that is much\nmore separate. Each of these packages can be used independently outside of\nRails. You can read more about Action Pack in\nlink:files/vendor/rails/actionpack/README.html.\n\n\n== Getting Started\n\n1. At the command prompt, create a new Rails application:\n       <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)\n\n2. Change directory to <tt>myapp</tt> and start the web server:\n       <tt>cd myapp; rails server</tt> (run with --help for options)\n\n3. Go to http://localhost:3000/ and you'll see:\n       \"Welcome aboard: You're riding Ruby on Rails!\"\n\n4. Follow the guidelines to start developing your application. You can find\nthe following resources handy:\n\n* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html\n* Ruby on Rails Tutorial Book: http://www.railstutorial.org/\n\n\n== Debugging Rails\n\nSometimes your application goes wrong. Fortunately there are a lot of tools that\nwill help you debug it and get it back on the rails.\n\nFirst area to check is the application log files. Have \"tail -f\" commands\nrunning on the server.log and development.log. Rails will automatically display\ndebugging and runtime information to these files. Debugging info will also be\nshown in the browser on requests from 127.0.0.1.\n\nYou can also log your own messages directly into the log file from your code\nusing the Ruby logger class from inside your controllers. Example:\n\n  class WeblogController < ActionController::Base\n    def destroy\n      @weblog = Weblog.find(params[:id])\n      @weblog.destroy\n      logger.info(\"#{Time.now} Destroyed Weblog ID ##{@weblog.id}!\")\n    end\n  end\n\nThe result will be a message in your log file along the lines of:\n\n  Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!\n\nMore information on how to use the logger is at http://www.ruby-doc.org/core/\n\nAlso, Ruby documentation can be found at http://www.ruby-lang.org/. There are\nseveral books available online as well:\n\n* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)\n* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)\n\nThese two books will bring you up to speed on the Ruby language and also on\nprogramming in general.\n\n\n== Debugger\n\nDebugger support is available through the debugger command when you start your\nMongrel or WEBrick server with --debugger. This means that you can break out of\nexecution at any point in the code, investigate and change the model, and then,\nresume execution! You need to install ruby-debug to run the server in debugging\nmode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:\n\n  class WeblogController < ActionController::Base\n    def index\n      @posts = Post.all\n      debugger\n    end\n  end\n\nSo the controller will accept the action, run the first line, then present you\nwith a IRB prompt in the server window. Here you can do things like:\n\n  >> @posts.inspect\n  => \"[#<Post:0x14a6be8\n          @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,\n       #<Post:0x14a6620\n          @attributes={\"title\"=>\"Rails\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]\"\n  >> @posts.first.title = \"hello from a debugger\"\n  => \"hello from a debugger\"\n\n...and even better, you can examine how your runtime objects actually work:\n\n  >> f = @posts.first\n  => #<Post:0x13630c4 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>\n  >> f.\n  Display all 152 possibilities? (y or n)\n\nFinally, when you're ready to resume execution, you can enter \"cont\".\n\n\n== Console\n\nThe console is a Ruby shell, which allows you to interact with your\napplication's domain model. Here you'll have all parts of the application\nconfigured, just like it is when the application is running. You can inspect\ndomain models, change values, and save to the database. Starting the script\nwithout arguments will launch it in the development environment.\n\nTo start the console, run <tt>rails console</tt> from the application\ndirectory.\n\nOptions:\n\n* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications\n  made to the database.\n* Passing an environment name as an argument will load the corresponding\n  environment. Example: <tt>rails console production</tt>.\n\nTo reload your controllers and models after launching the console run\n<tt>reload!</tt>\n\nMore information about irb can be found at:\nlink:http://www.rubycentral.org/pickaxe/irb.html\n\n\n== dbconsole\n\nYou can go to the command line of your database directly through <tt>rails\ndbconsole</tt>. You would be connected to the database with the credentials\ndefined in database.yml. Starting the script without arguments will connect you\nto the development database. Passing an argument will connect you to a different\ndatabase, like <tt>rails dbconsole production</tt>. Currently works for MySQL,\nPostgreSQL and SQLite 3.\n\n== Description of Contents\n\nThe default directory structure of a generated Ruby on Rails application:\n\n  |-- app\n  |   |-- assets\n  |   |   |-- images\n  |   |   |-- javascripts\n  |   |   `-- stylesheets\n  |   |-- controllers\n  |   |-- helpers\n  |   |-- mailers\n  |   |-- models\n  |   `-- views\n  |       `-- layouts\n  |-- config\n  |   |-- environments\n  |   |-- initializers\n  |   `-- locales\n  |-- db\n  |-- doc\n  |-- lib\n  |   |-- assets\n  |   `-- tasks\n  |-- log\n  |-- public\n  |-- script\n  |-- test\n  |   |-- fixtures\n  |   |-- functional\n  |   |-- integration\n  |   |-- performance\n  |   `-- unit\n  |-- tmp\n  |   `-- cache\n  |       `-- assets\n  `-- vendor\n      |-- assets\n      |   |-- javascripts\n      |   `-- stylesheets\n      `-- plugins\n\napp\n  Holds all the code that's specific to this particular application.\n\napp/assets\n  Contains subdirectories for images, stylesheets, and JavaScript files.\n\napp/controllers\n  Holds controllers that should be named like weblogs_controller.rb for\n  automated URL mapping. All controllers should descend from\n  ApplicationController which itself descends from ActionController::Base.\n\napp/models\n  Holds models that should be named like post.rb. Models descend from\n  ActiveRecord::Base by default.\n\napp/views\n  Holds the template files for the view that should be named like\n  weblogs/index.html.erb for the WeblogsController#index action. All views use\n  eRuby syntax by default.\n\napp/views/layouts\n  Holds the template files for layouts to be used with views. This models the\n  common header/footer method of wrapping views. In your views, define a layout\n  using the <tt>layout :default</tt> and create a file named default.html.erb.\n  Inside default.html.erb, call <% yield %> to render the view using this\n  layout.\n\napp/helpers\n  Holds view helpers that should be named like weblogs_helper.rb. These are\n  generated for you automatically when using generators for controllers.\n  Helpers can be used to wrap functionality for your views into methods.\n\nconfig\n  Configuration files for the Rails environment, the routing map, the database,\n  and other dependencies.\n\ndb\n  Contains the database schema in schema.rb. db/migrate contains all the\n  sequence of Migrations for your schema.\n\ndoc\n  This directory is where your application documentation will be stored when\n  generated using <tt>rake doc:app</tt>\n\nlib\n  Application specific libraries. Basically, any kind of custom code that\n  doesn't belong under controllers, models, or helpers. This directory is in\n  the load path.\n\npublic\n  The directory available for the web server. Also contains the dispatchers and the\n  default HTML files. This should be set as the DOCUMENT_ROOT of your web\n  server.\n\nscript\n  Helper scripts for automation and generation.\n\ntest\n  Unit and functional tests along with fixtures. When using the rails generate\n  command, template test files will be generated for you and placed in this\n  directory.\n\nvendor\n  External libraries that the application depends on. Also includes the plugins\n  subdirectory. If the app has frozen rails, those gems also go here, under\n  vendor/rails/. This directory is in the load path.\n"
  },
  {
    "path": "examples/rails_openid/Rakefile",
    "content": "#!/usr/bin/env rake\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire File.expand_path('../config/application', __FILE__)\n\nRailsOpenid::Application.load_tasks\n"
  },
  {
    "path": "examples/rails_openid/app/assets/javascripts/application.js",
    "content": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//\n// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,\n// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.\n//\n// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the\n// the compiled file.\n//\n// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD\n// GO AFTER THE REQUIRES BELOW.\n//\n//= require jquery\n//= require jquery_ujs\n//= require_tree .\n"
  },
  {
    "path": "examples/rails_openid/app/assets/stylesheets/application.css",
    "content": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,\n * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the top of the\n * compiled file, but it's generally better to create a new file per style scope.\n *\n *= require_self\n *= require_tree .\n */\n"
  },
  {
    "path": "examples/rails_openid/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  protect_from_forgery\nend\n"
  },
  {
    "path": "examples/rails_openid/app/controllers/consumer_controller.rb",
    "content": "require 'pathname'\n\nrequire \"openid\"\nrequire 'openid/extensions/sreg'\nrequire 'openid/extensions/pape'\nrequire 'openid/store/filesystem'\n\nclass ConsumerController < ApplicationController\n  layout nil\n\n  def index\n    # render an openid form\n  end\n\n  def start\n    begin\n      identifier = params[:openid_identifier]\n      if identifier.nil?\n        flash[:error] = \"Enter an OpenID identifier\"\n        redirect_to :action => 'index'\n        return\n      end\n      oidreq = consumer.begin(identifier)\n    rescue OpenID::OpenIDError => e\n      flash[:error] = \"Discovery failed for #{identifier}: #{e}\"\n      redirect_to :action => 'index'\n      return\n    end\n    if params[:use_sreg]\n      sregreq = OpenID::SReg::Request.new\n      # required fields\n      sregreq.request_fields(['email','nickname'], true)\n      # optional fields\n      sregreq.request_fields(['dob', 'fullname'], false)\n      oidreq.add_extension(sregreq)\n      oidreq.return_to_args['did_sreg'] = 'y'\n    end\n    if params[:use_pape]\n      papereq = OpenID::PAPE::Request.new\n      papereq.add_policy_uri(OpenID::PAPE::AUTH_PHISHING_RESISTANT)\n      papereq.max_auth_age = 2*60*60\n      oidreq.add_extension(papereq)\n      oidreq.return_to_args['did_pape'] = 'y'\n    end\n    if params[:force_post]\n      oidreq.return_to_args['force_post']='x'*2048\n    end\n    return_to = url_for :action => 'complete', :only_path => false\n    realm = url_for :action => 'index', :id => nil, :only_path => false\n    \n    if oidreq.send_redirect?(realm, return_to, params[:immediate])\n      redirect_to oidreq.redirect_url(realm, return_to, params[:immediate])\n    else\n      render :text => oidreq.html_markup(realm, return_to, params[:immediate], {'id' => 'openid_form'})\n    end\n  end\n\n  def complete\n    # FIXME - url_for some action is not necessarily the current URL.\n    current_url = url_for(:action => 'complete', :only_path => false)\n    parameters = params.reject{|k,v|request.path_parameters[k]}\n    parameters.reject!{|k,v|%w{action controller}.include? k.to_s}\n    oidresp = consumer.complete(parameters, current_url)\n    case oidresp.status\n    when OpenID::Consumer::FAILURE\n      if oidresp.display_identifier\n        flash[:error] = (\"Verification of #{oidresp.display_identifier}\"\\\n                         \" failed: #{oidresp.message}\")\n      else\n        flash[:error] = \"Verification failed: #{oidresp.message}\"\n      end\n    when OpenID::Consumer::SUCCESS\n      flash[:success] = (\"Verification of #{oidresp.display_identifier}\"\\\n                         \" succeeded.\")\n      if params[:did_sreg]\n        sreg_resp = OpenID::SReg::Response.from_success_response(oidresp)\n        sreg_message = \"Simple Registration data was requested\"\n        if sreg_resp.empty?\n          sreg_message << \", but none was returned.\"\n        else\n          sreg_message << \". The following data were sent:\"\n          sreg_resp.data.each {|k,v|\n            sreg_message << \"<br/><b>#{k}</b>: #{v}\"\n          }\n        end\n        flash[:sreg_results] = sreg_message\n      end\n      if params[:did_pape]\n        pape_resp = OpenID::PAPE::Response.from_success_response(oidresp)\n        pape_message = \"A phishing resistant authentication method was requested\"\n        if pape_resp.auth_policies.member? OpenID::PAPE::AUTH_PHISHING_RESISTANT\n          pape_message << \", and the server reported one.\"\n        else\n          pape_message << \", but the server did not report one.\"\n        end\n        if pape_resp.auth_time\n          pape_message << \"<br><b>Authentication time:</b> #{pape_resp.auth_time} seconds\"\n        end\n        if pape_resp.nist_auth_level\n          pape_message << \"<br><b>NIST Auth Level:</b> #{pape_resp.nist_auth_level}\"\n        end\n        flash[:pape_results] = pape_message\n      end\n    when OpenID::Consumer::SETUP_NEEDED\n      flash[:alert] = \"Immediate request failed - Setup Needed\"\n    when OpenID::Consumer::CANCEL\n      flash[:alert] = \"OpenID transaction cancelled.\"\n    else\n    end\n    redirect_to :action => 'index'\n  end\n\n  private\n\n  def consumer\n    if @consumer.nil?\n      dir = Pathname.new(RAILS_ROOT).join('db').join('cstore')\n      store = OpenID::Store::Filesystem.new(dir)\n      @consumer = OpenID::Consumer.new(session, store)\n    end\n    return @consumer\n  end\nend\n"
  },
  {
    "path": "examples/rails_openid/app/controllers/login_controller.rb",
    "content": "# Controller for handling the login, logout process for \"users\" of our\n# little server.  Users have no password.  This is just an example.\n\nrequire 'openid'\n\nclass LoginController < ApplicationController\n\n  layout 'server'\n\n  def base_url\n    url_for(:controller => 'login', :action => nil, :only_path => false)\n  end\n\n  def index\n    response.headers['X-XRDS-Location'] = url_for(:controller => \"server\",\n                                                  :action => \"idp_xrds\",\n                                                  :only_path => false)\n    @base_url = base_url\n    # just show the login page\n  end\n\n  def submit\n    user = params[:username]\n\n    # if we get a user, log them in by putting their username in\n    # the session hash.\n    unless user.nil?\n      session[:username] = user unless user.nil?\n      session[:approvals] = []\n      flash[:notice] = \"Your OpenID URL is <b>#{base_url}user/#{user}</b><br/><br/>Proceed to step 2 below.\"\n    else\n      flash[:error] = \"Sorry, couldn't log you in. Try again.\"\n    end\n    \n    redirect_to :action => 'index'\n  end\n\n  def logout\n    # delete the username from the session hash\n    session[:username] = nil\n    session[:approvals] = nil\n    redirect_to :action => 'index'\n  end\n\nend\n"
  },
  {
    "path": "examples/rails_openid/app/controllers/server_controller.rb",
    "content": "require 'pathname'\n\nrequire \"openid\"\nrequire \"openid/consumer/discovery\"\nrequire 'openid/extensions/sreg'\nrequire 'openid/extensions/pape'\nrequire 'openid/store/filesystem'\n\nclass ServerController < ApplicationController\n\n  include ServerHelper\n  include OpenID::Server\n  layout nil\n\n  def index\n    begin\n      oidreq = server.decode_request(params)\n    rescue ProtocolError => e\n      # invalid openid request, so just display a page with an error message\n      render :text => e.to_s, :status => 500\n      return\n    end\n\n    # no openid.mode was given\n    unless oidreq\n      render :text => \"This is an OpenID server endpoint.\"\n      return\n    end\n\n    oidresp = nil\n\n    if oidreq.kind_of?(CheckIDRequest)\n\n      identity = oidreq.identity\n\n      if oidreq.id_select\n        if oidreq.immediate\n          oidresp = oidreq.answer(false)\n        elsif session[:username].nil?\n          # The user hasn't logged in.\n          show_decision_page(oidreq)\n          return\n        else\n          # Else, set the identity to the one the user is using.\n          identity = url_for_user\n        end\n      end\n\n      if oidresp\n        nil\n      elsif self.is_authorized(identity, oidreq.trust_root)\n        oidresp = oidreq.answer(true, nil, identity)\n\n        # add the sreg response if requested\n        add_sreg(oidreq, oidresp)\n        # ditto pape\n        add_pape(oidreq, oidresp)\n\n      elsif oidreq.immediate\n        server_url = url_for :action => 'index'\n        oidresp = oidreq.answer(false, server_url)\n\n      else\n        show_decision_page(oidreq)\n        return\n      end\n\n    else\n      oidresp = server.handle_request(oidreq)\n    end\n\n    self.render_response(oidresp)\n  end\n\n  def show_decision_page(oidreq, message=\"Do you trust this site with your identity?\")\n    session[:last_oidreq] = oidreq\n    @oidreq = oidreq\n\n    if message\n      flash[:notice] = message\n    end\n\n    render :template => 'server/decide', :layout => 'server'\n  end\n\n  def user_page\n    # Yadis content-negotiation: we want to return the xrds if asked for.\n    accept = request.env['HTTP_ACCEPT']\n\n    # This is not technically correct, and should eventually be updated\n    # to do real Accept header parsing and logic.  Though I expect it will work\n    # 99% of the time.\n    if accept and accept.include?('application/xrds+xml')\n      user_xrds\n      return\n    end\n\n    # content negotiation failed, so just render the user page\n    xrds_url = url_for(:controller=>'user',:action=>params[:username])+'/xrds'\n    identity_page = <<EOS\n<html><head>\n<meta http-equiv=\"X-XRDS-Location\" content=\"#{xrds_url}\" />\n<link rel=\"openid.server\" href=\"#{url_for :action => 'index'}\" />\n</head><body><p>OpenID identity page for #{params[:username]}</p>\n</body></html>\nEOS\n\n    # Also add the Yadis location header, so that they don't have\n    # to parse the html unless absolutely necessary.\n    response.headers['X-XRDS-Location'] = xrds_url\n    render :text => identity_page\n  end\n\n  def user_xrds\n    types = [\n             OpenID::OPENID_2_0_TYPE,\n             OpenID::OPENID_1_0_TYPE,\n             OpenID::SREG_URI,\n            ]\n\n    render_xrds(types)\n  end\n\n  def idp_xrds\n    types = [\n             OpenID::OPENID_IDP_2_0_TYPE,\n            ]\n\n    render_xrds(types)\n  end\n\n  def decision\n    oidreq = session[:last_oidreq]\n    session[:last_oidreq] = nil\n\n    if params[:yes].nil?\n      redirect_to oidreq.cancel_url\n      return\n    else\n      id_to_send = params[:id_to_send]\n\n      identity = oidreq.identity\n      if oidreq.id_select\n        if id_to_send and id_to_send != \"\"\n          session[:username] = id_to_send\n          session[:approvals] = []\n          identity = url_for_user\n        else\n          msg = \"You must enter a username to in order to send \" +\n            \"an identifier to the Relying Party.\"\n          show_decision_page(oidreq, msg)\n          return\n        end\n      end\n\n      if session[:approvals]\n        session[:approvals] << oidreq.trust_root\n      else\n        session[:approvals] = [oidreq.trust_root]\n      end\n      oidresp = oidreq.answer(true, nil, identity)\n      add_sreg(oidreq, oidresp)\n      add_pape(oidreq, oidresp)\n      return self.render_response(oidresp)\n    end\n  end\n\n  protected\n\n  def server\n    if @server.nil?\n      server_url = url_for :action => 'index', :only_path => false\n      dir = Pathname.new(RAILS_ROOT).join('db').join('openid-store')\n      store = OpenID::Store::Filesystem.new(dir)\n      @server = Server.new(store, server_url)\n    end\n    return @server\n  end\n\n  def approved(trust_root)\n    return false if session[:approvals].nil?\n    return session[:approvals].member?(trust_root)\n  end\n\n  def is_authorized(identity_url, trust_root)\n    return (session[:username] and (identity_url == url_for_user) and self.approved(trust_root))\n  end\n\n  def render_xrds(types)\n    type_str = \"\"\n\n    types.each { |uri|\n      type_str += \"<Type>#{uri}</Type>\\n      \"\n    }\n\n    yadis = <<EOS\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS\n    xmlns:xrds=\"xri://$xrds\"\n    xmlns=\"xri://$xrd*($v*2.0)\">\n  <XRD>\n    <Service priority=\"0\">\n      #{type_str}\n      <URI>#{url_for(:controller => 'server', :only_path => false)}</URI>\n    </Service>\n  </XRD>\n</xrds:XRDS>\nEOS\n\n    render :text => yadis, :content_type => 'application/xrds+xml'\n  end\n\n  def add_sreg(oidreq, oidresp)\n    # check for Simple Registration arguments and respond\n    sregreq = OpenID::SReg::Request.from_openid_request(oidreq)\n\n    return if sregreq.nil?\n    # In a real application, this data would be user-specific,\n    # and the user should be asked for permission to release\n    # it.\n    sreg_data = {\n      'nickname' => session[:username],\n      'fullname' => 'Mayor McCheese',\n      'email' => 'mayor@example.com'\n    }\n\n    sregresp = OpenID::SReg::Response.extract_response(sregreq, sreg_data)\n    oidresp.add_extension(sregresp)\n  end\n\n  def add_pape(oidreq, oidresp)\n    papereq = OpenID::PAPE::Request.from_openid_request(oidreq)\n    return if papereq.nil?\n    paperesp = OpenID::PAPE::Response.new\n    paperesp.nist_auth_level = 0 # we don't even do auth at all!\n    oidresp.add_extension(paperesp)\n  end\n\n  def render_response(oidresp)\n    if oidresp.needs_signing\n      signed_response = server.signatory.sign(oidresp)\n    end\n    web_response = server.encode_response(oidresp)\n\n    case web_response.code\n    when HTTP_OK\n      render :text => web_response.body, :status => 200\n\n    when HTTP_REDIRECT\n      redirect_to web_response.headers['location']\n\n    else\n      render :text => web_response.body, :status => 400\n    end\n  end\n\n\nend\n"
  },
  {
    "path": "examples/rails_openid/app/helpers/application_helper.rb",
    "content": "module ApplicationHelper\nend\n"
  },
  {
    "path": "examples/rails_openid/app/helpers/login_helper.rb",
    "content": "module LoginHelper\nend\n"
  },
  {
    "path": "examples/rails_openid/app/helpers/server_helper.rb",
    "content": "\nmodule ServerHelper\n\n  def url_for_user\n    url_for :controller => 'user', :action => session[:username]\n  end\n\nend\n\n"
  },
  {
    "path": "examples/rails_openid/app/mailers/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/app/models/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/app/views/consumer/index.html.erb",
    "content": "<html>\n<head>\n<title>Rails OpenID Example Relying Party</title>\n</head>\n  <style type=\"text/css\">\n      * {\n        font-family: verdana,sans-serif;\n      }\n      body {\n        width: 50em;\n        margin: 1em;\n      }\n      div {\n        padding: .5em;\n      }\n      .alert {\n        border: 1px solid #e7dc2b;\n        background: #fff888;\n      }\n      .error {\n        border: 1px solid #ff0000;\n        background: #ffaaaa;\n      }\n      .success {\n        border: 1px solid #00ff00;\n        background: #aaffaa;\n      }\n      #verify-form {\n        border: 1px solid #777777;\n        background: #dddddd;\n        margin-top: 1em;\n        padding-bottom: 0em;\n      }\n      input.openid {\n        background: url( /images/openid_login_bg.gif ) no-repeat;\n        background-position: 0 50%;\n        background-color: #fff;\n        padding-left: 18px;\n      }\n  </style>\n  <body>\n    <h1>Rails OpenID Example Relying Party</h1>\n    <% if flash[:alert] %>\n      <div class='alert'>\n       <%= h(flash[:alert]) %>\n      </div>\n    <% end %>\n    <% if flash[:error] %>\n      <div class='error'>\n       <%= h(flash[:error]) %>\n      </div>\n    <% end %>\n    <% if flash[:success] %>\n      <div class='success'>\n       <%= h(flash[:success]) %>\n      </div>\n    <% end %>\n    <% if flash[:sreg_results] %>\n      <div class='alert'>\n      <%= flash[:sreg_results] %>\n      </div>\n    <% end %>\n    <% if flash[:pape_results] %>\n      <div class='alert'>\n      <%= flash[:pape_results] %>\n      </div>\n    <% end %>\n    <div id=\"verify-form\">\n      <form method=\"get\" accept-charset=\"UTF-8\" \n            action='<%= url_for :action => 'start' %>'>\n        Identifier:\n        <input type=\"text\" class=\"openid\" name=\"openid_identifier\" />\n        <input type=\"submit\" value=\"Verify\" /><br />\n        <input type=\"checkbox\" name=\"immediate\" id=\"immediate\" /><label for=\"immediate\">Use immediate mode</label><br/>\n        <input type=\"checkbox\" name=\"use_sreg\" id=\"use_sreg\" /><label for=\"use_sreg\">Request registration data</label><br/>\n        <input type=\"checkbox\" name=\"use_pape\" id=\"use_pape\" /><label for=\"use_pape\">Request phishing-resistent auth policy (PAPE)</label><br/>\n        <input type=\"checkbox\" name=\"force_post\" id=\"force_post\" /><label for=\"force_post\">Force the transaction to use POST by adding 2K of extra data</label>\n      </form>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/rails_openid/app/views/layouts/server.html.erb",
    "content": "<html>\n  <head><title>OpenID Server Example</title>\n  <%#= csrf_meta_tags %>\n  </head>\n  <style type=\"text/css\">\n      * {\n        font-family: verdana,sans-serif;\n      }\n      body {\n        width: 50em;\n        margin: 1em;\n      }\n      div {\n        padding: .5em;\n      }\n      table {\n        margin: none;\n        padding: none;\n      }\n      .notice {\n        border: 1px solid #60964f;\n        background: #b3dca7;\n      }\n      .error {\n        border: 1px solid #ff0000;\n        background: #ffaaaa;\n      }\n      #login-form {\n        border: 1px solid #777777;\n        background: #dddddd;\n        margin-top: 1em;\n        padding-bottom: 0em;\n      }\n      table {\n        padding: 1em;\n      }\n      li {margin-bottom: .5em;}\n      span.openid:before {\n        content: url(<%= @base_url %>images/openid_login_bg.gif) ;\n      }\n      span.openid {\n        font-size: smaller;\n      }\n  </style>\n  <body>\n\n\n\n    <% if session[:username] %>\n      <div style=\"float:right;\">\n        Welcome, <%= session[:username] %> | <%= link_to('Log out', :controller => 'login', :action => 'logout') %><br />\n\t<span class=\"openid\"><%= @base_url %>user/<%= session[:username] %></span>\n      </div>\n    <% end %>\n\n    <h3>Ruby OpenID Server Example</h3>\n\n    <hr/>\n\n    <% if flash[:notice] or flash[:error] %>\n     <div class=\"<%= flash[:notice].nil? ? 'error' : 'notice' %>\">\n       <%= flash[:error] or flash[:notice] %>\n     </div>\n    <% end %>\n\n    <%= yield %>\n\n\n  </body>\n</html>\n"
  },
  {
    "path": "examples/rails_openid/app/views/login/index.html.erb",
    "content": "\n\n<% if session[:username].nil? %>\n\n<div id=\"login-form\">\n  <form method=\"get\" action=\"<%= url_for :controller => 'login', :action => 'submit' %>\">\nType a username: \n  <input type=\"text\" name=\"username\" />\n  <input type=\"submit\" value=\"Log In\" />\n  </form>\n\n</div>\n\n<% end %>\n\n<p> Welcome to the Ruby OpenID example.  This code is a starting point\nfor developers wishing to implement an OpenID provider or relying\nparty.  We've used the <a href=\"http://rubyonrails.org/\">Rails</a>\nplatform to demonstrate, but the library code is not Rails specific.</p>\n\n<h2>To use the example provider</h2>\n<p>\n  <ol>\n\n    <li>Enter a username in the form above.  You will be \"Logged In\"\n    to the server, at which point you may authenticate using an OpenID\n    consumer. Your OpenID URL will be displayed after you log\n    in.<p>The server will automatically create an identity page for\n    you at <%= @base_url %>user/<i>name</i></p></li>\n\n    <li><p>Because WEBrick can only handle one thing at a time, you'll need to\n    run another instance of the example on another port if you want to use\n    a relying party to use with this example provider:</p>\n    <blockquote>\n    <code>script/server --port=3001</code>\n    </blockquote>\n\n    <p>(The RP needs to be able to access the provider, so unless you're\n    running this example on a public IP, you can't use the live example\n    at <a href=\"http://openidenabled.com/\">openidenabled.com</a> on\n    your local provider.)</p>\n    </li>\n\n    <li>Point your browser to this new instance and follow the directions\n    below.</li>\n    <!-- Fun fact: 'url_for :port => 3001' doesn't work very well. -->\n  </ol>\n\n</p>\n\n<h2>To use the example relying party</h2>\n\n<p>Visit <a href=\"<%= url_for :controller => 'consumer' %>\">/consumer</a>\nand enter your OpenID.</p>\n</p>\n\n"
  },
  {
    "path": "examples/rails_openid/app/views/server/decide.html.erb",
    "content": "<form method=\"post\" action=\"<%= url_for :controller => 'server', :action => 'decision' %>\">\n  <input type=\"hidden\" name=\"authenticity_token\" value=\"<%= form_authenticity_token %>\">\n\n<table>\n  <tr><td>Site:</td><td><%= @oidreq.trust_root %></td></tr>\n\n  <% if @oidreq.id_select %>\n    <tr>\n      <td colspan=\"2\">\n        You entered the server identifier at the relying party.\n        You'll need to send an identifier of your choosing.  Enter a\n        username below.\n      </td>\n    </tr>\n    <tr>\n      <td>Identity to send:</td>\n      <td><input type=\"text\" name=\"id_to_send\" size=\"25\" /></td>\n    </tr>\n  <% else %>\n    <tr><td>Identity:</td><td><%= @oidreq.identity %></td></tr>\n  <% end %>\n</table>\n\n<input type=\"submit\" name=\"yes\" value=\"yes\" />\n<input type=\"submit\" name=\"no\" value=\"no\" />\n\n</form>\n"
  },
  {
    "path": "examples/rails_openid/config/application.rb",
    "content": "require File.expand_path('../boot', __FILE__)\n\nrequire 'rails/all'\n\nif defined?(Bundler)\n  # If you precompile assets before deploying to production, use this line\n  Bundler.require(*Rails.groups(:assets => %w(development test)))\n  # If you want your assets lazily compiled in production, use this line\n  # Bundler.require(:default, :assets, Rails.env)\nend\n\nmodule RailsOpenid\n  class Application < Rails::Application\n    # Settings in config/environments/* take precedence over those specified here.\n    # Application configuration should go into files in config/initializers\n    # -- all .rb files in that directory are automatically loaded.\n\n    # Custom directories with classes and modules you want to be autoloadable.\n    # config.autoload_paths += %W(#{config.root}/extras)\n\n    # Only load the plugins named here, in the order given (default is alphabetical).\n    # :all can be used as a placeholder for all plugins not explicitly named.\n    # config.plugins = [ :exception_notification, :ssl_requirement, :all ]\n\n    # Activate observers that should always be running.\n    # config.active_record.observers = :cacher, :garbage_collector, :forum_observer\n\n    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.\n    # Run \"rake -D time\" for a list of tasks for finding time zone names. Default is UTC.\n    # config.time_zone = 'Central Time (US & Canada)'\n\n    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.\n    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]\n    # config.i18n.default_locale = :de\n\n    # Configure the default encoding used in templates for Ruby 1.9.\n    config.encoding = \"utf-8\"\n\n    # Configure sensitive parameters which will be filtered from the log file.\n    config.filter_parameters += [:password]\n\n    # Enable escaping HTML in JSON.\n    config.active_support.escape_html_entities_in_json = true\n\n    # Use SQL instead of Active Record's schema dumper when creating the database.\n    # This is necessary if your schema can't be completely dumped by the schema dumper,\n    # like if you have constraints or database-specific column types\n    # config.active_record.schema_format = :sql\n\n    # Enforce whitelist mode for mass assignment.\n    # This will create an empty whitelist of attributes available for mass-assignment for all models\n    # in your app. As such, your models will need to explicitly whitelist or blacklist accessible\n    # parameters by using an attr_accessible or attr_protected declaration.\n    config.active_record.whitelist_attributes = true\n\n    # Enable the asset pipeline\n    config.assets.enabled = true\n\n    # Version of your assets, change this if you want to expire all your assets\n    config.assets.version = '1.0'\n  end\nend\n"
  },
  {
    "path": "examples/rails_openid/config/boot.rb",
    "content": "require 'rubygems'\n\n# Set up gems listed in the Gemfile.\nENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)\n\nrequire 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])\n"
  },
  {
    "path": "examples/rails_openid/config/database.yml",
    "content": "# SQLite version 3.x\n#   gem install sqlite3\n#\n#   Ensure the SQLite 3 gem is defined in your Gemfile\n#   gem 'sqlite3'\ndevelopment:\n  adapter: sqlite3\n  database: db/development.sqlite3\n  pool: 5\n  timeout: 5000\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  adapter: sqlite3\n  database: db/test.sqlite3\n  pool: 5\n  timeout: 5000\n\nproduction:\n  adapter: sqlite3\n  database: db/production.sqlite3\n  pool: 5\n  timeout: 5000\n"
  },
  {
    "path": "examples/rails_openid/config/environment.rb",
    "content": "# Load the rails application\nrequire File.expand_path('../application', __FILE__)\n\n# Initialize the rails application\nRailsOpenid::Application.initialize!\n"
  },
  {
    "path": "examples/rails_openid/config/environments/development.rb",
    "content": "RailsOpenid::Application.configure do\n  # Settings specified here will take precedence over those in config/application.rb\n\n  # In the development environment your application's code is reloaded on\n  # every request. This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.cache_classes = false\n\n  # Log error messages when you accidentally call methods on nil.\n  config.whiny_nils = true\n\n  # Show full error reports and disable caching\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n\n  # Don't care if the mailer can't send\n  config.action_mailer.raise_delivery_errors = false\n\n  # Print deprecation notices to the Rails logger\n  config.active_support.deprecation = :log\n\n  # Only use best-standards-support built into browsers\n  config.action_dispatch.best_standards_support = :builtin\n\n  # Raise exception on mass assignment protection for Active Record models\n  config.active_record.mass_assignment_sanitizer = :strict\n\n  # Log the query plan for queries taking more than this (works\n  # with SQLite, MySQL, and PostgreSQL)\n  config.active_record.auto_explain_threshold_in_seconds = 0.5\n\n  # Do not compress assets\n  config.assets.compress = false\n\n  # Expands the lines which load the assets\n  config.assets.debug = true\nend\n"
  },
  {
    "path": "examples/rails_openid/config/environments/production.rb",
    "content": "RailsOpenid::Application.configure do\n  # Settings specified here will take precedence over those in config/application.rb\n\n  # Code is not reloaded between requests\n  config.cache_classes = true\n\n  # Full error reports are disabled and caching is turned on\n  config.consider_all_requests_local       = false\n  config.action_controller.perform_caching = true\n\n  # Disable Rails's static asset server (Apache or nginx will already do this)\n  config.serve_static_assets = false\n\n  # Compress JavaScripts and CSS\n  config.assets.compress = true\n\n  # Don't fallback to assets pipeline if a precompiled asset is missed\n  config.assets.compile = false\n\n  # Generate digests for assets URLs\n  config.assets.digest = true\n\n  # Defaults to nil and saved in location specified by config.assets.prefix\n  # config.assets.manifest = YOUR_PATH\n\n  # Specifies the header that your server uses for sending files\n  # config.action_dispatch.x_sendfile_header = \"X-Sendfile\" # for apache\n  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  # config.force_ssl = true\n\n  # See everything in the log (default is :info)\n  # config.log_level = :debug\n\n  # Prepend all log lines with the following tags\n  # config.log_tags = [ :subdomain, :uuid ]\n\n  # Use a different logger for distributed setups\n  # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)\n\n  # Use a different cache store in production\n  # config.cache_store = :mem_cache_store\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server\n  # config.action_controller.asset_host = \"http://assets.example.com\"\n\n  # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)\n  # config.assets.precompile += %w( search.js )\n\n  # Disable delivery errors, bad email addresses will be ignored\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable threaded mode\n  # config.threadsafe!\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation can not be found)\n  config.i18n.fallbacks = true\n\n  # Send deprecation notices to registered listeners\n  config.active_support.deprecation = :notify\n\n  # Log the query plan for queries taking more than this (works\n  # with SQLite, MySQL, and PostgreSQL)\n  # config.active_record.auto_explain_threshold_in_seconds = 0.5\nend\n"
  },
  {
    "path": "examples/rails_openid/config/environments/test.rb",
    "content": "RailsOpenid::Application.configure do\n  # Settings specified here will take precedence over those in config/application.rb\n\n  # The test environment is used exclusively to run your application's\n  # test suite. You never need to work with it otherwise. Remember that\n  # your test database is \"scratch space\" for the test suite and is wiped\n  # and recreated between test runs. Don't rely on the data there!\n  config.cache_classes = true\n\n  # Configure static asset server for tests with Cache-Control for performance\n  config.serve_static_assets = true\n  config.static_cache_control = \"public, max-age=3600\"\n\n  # Log error messages when you accidentally call methods on nil\n  config.whiny_nils = true\n\n  # Show full error reports and disable caching\n  config.consider_all_requests_local       = true\n  config.action_controller.perform_caching = false\n\n  # Raise exceptions instead of rendering exception templates\n  config.action_dispatch.show_exceptions = false\n\n  # Disable request forgery protection in test environment\n  config.action_controller.allow_forgery_protection    = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Raise exception on mass assignment protection for Active Record models\n  config.active_record.mass_assignment_sanitizer = :strict\n\n  # Print deprecation notices to the stderr\n  config.active_support.deprecation = :stderr\nend\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/backtrace_silencers.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.\n# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }\n\n# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.\n# Rails.backtrace_cleaner.remove_silencers!\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format\n# (all these examples are active by default):\n# ActiveSupport::Inflector.inflections do |inflect|\n#   inflect.plural /^(ox)$/i, '\\1en'\n#   inflect.singular /^(ox)en/i, '\\1'\n#   inflect.irregular 'person', 'people'\n#   inflect.uncountable %w( fish sheep )\n# end\n#\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections do |inflect|\n#   inflect.acronym 'RESTful'\n# end\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/mime_types.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::Type.register \"text/richtext\", :rtf\n# Mime::Type.register_alias \"text/html\", :iphone\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/rails_root.rb",
    "content": "::RAILS_ROOT = Rails.root\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/secret_token.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Your secret key for verifying the integrity of signed cookies.\n# If you change this key, all old signed cookies will become invalid!\n# Make sure the secret is at least 30 characters and all random,\n# no regular words or you'll be exposed to dictionary attacks.\nRailsOpenid::Application.config.secret_token = '2314c4d00e3702d446505b8df2732c433379a0d61ac94c32a25f71612ab6df457bc9979eb32cae28ad6feacdd5a9ae7ac330934c5fb53877e02ce8e23ac0f494'\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/session_store.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\nRailsOpenid::Application.config.session_store :cookie_store, :key => '_rails_openid_session'\n\n# Use the database for sessions instead of the cookie-based default,\n# which shouldn't be used to store highly confidential information\n# (create the session table with \"rails generate session_migration\")\n# RailsOpenid::Application.config.session_store :active_record_store\n"
  },
  {
    "path": "examples/rails_openid/config/initializers/wrap_parameters.rb",
    "content": "# Be sure to restart your server when you modify this file.\n#\n# This file contains settings for ActionController::ParamsWrapper which\n# is enabled by default.\n\n# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.\nActiveSupport.on_load(:action_controller) do\n  wrap_parameters :format => [:json]\nend\n\n# Disable root element in JSON by default.\nActiveSupport.on_load(:active_record) do\n  self.include_root_in_json = false\nend\n"
  },
  {
    "path": "examples/rails_openid/config/locales/en.yml",
    "content": "# Sample localization file for English. Add more files in this directory for other locales.\n# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "examples/rails_openid/config/routes.rb",
    "content": "RailsOpenid::Application.routes.draw do\n  root :controller => 'login', :action => :index\n  match 'server/xrds', :controller => 'server', :action => 'idp_xrds'\n  match 'user/:username', :controller => 'server', :action => 'user_page'\n  match 'user/:username/xrds', :controller => 'server', :action => 'user_xrds'\n\n  # Allow downloading Web Service WSDL as a file with an extension\n  # instead of a file named 'wsdl'\n  match ':controller/service.wsdl', :action => 'wsdl'\n\n  # Install the default route as the lowest priority.\n  match ':controller/:action/:id'\n\n\n  # The priority is based upon order of creation:\n  # first created -> highest priority.\n\n  # Sample of regular route:\n  #   match 'products/:id' => 'catalog#view'\n  # Keep in mind you can assign values other than :controller and :action\n\n  # Sample of named route:\n  #   match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase\n  # This route can be invoked with purchase_url(:id => product.id)\n\n  # Sample resource route (maps HTTP verbs to controller actions automatically):\n  #   resources :products\n\n  # Sample resource route with options:\n  #   resources :products do\n  #     member do\n  #       get 'short'\n  #       post 'toggle'\n  #     end\n  #\n  #     collection do\n  #       get 'sold'\n  #     end\n  #   end\n\n  # Sample resource route with sub-resources:\n  #   resources :products do\n  #     resources :comments, :sales\n  #     resource :seller\n  #   end\n\n  # Sample resource route with more complex sub-resources\n  #   resources :products do\n  #     resources :comments\n  #     resources :sales do\n  #       get 'recent', :on => :collection\n  #     end\n  #   end\n\n  # Sample resource route within a namespace:\n  #   namespace :admin do\n  #     # Directs /admin/products/* to Admin::ProductsController\n  #     # (app/controllers/admin/products_controller.rb)\n  #     resources :products\n  #   end\n\n  # You can have the root of your site routed with \"root\"\n  # just remember to delete public/index.html.\n  # root :to => 'welcome#index'\n\n  # See how all your routes lay out with \"rake routes\"\n\n  # This is a legacy wild controller route that's not recommended for RESTful applications.\n  # Note: This route will make all actions in every controller accessible via GET requests.\n  match ':controller(/:action(/:id))(.:format)'\nend\n"
  },
  {
    "path": "examples/rails_openid/config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire ::File.expand_path('../config/environment',  __FILE__)\nrun RailsOpenid::Application\n"
  },
  {
    "path": "examples/rails_openid/db/development.sqlite3",
    "content": ""
  },
  {
    "path": "examples/rails_openid/db/seeds.rb",
    "content": "# This file should contain all the record creation needed to seed the database with its default values.\n# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).\n#\n# Examples:\n#\n#   cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])\n#   Mayor.create(:name => 'Emanuel', :city => cities.first)\n"
  },
  {
    "path": "examples/rails_openid/lib/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/lib/tasks/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/log/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/log/development.log",
    "content": "Connecting to database specified by database.yml\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:22:11 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/consumer\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (3.3ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:23:00 -0700 2013\n\nLoadError (no such file to load -- openid):\n  app/controllers/consumer_controller.rb:3\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.8ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (13.8ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (31.1ms)\nConnecting to database specified by database.yml\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:23:40 -0700 2013\nProcessing by ConsumerController#index as HTML\nCompleted 500 Internal Server Error in 24ms\n\nActionView::MissingTemplate (Missing template consumer/index, application/index with {:handlers=>[:coffee, :erb, :builder], :locale=>[:en], :formats=>[:html]}. Searched in:\n  * \"/Users/marcel/.gem/ruby/1.8/gems/ruby-openid-2.2.3/examples/rails_openid/app/views\"\n):\n  actionpack (3.2.13) lib/action_view/path_set.rb:58:in `find'\n  actionpack (3.2.13) lib/action_view/lookup_context.rb:109:in `find_template'\n  actionpack (3.2.13) lib/action_view/renderer/abstract_renderer.rb:3:in `__send__'\n  actionpack (3.2.13) lib/action_view/renderer/abstract_renderer.rb:3:in `find_template'\n  actionpack (3.2.13) lib/action_view/renderer/template_renderer.rb:34:in `determine_template'\n  actionpack (3.2.13) lib/action_view/renderer/template_renderer.rb:10:in `render'\n  actionpack (3.2.13) lib/action_view/renderer/renderer.rb:36:in `render_template'\n  actionpack (3.2.13) lib/action_view/renderer/renderer.rb:17:in `render'\n  actionpack (3.2.13) lib/abstract_controller/rendering.rb:110:in `_render_template'\n  actionpack (3.2.13) lib/action_controller/metal/streaming.rb:225:in `_render_template'\n  actionpack (3.2.13) lib/abstract_controller/rendering.rb:103:in `render_to_body'\n  actionpack (3.2.13) lib/action_controller/metal/renderers.rb:28:in `render_to_body'\n  actionpack (3.2.13) lib/action_controller/metal/compatibility.rb:50:in `render_to_body'\n  actionpack (3.2.13) lib/abstract_controller/rendering.rb:88:in `render'\n  actionpack (3.2.13) lib/action_controller/metal/rendering.rb:16:in `render'\n  actionpack (3.2.13) lib/action_controller/metal/instrumentation.rb:40:in `render'\n  activesupport (3.2.13) lib/active_support/core_ext/benchmark.rb:5:in `ms'\n  /opt/local/lib/ruby/1.8/benchmark.rb:308:in `realtime'\n  activesupport (3.2.13) lib/active_support/core_ext/benchmark.rb:5:in `ms'\n  actionpack (3.2.13) lib/action_controller/metal/instrumentation.rb:40:in `render'\n  actionpack (3.2.13) lib/action_controller/metal/instrumentation.rb:83:in `cleanup_view_runtime'\n  activerecord (3.2.13) lib/active_record/railties/controller_runtime.rb:24:in `cleanup_view_runtime'\n  actionpack (3.2.13) lib/action_controller/metal/instrumentation.rb:39:in `render'\n  actionpack (3.2.13) lib/action_controller/metal/implicit_render.rb:10:in `default_render'\n  actionpack (3.2.13) lib/action_controller/metal/implicit_render.rb:5:in `send_action'\n  actionpack (3.2.13) lib/abstract_controller/base.rb:167:in `process_action'\n  actionpack (3.2.13) lib/action_controller/metal/rendering.rb:10:in `process_action'\n  actionpack (3.2.13) lib/abstract_controller/callbacks.rb:18:in `process_action'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:414:in `_run__1789127949__process_action__199225275__callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_process_action_callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'\n  actionpack (3.2.13) lib/abstract_controller/callbacks.rb:17:in `process_action'\n  actionpack (3.2.13) lib/action_controller/metal/rescue.rb:29:in `process_action'\n  actionpack (3.2.13) lib/action_controller/metal/instrumentation.rb:30:in `process_action'\n  activesupport (3.2.13) lib/active_support/notifications.rb:123:in `instrument'\n  activesupport (3.2.13) lib/active_support/notifications/instrumenter.rb:20:in `instrument'\n  activesupport (3.2.13) lib/active_support/notifications.rb:123:in `instrument'\n  actionpack (3.2.13) lib/action_controller/metal/instrumentation.rb:29:in `process_action'\n  actionpack (3.2.13) lib/action_controller/metal/params_wrapper.rb:207:in `process_action'\n  activerecord (3.2.13) lib/active_record/railties/controller_runtime.rb:18:in `process_action'\n  actionpack (3.2.13) lib/abstract_controller/base.rb:121:in `process'\n  actionpack (3.2.13) lib/abstract_controller/rendering.rb:45:in `process'\n  actionpack (3.2.13) lib/action_controller/metal.rb:203:in `dispatch'\n  actionpack (3.2.13) lib/action_controller/metal/rack_delegation.rb:14:in `dispatch'\n  actionpack (3.2.13) lib/action_controller/metal.rb:246:in `action'\n  actionpack (3.2.13) lib/action_dispatch/routing/route_set.rb:73:in `call'\n  actionpack (3.2.13) lib/action_dispatch/routing/route_set.rb:73:in `dispatch'\n  actionpack (3.2.13) lib/action_dispatch/routing/route_set.rb:36:in `call'\n  journey (1.0.4) lib/journey/router.rb:68:in `call'\n  journey (1.0.4) lib/journey/router.rb:56:in `each'\n  journey (1.0.4) lib/journey/router.rb:56:in `call'\n  actionpack (3.2.13) lib/action_dispatch/routing/route_set.rb:612:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/best_standards_support.rb:17:in `call'\n  rack (1.4.5) lib/rack/etag.rb:23:in `call'\n  rack (1.4.5) lib/rack/conditionalget.rb:25:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/head.rb:14:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/params_parser.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/flash.rb:242:in `call'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/cookies.rb:341:in `call'\n  activerecord (3.2.13) lib/active_record/query_cache.rb:64:in `call'\n  activerecord (3.2.13) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:28:in `call'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `_run__397314981__call__4__callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:27:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/reloader.rb:65:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/missing_template.erb within rescues/layout (1.9ms)\nConnecting to database specified by database.yml\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:25:07 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (6.1ms)\nCompiled application.css  (1ms)  (pid 67167)\nCompiled jquery.js  (13ms)  (pid 67167)\nCompiled jquery_ujs.js  (1ms)  (pid 67167)\nCompiled application.js  (113ms)  (pid 67167)\nCompleted 200 OK in 426ms (Views: 425.4ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:25:09 -0700 2013\nServed asset /application.css - 200 OK (4ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:25:09 -0700 2013\nServed asset /jquery.js - 200 OK (15ms)\n\n\nStarted GET \"/assets/jquery_ujs.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:25:09 -0700 2013\nServed asset /jquery_ujs.js - 200 OK (13ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:25:09 -0700 2013\nServed asset /application.js - 200 OK (32ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel\" for 127.0.0.1 at Sun Mar 31 05:25:22 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/open_id/marcel\"}\nCompleted 500 Internal Server Error in 2ms\n\nNameError (uninitialized constant ConsumerController::RAILS_ROOT):\n  app/controllers/consumer_controller.rb:116:in `consumer'\n  app/controllers/consumer_controller.rb:23:in `start'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.8ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (2.1ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (121.5ms)\nConnecting to database specified by database.yml\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel\" for 127.0.0.1 at Sun Mar 31 05:26:13 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/open_id/marcel\"}\nRedirected to http://localhost:3009/open_id/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582b66%7D%7BvdrbdQ%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 661ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582b69%7D%7BodxU%2BQ%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.invalidate_handle=%7BHMAC-SHA1%7D%7B51582b66%7D%7BvdrbdQ%3D%3D%7D&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fserver&openid.response_nonce=2013-03-31T12%3A26%3A17ZVVzuGN&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=HHdHfhXu%2FmKVDXkCffxuD%2BYuNeU%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cinvalidate_handle%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 05:26:17 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"HHdHfhXu/mKVDXkCffxuD+YuNeU=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/open_id/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T12:26:17ZVVzuGN\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/open_id/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,invalidate_handle,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.invalidate_handle\"=>\"{HMAC-SHA1}{51582b66}{vdrbdQ==}\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582b69}{odxU+Q==}\", \"openid.claimed_id\"=>\"http://localhost:3009/open_id/marcel\"}\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 4ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:26:17 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (14.7ms)\nCompleted 200 OK in 59ms (Views: 58.8ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:26:17 -0700 2013\nServed asset /application.js - 304 Not Modified (8ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:26:17 -0700 2013\nServed asset /application.css - 304 Not Modified (132ms)\nConnecting to database specified by database.yml\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:30:38 -0700 2013\n\nActionDispatch::Session::SessionRestoreError (Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: uninitialized constant OpenID [NameError])\n):\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:64:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/cookie_store.rb:48:in `unpacked_cookie_data'\n  rack (1.4.5) lib/rack/session/cookie.rb:107:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:57:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `load_session_id!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:32:in `[]'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:267:in `current_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:273:in `session_exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:127:in `load_for_read!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:64:in `key?'\n  actionpack (3.2.13) lib/action_dispatch/middleware/flash.rb:258:in `call'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/cookies.rb:341:in `call'\n  activerecord (3.2.13) lib/active_record/query_cache.rb:64:in `call'\n  activerecord (3.2.13) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:28:in `call'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `_run__397314981__call__4__callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:27:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/reloader.rb:65:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.8ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (4.6ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (23.6ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:30:45 -0700 2013\n\nActionDispatch::Session::SessionRestoreError (Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: uninitialized constant OpenID [NameError])\n):\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:64:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/cookie_store.rb:48:in `unpacked_cookie_data'\n  rack (1.4.5) lib/rack/session/cookie.rb:107:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:57:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `load_session_id!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:32:in `[]'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:267:in `current_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:273:in `session_exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:127:in `load_for_read!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:64:in `key?'\n  actionpack (3.2.13) lib/action_dispatch/middleware/flash.rb:258:in `call'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/cookies.rb:341:in `call'\n  activerecord (3.2.13) lib/active_record/query_cache.rb:64:in `call'\n  activerecord (3.2.13) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:28:in `call'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `_run__397314981__call__4__callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:27:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/reloader.rb:65:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.8ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.1ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (18.9ms)\nConnecting to database specified by database.yml\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:31:30 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (7.0ms)\nCompleted 200 OK in 198ms (Views: 197.4ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:31:31 -0700 2013\nServed asset /jquery.js - 304 Not Modified (7ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:31:31 -0700 2013\nServed asset /application.css - 304 Not Modified (3ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:31:31 -0700 2013\nServed asset /application.js - 304 Not Modified (44ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel\" for 127.0.0.1 at Sun Mar 31 05:31:36 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/open_id/marcel\"}\nRedirected to http://localhost:3009/open_id/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582b66%7D%7BvdrbdQ%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 65ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582cb5%7D%7B2olQew%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel&openid.invalidate_handle=%7BHMAC-SHA1%7D%7B51582b66%7D%7BvdrbdQ%3D%3D%7D&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fserver&openid.response_nonce=2013-03-31T12%3A31%3A49Z8CDJUo&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=skvSU5yuQsW%2BZwCkDbUbGmtMngc%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cinvalidate_handle%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 05:31:49 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"skvSU5yuQsW+ZwCkDbUbGmtMngc=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/open_id/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T12:31:49Z8CDJUo\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/open_id/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,invalidate_handle,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.invalidate_handle\"=>\"{HMAC-SHA1}{51582b66}{vdrbdQ==}\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582cb5}{2olQew==}\", \"openid.claimed_id\"=>\"http://localhost:3009/open_id/marcel\"}\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 65ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:31:49 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.7ms)\nCompleted 200 OK in 7ms (Views: 6.9ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:31:49 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:31:49 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:31:49 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\nConnecting to database specified by database.yml\n\n\nStarted GET \"/server\" for 127.0.0.1 at Sun Mar 31 05:32:59 -0700 2013\n\nActionDispatch::Session::SessionRestoreError (Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: uninitialized constant OpenID [NameError])\n):\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:64:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/cookie_store.rb:48:in `unpacked_cookie_data'\n  rack (1.4.5) lib/rack/session/cookie.rb:107:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:57:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `load_session_id!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:32:in `[]'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:267:in `current_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:273:in `session_exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:127:in `load_for_read!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:64:in `key?'\n  actionpack (3.2.13) lib/action_dispatch/middleware/flash.rb:258:in `call'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/cookies.rb:341:in `call'\n  activerecord (3.2.13) lib/active_record/query_cache.rb:64:in `call'\n  activerecord (3.2.13) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:28:in `call'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `_run__397314981__call__4__callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:27:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/reloader.rb:65:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.9ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (2.0ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (19.9ms)\n\n\nStarted GET \"/server\" for 127.0.0.1 at Sun Mar 31 05:33:19 -0700 2013\nProcessing by ServerController#index as HTML\n  Rendered text template (0.0ms)\nCompleted 500 Internal Server Error in 31ms (Views: 25.3ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server\" for 127.0.0.1 at Sun Mar 31 05:33:41 -0700 2013\nProcessing by ServerController#index as HTML\n  Rendered text template (0.0ms)\nCompleted 500 Internal Server Error in 5ms (Views: 2.7ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:33:47 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.8ms)\nCompleted 200 OK in 11ms (Views: 10.3ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:33:47 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:33:47 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:33:47 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fopen_id%2Fmarcel\" for 127.0.0.1 at Sun Mar 31 05:33:57 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/open_id/marcel\"}\n\n\nStarted GET \"/open_id/marcel\" for 127.0.0.1 at Sun Mar 31 05:33:57 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/open_id/marcel\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.6ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 38ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:33:57 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.7ms)\nCompleted 200 OK in 8ms (Views: 8.0ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:33:57 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:33:57 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:33:57 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fserver\" for 127.0.0.1 at Sun Mar 31 05:34:12 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/server\"}\n\n\nStarted GET \"/server\" for 127.0.0.1 at Sun Mar 31 05:34:12 -0700 2013\nProcessing by ServerController#index as application/xrds+xml\n  Rendered text template (0.0ms)\nCompleted 500 Internal Server Error in 5ms (Views: 1.9ms | ActiveRecord: 0.0ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 20ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:34:13 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.6ms)\nCompleted 200 OK in 11ms (Views: 10.2ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:34:13 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:34:13 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:34:13 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fserver%2Fidp_xrds\" for 127.0.0.1 at Sun Mar 31 05:35:31 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/server/idp_xrds\"}\n\n\nStarted GET \"/server/idp_xrds\" for 127.0.0.1 at Sun Mar 31 05:35:31 -0700 2013\nProcessing by ServerController#idp_xrds as application/xrds+xml\n  Rendered text template (0.0ms)\nCompleted 200 OK in 3ms (Views: 0.6ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server\" for 127.0.0.1 at Sun Mar 31 05:35:32 -0700 2013\nProcessing by ServerController#index as */*\n  Parameters: {\"openid.mode\"=>\"associate\", \"openid.assoc_type\"=>\"HMAC-SHA1\", \"openid.session_type\"=>\"DH-SHA1\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.dh_consumer_public\"=>\"Ohd6vp/95+pq56SpKTaquSLrgWcVYQwVDbe+KsVnfn/bWAsT2TGS3v7AjwWKx5rfCZauIQ/c55uwMjT5iX5sAQFcNdOVtl5Ue42oFfkHDu01A7xovf2ZUdbw10m0Cl+g0BjDZRBagDShrlvBP3SZDMPvQu4o/04GhA6rChWnaGA=\"}\nWARNING: Can't verify CSRF token authenticity\n  Rendered text template (0.0ms)\nCompleted 200 OK in 40ms (Views: 0.7ms | ActiveRecord: 0.0ms)\nRedirected to http://localhost:3009/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 308ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:35:32 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\n  Rendered server/decide.html.erb within layouts/server (1.2ms)\nCompleted 200 OK in 21ms (Views: 5.0ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:37:07 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\n  Rendered server/decide.html.erb within layouts/server (2.1ms)\nCompleted 200 OK in 18ms (Views: 5.0ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:37:10 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\n  Rendered server/decide.html.erb within layouts/server (1.1ms)\nCompleted 200 OK in 16ms (Views: 4.4ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:38:02 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.0ms)\nCompleted 200 OK in 15ms (Views: 8.3ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:38:11 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.2ms)\nCompleted 200 OK in 18ms (Views: 14.0ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:39:23 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.1ms)\nCompleted 200 OK in 8ms (Views: 5.0ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:39:26 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nCompleted 200 OK in 11ms (Views: 4.1ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:39:52 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"http://localhost:3009/marcel\"}\nWARNING: Can't verify CSRF token authenticity\nCompleted 500 Internal Server Error in 1ms\n\nNoMethodError (undefined method `identity' for nil:NilClass):\n  app/controllers/server_controller.rb:143:in `decision'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.7ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.4ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (15.5ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:41:02 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"http://localhost:3009/marcel\"}\nWARNING: Can't verify CSRF token authenticity\nCompleted 500 Internal Server Error in 1ms\n\nNoMethodError (undefined method `identity' for nil:NilClass):\n  app/controllers/server_controller.rb:143:in `decision'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.9ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.2ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (14.4ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:41:56 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"http://localhost:3009/marcel\"}\nWARNING: Can't verify CSRF token authenticity\n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 1.3ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 05:42:11 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.8ms)\nCompleted 200 OK in 12ms (Views: 10.9ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:42:12 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 05:42:12 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 05:42:12 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fserver%2Fidp_xrds\" for 127.0.0.1 at Sun Mar 31 05:42:28 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/server/idp_xrds\"}\n\n\nStarted GET \"/server/idp_xrds\" for 127.0.0.1 at Sun Mar 31 05:42:28 -0700 2013\nProcessing by ServerController#idp_xrds as application/xrds+xml\n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms)\nRedirected to http://localhost:3009/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 31ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:42:28 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nCompleted 200 OK in 8ms (Views: 3.8ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:42:36 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"http://localhost:3009/marcel\"}\nWARNING: Can't verify CSRF token authenticity\n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:45:38 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nCompleted 200 OK in 19ms (Views: 10.1ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:46:02 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nCompleted 200 OK in 7ms (Views: 3.7ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:46:33 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.5ms)\nsession: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\nCompleted 500 Internal Server Error in 10ms\n\nAbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\".):\n  app/controllers/server_controller.rb:88:in `show_decision_page'\n  app/controllers/server_controller.rb:45:in `index'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (2.2ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (5.0ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (25.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:46:47 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nCompleted 200 OK in 8ms (Views: 4.8ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:48:24 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.3ms)\nsession: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nCompleted 200 OK in 15ms (Views: 11.0ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:48:44 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"http://localhost:3009/marcel\"}\nWARNING: Can't verify CSRF token authenticity\nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:54:41 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.6ms)\nsession: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession size: 804\nCompleted 200 OK in 14ms (Views: 5.4ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:54:59 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nCompleted 500 Internal Server Error in 12ms\n\nTypeError (can't dump anonymous module #<Module:0x10225e948>):\n  app/controllers/server_controller.rb:45:in `dump'\n  app/controllers/server_controller.rb:45:in `index'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.9ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (3.6ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (27.3ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:56:19 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nCompleted 200 OK in 8ms (Views: 4.3ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:56:21 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:56:26 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:56:36 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nCompleted 200 OK in 8ms (Views: 3.7ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:56:37 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.5ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nCompleted 200 OK in 7ms (Views: 3.1ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:56:44 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.8ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:57:14 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 1.4ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:57:35 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 3ms (Views: 2.1ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 05:58:17 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 1.3ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 05:59:23 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: 0df6cf9c44f69c6a4ab15e8a889cdbfc\nCompleted 200 OK in 8ms (Views: 3.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:00:27 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: 0df6cf9c44f69c6a4ab15e8a889cdbfc\nsession_id cookie: \nCompleted 200 OK in 8ms (Views: 4.4ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:00:46 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.9ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: 0df6cf9c44f69c6a4ab15e8a889cdbfc\nsession_id cookie: BAh7CSIPc2Vzc2lvbl9pZCIlMGRmNmNmOWM0NGY2OWM2YTRhYjE1ZThhODg5Y2RiZmMiCmhlbGxvIgp3b3JsZCIKZmxhc2hvOiVBY3Rpb25EaXNwYXRjaDo6Rmxhc2g6OkZsYXNoSGFzaAk6DUBmbGFzaGVzewY6C25vdGljZSIvRG8geW91IHRydXN0IHRoaXMgc2l0ZSB3aXRoIHlvdXIgaWRlbnRpdHk/OgpAdXNlZG86CFNldAY6CkBoYXNoewA6DEBjbG9zZWRGOglAbm93MCIQbGFzdF9vaWRyZXFvOiNPcGVuSUQ6OlNlcnZlcjo6Q2hlY2tJRFJlcXVlc3QOOhBAdHJ1c3Rfcm9vdCIjaHR0cDovL2xvY2FsaG9zdDozMDEwL2NvbnN1bWVyOg9AcmV0dXJuX3RvIixodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXIvY29tcGxldGU6DUBtZXNzYWdlbzoUT3BlbklEOjpNZXNzYWdlCDoQQG5hbWVzcGFjZXNvOhlPcGVuSUQ6Ok5hbWVzcGFjZU1hcAg6GEBhbGlhc190b19uYW1lc3BhY2V7BjoTbnVsbF9uYW1lc3BhY2UiJWh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wOhlAaW1wbGljaXRfbmFtZXNwYWNlc1sAOhhAbmFtZXNwYWNlX3RvX2FsaWFzewYiJWh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wOxU6E0BvcGVuaWRfbnNfdXJpQBc6CkBhcmdzew1bB0AXIgltb2RlIhJjaGVja2lkX3NldHVwWwdAFyIPY2xhaW1lZF9pZCI3aHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjAvaWRlbnRpZmllcl9zZWxlY3RbBzoTYmFyZV9uYW1lc3BhY2UiC2FjdGlvbiIKaW5kZXhbBzsaIg9jb250cm9sbGVyIgtzZXJ2ZXJbB0AXIhFhc3NvY19oYW5kbGUiJHtITUFDLVNIQTF9ezUxNTgyZDk0fXtjZGtvMWc9PX1bB0AXIg5yZXR1cm5fdG9AE1sHQBciCnJlYWxtQBJbB0AXIg1pZGVudGl0eSI3aHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjAvaWRlbnRpZmllcl9zZWxlY3Q6D0BpbW1lZGlhdGVGOg5AaWRlbnRpdHlAMToSQGFzc29jX2hhbmRsZUAqOhBAY2xhaW1lZF9pZEAhOgpAbW9kZSISY2hlY2tpZF9zZXR1cDoRQG9wX2VuZHBvaW50IiFodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVy--e9977c9d345b7da60a35bbd6b27618a288f54057\nCompleted 200 OK in 31ms (Views: 14.7ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:01:27 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7CSIPc2Vzc2lvbl9pZCIlMGRmNmNmOWM0NGY2OWM2YTRhYjE1ZThhODg5Y2RiZmMiCmhlbGxvIgp3b3JsZCIQbGFzdF9vaWRyZXFvOiNPcGVuSUQ6OlNlcnZlcjo6Q2hlY2tJRFJlcXVlc3QOOhBAdHJ1c3Rfcm9vdCIjaHR0cDovL2xvY2FsaG9zdDozMDEwL2NvbnN1bWVyOg9AcmV0dXJuX3RvIixodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXIvY29tcGxldGU6DUBtZXNzYWdlbzoUT3BlbklEOjpNZXNzYWdlCDoQQG5hbWVzcGFjZXNvOhlPcGVuSUQ6Ok5hbWVzcGFjZU1hcAg6GEBhbGlhc190b19uYW1lc3BhY2V7BjoTbnVsbF9uYW1lc3BhY2UiJWh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wOhlAaW1wbGljaXRfbmFtZXNwYWNlc1sAOhhAbmFtZXNwYWNlX3RvX2FsaWFzewYiJWh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wOw06E0BvcGVuaWRfbnNfdXJpQBE6CkBhcmdzew1bB0ARIgltb2RlIhJjaGVja2lkX3NldHVwWwdAESIPY2xhaW1lZF9pZCI3aHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjAvaWRlbnRpZmllcl9zZWxlY3RbBzoTYmFyZV9uYW1lc3BhY2UiC2FjdGlvbiIKaW5kZXhbBzsSIg9jb250cm9sbGVyIgtzZXJ2ZXJbB0ARIhFhc3NvY19oYW5kbGUiJHtITUFDLVNIQTF9ezUxNTgyZDk0fXtjZGtvMWc9PX1bB0ARIg5yZXR1cm5fdG9ADVsHQBEiCnJlYWxtQAxbB0ARIg1pZGVudGl0eSI3aHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjAvaWRlbnRpZmllcl9zZWxlY3Q6D0BpbW1lZGlhdGVGOg5AaWRlbnRpdHlAKzoSQGFzc29jX2hhbmRsZUAkOhBAY2xhaW1lZF9pZEAbOgpAbW9kZSISY2hlY2tpZF9zZXR1cDoRQG9wX2VuZHBvaW50IiFodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVyIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIi9EbyB5b3UgdHJ1c3QgdGhpcyBzaXRlIHdpdGggeW91ciBpZGVudGl0eT86CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cw--097664684a5b4ec726b2e9621c60066bdffa2b08\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 1.2ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:01:46 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiViMjkzOWYzZGM1MGUzZGVhN2MzMjQwODgwMzk4ZDRlZg==--5c9b11469badcabaca82548d4d506f6b7b1e834f\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 1.2ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:07:21 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.2ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: d50d3dd624bc8ca8e44f1203c96a8f93\nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiVkNTBkM2RkNjI0YmM4Y2E4ZTQ0ZjEyMDNjOTZhOGY5Mw==--4184fd96c6f41491285b5f6c1e96b4b72e28c2f8\nCompleted 200 OK in 10ms (Views: 4.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:07:33 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (157.0ms)\nCompleted 500 Internal Server Error in 166ms\n\nActionView::Template::Error (undefined local variable or method `form_authenticity_token_field' for #<#<Class:0x103647298>:0x10358a698>):\n    1: <form method=\"post\" action=\"<%= url_for :controller => 'server', :action => 'decision' %>\">\n    2:   <%= form_authenticity_token_field %>\n    3: \n    4: <table>\n    5:   <tr><td>Site:</td><td><%= @oidreq.trust_root %></td></tr>\n  app/views/server/decide.html.erb:2:in `_app_views_server_decide_html_erb___1096845836_2175551400'\n  app/controllers/server_controller.rb:92:in `show_decision_page'\n  app/controllers/server_controller.rb:43:in `index'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.8ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (2.3ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/template_error.erb within rescues/layout (15.6ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:09:13 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (42.6ms)\nCompleted 500 Internal Server Error in 49ms\n\nActionView::Template::Error (undefined local variable or method `form_authenticity_param' for #<#<Class:0x103647298>:0x103920618>):\n    1: <form method=\"post\" action=\"<%= url_for :controller => 'server', :action => 'decision' %>\">\n    2:   <input type=\"hidden\" name=\"<%= form_authenticity_param %>\" value=\"<%= form_authenticity_token %>\">\n    3: \n    4: <table>\n    5:   <tr><td>Site:</td><td><%= @oidreq.trust_root %></td></tr>\n  app/views/server/decide.html.erb:2:in `_app_views_server_decide_html_erb___1096845836_2177422820'\n  app/controllers/server_controller.rb:92:in `show_decision_page'\n  app/controllers/server_controller.rb:43:in `index'\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (3.1ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.6ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/template_error.erb within rescues/layout (20.9ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:10:58 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.0ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: d50d3dd624bc8ca8e44f1203c96a8f93\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMTVIMWQ0bEVzMnA4Qjd0enZHTXNIZXRLOUtPcTZCbFl4UFN6dyt2SFJTckE9Ig9zZXNzaW9uX2lkIiVkNTBkM2RkNjI0YmM4Y2E4ZTQ0ZjEyMDNjOTZhOGY5MyIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiL0RvIHlvdSB0cnVzdCB0aGlzIHNpdGUgd2l0aCB5b3VyIGlkZW50aXR5PzoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--0b12a84d3b56c065d035566362e3e9374de86039\nCompleted 200 OK in 8ms (Views: 4.8ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:11:00 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMTVIMWQ0bEVzMnA4Qjd0enZHTXNIZXRLOUtPcTZCbFl4UFN6dyt2SFJTckE9Ig9zZXNzaW9uX2lkIiVkNTBkM2RkNjI0YmM4Y2E4ZTQ0ZjEyMDNjOTZhOGY5MyIKaGVsbG8iCndvcmxkIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIi9EbyB5b3UgdHJ1c3QgdGhpcyBzaXRlIHdpdGggeW91ciBpZGVudGl0eT86CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cwIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7FToTQG9wZW5pZF9uc191cmlAGToKQGFyZ3N7DVsHQBkiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0AZIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxoiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBkiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBkiDnJldHVybl90b0AVWwdAGSIKcmVhbG1AFFsHQBkiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAzOhJAYXNzb2NfaGFuZGxlQCw6EEBjbGFpbWVkX2lkQCM6CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXI=--efe18d78f0dd5ccfbb16f8706b35781d95ffead8\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:11:03 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: b0111bc499a041f6669c0cffd50c4854\nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiViMDExMWJjNDk5YTA0MWY2NjY5YzBjZmZkNTBjNDg1NA==--73c5bfc2bb790437f2b141aaf0c20cd6cc426da7\nCompleted 200 OK in 9ms (Views: 3.5ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:11:05 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.8ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: b0111bc499a041f6669c0cffd50c4854\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMURlelQzMjdHQk9aRUdxakRaM2pjeTFjY3ZsRmN0eHJQdUF2UGJLWUg3WTA9Ig9zZXNzaW9uX2lkIiViMDExMWJjNDk5YTA0MWY2NjY5YzBjZmZkNTBjNDg1NCIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiL0RvIHlvdSB0cnVzdCB0aGlzIHNpdGUgd2l0aCB5b3VyIGlkZW50aXR5PzoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--4aa0ecdd65e5219704711e40c58139be1348db1f\nCompleted 200 OK in 10ms (Views: 5.1ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:11:06 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMURlelQzMjdHQk9aRUdxakRaM2pjeTFjY3ZsRmN0eHJQdUF2UGJLWUg3WTA9Ig9zZXNzaW9uX2lkIiViMDExMWJjNDk5YTA0MWY2NjY5YzBjZmZkNTBjNDg1NCIKaGVsbG8iCndvcmxkIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIi9EbyB5b3UgdHJ1c3QgdGhpcyBzaXRlIHdpdGggeW91ciBpZGVudGl0eT86CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cwIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7FToTQG9wZW5pZF9uc191cmlAGToKQGFyZ3N7DVsHQBkiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0AZIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxoiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBkiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBkiDnJldHVybl90b0AVWwdAGSIKcmVhbG1AFFsHQBkiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAzOhJAYXNzb2NfaGFuZGxlQCw6EEBjbGFpbWVkX2lkQCM6CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXI=--2e2b7c25a4b12809bc26063679446f2ea9bb0c95\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 0.6ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:11:40 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiVkZTEzMjgwMDA5ZGU5NGQ1NjkzYWExNjUxNDFmMWYyNQ==--fb03aa366527aebbe8dd7fd9a6be3905ea7690b6\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 4ms (Views: 1.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:11:42 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: 396fcb58d8777c7f869bf8084e96482c\nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiUzOTZmY2I1OGQ4Nzc3YzdmODY5YmY4MDg0ZTk2NDgyYw==--48268edfcda33e221ff56a92f7c2cc30efd4951a\nCompleted 200 OK in 14ms (Views: 5.4ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:11:43 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMVBONjA1NXlibkNzaWJEZ1pTZ0k5KzEwZStoVXdRZUpPYU5FYzRlL3J0MW89Ig9zZXNzaW9uX2lkIiUzOTZmY2I1OGQ4Nzc3YzdmODY5YmY4MDg0ZTk2NDgyYyIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiL0RvIHlvdSB0cnVzdCB0aGlzIHNpdGUgd2l0aCB5b3VyIGlkZW50aXR5PzoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--9d8e44ba14ab5c85a086e860ea1481f71067e88f\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:12:51 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.0ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: 2c66ed926ccfe2103c298469eb4818f8\nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiUyYzY2ZWQ5MjZjY2ZlMjEwM2MyOTg0NjllYjQ4MThmOA==--6457aa16715de946fb9167e165af7f022d49cd5f\nCompleted 200 OK in 7ms (Views: 3.8ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:12:53 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"_token\"=>\"rLGwlRuk0Zz/nS165br3PrN5Zre3xpwKpSsHGDqntog=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nWARNING: Can't verify CSRF token authenticity\nsession_id: \nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMXJMR3dsUnVrMFp6L25TMTY1YnIzUHJONVpyZTN4cHdLcFNzSEdEcW50b2c9Ig9zZXNzaW9uX2lkIiUyYzY2ZWQ5MjZjY2ZlMjEwM2MyOTg0NjllYjQ4MThmOCIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiL0RvIHlvdSB0cnVzdCB0aGlzIHNpdGUgd2l0aCB5b3VyIGlkZW50aXR5PzoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--f8ed02db84ccf0f80fc1bd2f0f4095cbc249caac\nhello: \nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 0.9ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:15:54 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.7ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7BzoQbGFzdF9vaWRyZXEwIg9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMQ==--0b1f7487ae5567045e91d39d107132c75a1e1c59\nCompleted 200 OK in 45ms (Views: 40.8ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:15:55 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMSIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiL0RvIHlvdSB0cnVzdCB0aGlzIHNpdGUgd2l0aCB5b3VyIGlkZW50aXR5PzoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--9a1ec542115a344418fdc5587685b73737444aef\nhello: world\nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.7ms)\nCompleted 200 OK in 5ms (Views: 4.4ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:15:58 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMSIKaGVsbG8iCndvcmxkIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIlZZb3UgbXVzdCBlbnRlciBhIHVzZXJuYW1lIHRvIGluIG9yZGVyIHRvIHNlbmQgYW4gaWRlbnRpZmllciB0byB0aGUgUmVseWluZyBQYXJ0eS46CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cwIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7FToKQGFyZ3N7DVsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHQBkiD2NsYWltZWRfaWQiN2h0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wL2lkZW50aWZpZXJfc2VsZWN0WwdAGSIJbW9kZSISY2hlY2tpZF9zZXR1cFsHOxkiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBkiDnJldHVybl90b0AVWwdAGSIRYXNzb2NfaGFuZGxlIiR7SE1BQy1TSEExfXs1MTU4MmQ5NH17Y2RrbzFnPT19WwdAGSINaWRlbnRpdHkiN2h0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wL2lkZW50aWZpZXJfc2VsZWN0WwdAGSIKcmVhbG1AFDoTQG9wZW5pZF9uc191cmlAGToPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAxOhJAYXNzb2NfaGFuZGxlQC46EUBvcF9lbmRwb2ludCIhaHR0cDovL2xvY2FsaG9zdDozMDA5L3NlcnZlcjoKQG1vZGUiEmNoZWNraWRfc2V0dXA6EEBjbGFpbWVkX2lkQCM=--d18faa6ccd17ce34ee9d7d0cc67cfa3b8e5589c1\nhello: world\nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.8ms)\nCompleted 200 OK in 6ms (Views: 5.0ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:16:23 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMSIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiVllvdSBtdXN0IGVudGVyIGEgdXNlcm5hbWUgdG8gaW4gb3JkZXIgdG8gc2VuZCBhbiBpZGVudGlmaWVyIHRvIHRoZSBSZWx5aW5nIFBhcnR5LjoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--755060a8760641df75a8c6ce57f0e8e076a90b57\nhello: world\nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nCompleted 200 OK in 7ms (Views: 4.4ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:16:27 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMSIKaGVsbG8iCndvcmxkIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIlZZb3UgbXVzdCBlbnRlciBhIHVzZXJuYW1lIHRvIGluIG9yZGVyIHRvIHNlbmQgYW4gaWRlbnRpZmllciB0byB0aGUgUmVseWluZyBQYXJ0eS46CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cwIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7FToKQGFyZ3N7DVsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHQBkiD2NsYWltZWRfaWQiN2h0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wL2lkZW50aWZpZXJfc2VsZWN0WwdAGSIJbW9kZSISY2hlY2tpZF9zZXR1cFsHOxkiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBkiDnJldHVybl90b0AVWwdAGSIRYXNzb2NfaGFuZGxlIiR7SE1BQy1TSEExfXs1MTU4MmQ5NH17Y2RrbzFnPT19WwdAGSINaWRlbnRpdHkiN2h0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wL2lkZW50aWZpZXJfc2VsZWN0WwdAGSIKcmVhbG1AFDoTQG9wZW5pZF9uc191cmlAGToPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAxOhJAYXNzb2NfaGFuZGxlQC46EUBvcF9lbmRwb2ludCIhaHR0cDovL2xvY2FsaG9zdDozMDA5L3NlcnZlcjoKQG1vZGUiEmNoZWNraWRfc2V0dXA6EEBjbGFpbWVkX2lkQCM=--d18faa6ccd17ce34ee9d7d0cc67cfa3b8e5589c1\nhello: world\nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (0.6ms)\nCompleted 200 OK in 4ms (Views: 3.1ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"http://localhost:3009/marcel\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMSIKaGVsbG8iCndvcmxkIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7DToTQG9wZW5pZF9uc191cmlAEzoKQGFyZ3N7DVsHQBMiCW1vZGUiEmNoZWNraWRfc2V0dXBbB0ATIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOxIiD2NvbnRyb2xsZXIiC3NlcnZlclsHQBMiEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQBMiDnJldHVybl90b0APWwdAEyIKcmVhbG1ADlsHQBMiDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUAtOhJAYXNzb2NfaGFuZGxlQCY6EEBjbGFpbWVkX2lkQB06CkBtb2RlIhJjaGVja2lkX3NldHVwOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIiCmZsYXNobzolQWN0aW9uRGlzcGF0Y2g6OkZsYXNoOjpGbGFzaEhhc2gJOg1AZmxhc2hlc3sGOgtub3RpY2UiVllvdSBtdXN0IGVudGVyIGEgdXNlcm5hbWUgdG8gaW4gb3JkZXIgdG8gc2VuZCBhbiBpZGVudGlmaWVyIHRvIHRoZSBSZWx5aW5nIFBhcnR5LjoKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoJQG5vdzA=--755060a8760641df75a8c6ce57f0e8e076a90b57\nhello: world\nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRedirected to http://localhost:3010/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A16%3A32ZBnQWSS&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=nk5Cd60iCeaC5tEfTFXeq8bJxkM%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\nCompleted 302 Found in 50ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A16%3A32ZBnQWSS&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=nk5Cd60iCeaC5tEfTFXeq8bJxkM%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"nk5Cd60iCeaC5tEfTFXeq8bJxkM=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T13:16:32ZBnQWSS\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.claimed_id\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\"}\n\n\nStarted GET \"/user/http://localhost:3009/marcel\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/user/http:/localhost:3009/marcel\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.6ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 103ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (1.3ms)\nCompleted 200 OK in 229ms (Views: 219.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:16:32 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:16:53 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"marcel\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CyIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig9zZXNzaW9uX2lkIiVmZjRhYjNjMjgzM2Q2MzI0ZjFmY2MzYTRkZTU4N2ZlMSINdXNlcm5hbWUiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9tYXJjZWwiCmhlbGxvIgp3b3JsZCIKZmxhc2hvOiVBY3Rpb25EaXNwYXRjaDo6Rmxhc2g6OkZsYXNoSGFzaAk6DUBmbGFzaGVzewY6CmVycm9yIgGaVmVyaWZpY2F0aW9uIGZhaWxlZDogSFRUUCBSZXNwb25zZSBzdGF0dXMgZnJvbSBpZGVudGl0eSBVUkwgaG9zdCBpcyBub3QgIjIwMCIuR290IHN0YXR1cyAiNDA0IiBmb3IgaHR0cDovL2xvY2FsaG9zdDozMDA5L3VzZXIvaHR0cDovL2xvY2FsaG9zdDozMDA5L21hcmNlbDoKQHVzZWRvOghTZXQGOgpAaGFzaHsGOwdUOgxAY2xvc2VkRjoJQG5vdzAiDmFwcHJvdmFsc1sGIiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI=--419dee51ac0f7222ab6c721397583a987665e92b\nhello: world\nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:16:57 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"jgWPOedLngI1CoE4miIBy3FprrZr/xYlHnBoTWn9fOc=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"\"}\nsession_id: ff4ab3c2833d6324f1fcc3a4de587fe1\nsession_id cookie: BAh7CiIQX2NzcmZfdG9rZW4iMWpnV1BPZWRMbmdJMUNvRTRtaUlCeTNGcHJyWnIveFlsSG5Cb1RXbjlmT2M9Ig11c2VybmFtZSIhaHR0cDovL2xvY2FsaG9zdDozMDA5L21hcmNlbCIPc2Vzc2lvbl9pZCIlZmY0YWIzYzI4MzNkNjMyNGYxZmNjM2E0ZGU1ODdmZTEiCmhlbGxvIgp3b3JsZCIOYXBwcm92YWxzWwYiI2h0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lcg==--793b2103b83a8f1f36926f105f4f81683d9f5a7f\nhello: world\nlast_oidreq: \n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:17:08 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRedirected to http://localhost:3010/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A17%3A08ZUxQiz5&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=4HAQxaR%2B5Mujm5aHJjPjV8E6kPo%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\nCompleted 302 Found in 196ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A17%3A08ZUxQiz5&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=4HAQxaR%2B5Mujm5aHJjPjV8E6kPo%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 06:17:09 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"4HAQxaR+5Mujm5aHJjPjV8E6kPo=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T13:17:08ZUxQiz5\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.claimed_id\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\"}\n\n\nStarted GET \"/user/http://localhost:3009/marcel\" for 127.0.0.1 at Sun Mar 31 06:17:09 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/user/http:/localhost:3009/marcel\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (3.2ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 70ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 06:17:09 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (1.1ms)\nCompleted 200 OK in 21ms (Views: 17.4ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:09 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:09 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:09 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:17:16 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRedirected to http://localhost:3010/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A17%3A16ZE1EQft&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=EBYQ%2BmvGOdl6C1fW2uvPjRS3XMk%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\nCompleted 302 Found in 25ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A17%3A16ZE1EQft&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=EBYQ%2BmvGOdl6C1fW2uvPjRS3XMk%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 06:17:16 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"EBYQ+mvGOdl6C1fW2uvPjRS3XMk=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T13:17:16ZE1EQft\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.claimed_id\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\"}\n\n\nStarted GET \"/user/http://localhost:3009/marcel\" for 127.0.0.1 at Sun Mar 31 06:17:16 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/user/http:/localhost:3009/marcel\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.7ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 62ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:16 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:16 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:16 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fserver%2Fidp_xrds\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/server/idp_xrds\"}\n\n\nStarted GET \"/server/idp_xrds\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nProcessing by ServerController#idp_xrds as application/xrds+xml\n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 0.6ms | ActiveRecord: 0.0ms)\nRedirected to http://localhost:3009/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 60ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nRedirected to http://localhost:3010/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A17%3A26Z1eD29V&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=MXD7sDIZw8q8tsYYlj%2FAsSmIHTg%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\nCompleted 302 Found in 27ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fhttp%3A%2F%2Flocalhost%3A3009%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A17%3A26Z1eD29V&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=MXD7sDIZw8q8tsYYlj%2FAsSmIHTg%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"MXD7sDIZw8q8tsYYlj/AsSmIHTg=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T13:17:26Z1eD29V\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.claimed_id\"=>\"http://localhost:3009/user/http://localhost:3009/marcel\"}\n\n\nStarted GET \"/user/http://localhost:3009/marcel\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/user/http:/localhost:3009/marcel\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.6ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 61ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.8ms)\nCompleted 200 OK in 11ms (Views: 10.5ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:26 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 06:17:59 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.7ms)\nCompleted 200 OK in 10ms (Views: 9.6ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:59 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:59 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:17:59 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fserver%2Fidp_xrds\" for 127.0.0.1 at Sun Mar 31 06:18:07 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/server/idp_xrds\"}\n\n\nStarted GET \"/server/idp_xrds\" for 127.0.0.1 at Sun Mar 31 06:18:07 -0700 2013\nProcessing by ServerController#idp_xrds as application/xrds+xml\n  Rendered text template (0.0ms)\nCompleted 200 OK in 2ms (Views: 0.8ms | ActiveRecord: 0.0ms)\nRedirected to http://localhost:3009/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 26ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:18:07 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://specs.openid.net/auth/2.0/identifier_select\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://specs.openid.net/auth/2.0/identifier_select\"}\nThe user hasn't logged in\noidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (6.2ms)\nsession last_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nsession last_oidreq size: 804\nsession_id: d6218bef503e314e9d7332aad45fbbc9\nsession_id cookie: BAh7CSIQX2NzcmZfdG9rZW4iMXlHVTJMb2V0MUZ0ZEZRUVcyZityYkJtTFVBZWkxT0lJczNpRUE5cmpUQms9Ii5PcGVuSUQ6OkNvbnN1bWVyOjpsYXN0X3JlcXVlc3RlZF9lbmRwb2ludG86Ik9wZW5JRDo6T3BlbklEU2VydmljZUVuZHBvaW50DDoSQGNhbm9uaWNhbF9pZDA6EEBjbGFpbWVkX2lkMDoOQGxvY2FsX2lkMDoYQGRpc3BsYXlfaWRlbnRpZmllcjA6D0B0eXBlX3VyaXNbBiIsaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjAvc2VydmVyOhBAdXNlZF95YWRpc1Q6EEBzZXJ2ZXJfdXJsIiFodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVyIg9zZXNzaW9uX2lkIiVkNjIxOGJlZjUwM2UzMTRlOWQ3MzMyYWFkNDVmYmJjOSI9T3BlbklEOjpDb25zdW1lcjo6RGlzY292ZXJlZFNlcnZpY2VzOjpPcGVuSUQ6OkNvbnN1bWVyOjpvOilPcGVuSUQ6OkNvbnN1bWVyOjpEaXNjb3ZlcmVkU2VydmljZXMJOhJAc3RhcnRpbmdfdXJsIipodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVyL2lkcF94cmRzOg9AeWFkaXNfdXJsIipodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVyL2lkcF94cmRzOg1AY3VycmVudEAJOg5Ac2VydmljZXNbAA==--41e24383267c05452a1bcd365b2ce4c9c5156926\nCompleted 200 OK in 24ms (Views: 16.2ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"yGU2Loet1FtdFQQW2f+rbBmLUAei1OIIs3iEA9rjTBk=\", \"yes\"=>\"yes\", \"id_to_send\"=>\"marcel\"}\nsession_id: d6218bef503e314e9d7332aad45fbbc9\nsession_id cookie: BAh7DCIuT3BlbklEOjpDb25zdW1lcjo6bGFzdF9yZXF1ZXN0ZWRfZW5kcG9pbnRvOiJPcGVuSUQ6Ok9wZW5JRFNlcnZpY2VFbmRwb2ludAw6DkBsb2NhbF9pZDA6GEBkaXNwbGF5X2lkZW50aWZpZXIwOg9AdHlwZV91cmlzWwYiLGh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wL3NlcnZlcjoQQHVzZWRfeWFkaXNUOhBAc2VydmVyX3VybCIhaHR0cDovL2xvY2FsaG9zdDozMDA5L3NlcnZlcjoSQGNhbm9uaWNhbF9pZDA6EEBjbGFpbWVkX2lkMCIQX2NzcmZfdG9rZW4iMXlHVTJMb2V0MUZ0ZEZRUVcyZityYkJtTFVBZWkxT0lJczNpRUE5cmpUQms9Ig9zZXNzaW9uX2lkIiVkNjIxOGJlZjUwM2UzMTRlOWQ3MzMyYWFkNDVmYmJjOSIKaGVsbG8iCndvcmxkIj1PcGVuSUQ6OkNvbnN1bWVyOjpEaXNjb3ZlcmVkU2VydmljZXM6Ok9wZW5JRDo6Q29uc3VtZXI6Om86KU9wZW5JRDo6Q29uc3VtZXI6OkRpc2NvdmVyZWRTZXJ2aWNlcwk6D0B5YWRpc191cmwiKmh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXIvaWRwX3hyZHM6DkBzZXJ2aWNlc1sAOhJAc3RhcnRpbmdfdXJsIipodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVyL2lkcF94cmRzOg1AY3VycmVudEAHIhBsYXN0X29pZHJlcW86I09wZW5JRDo6U2VydmVyOjpDaGVja0lEUmVxdWVzdA46EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6D0ByZXR1cm5fdG8iLGh0dHA6Ly9sb2NhbGhvc3Q6MzAxMC9jb25zdW1lci9jb21wbGV0ZToNQG1lc3NhZ2VvOhRPcGVuSUQ6Ok1lc3NhZ2UIOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQGFsaWFzX3RvX25hbWVzcGFjZXsGOhNudWxsX25hbWVzcGFjZSIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6GEBuYW1lc3BhY2VfdG9fYWxpYXN7BiIlaHR0cDovL3NwZWNzLm9wZW5pZC5uZXQvYXV0aC8yLjA7GjoTQG9wZW5pZF9uc191cmlAHToKQGFyZ3N7DVsHQB0iCW1vZGUiEmNoZWNraWRfc2V0dXBbB0AdIg9jbGFpbWVkX2lkIjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdFsHOhNiYXJlX25hbWVzcGFjZSILYWN0aW9uIgppbmRleFsHOx8iD2NvbnRyb2xsZXIiC3NlcnZlclsHQB0iEWFzc29jX2hhbmRsZSIke0hNQUMtU0hBMX17NTE1ODJkOTR9e2Nka28xZz09fVsHQB0iDnJldHVybl90b0AZWwdAHSIKcmVhbG1AGFsHQB0iDWlkZW50aXR5IjdodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMC9pZGVudGlmaWVyX3NlbGVjdDoPQGltbWVkaWF0ZUY6DkBpZGVudGl0eUA3OhJAYXNzb2NfaGFuZGxlQDA7DEAnOgpAbW9kZSISY2hlY2tpZF9zZXR1cDoRQG9wX2VuZHBvaW50IiFodHRwOi8vbG9jYWxob3N0OjMwMDkvc2VydmVyIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIi9EbyB5b3UgdHJ1c3QgdGhpcyBzaXRlIHdpdGggeW91ciBpZGVudGl0eT86CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cw--279369ace44ed4ba1137444c001224c933b254aa\nhello: world\nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://specs.openid.net/auth/2.0/identifier_select im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRedirected to http://localhost:3010/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A18%3A11ZrhmBhZ&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=VeJwgcmZ8yfGmhLFDsQnLFt4E%2FA%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\nCompleted 302 Found in 14ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A18%3A11ZrhmBhZ&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=VeJwgcmZ8yfGmhLFDsQnLFt4E%2FA%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"VeJwgcmZ8yfGmhLFDsQnLFt4E/A=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T13:18:11ZrhmBhZ\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.claimed_id\"=>\"http://localhost:3009/user/marcel\"}\n\n\nStarted GET \"/user/marcel\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/user/marcel\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.7ms)\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 42ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (0.8ms)\nCompleted 200 OK in 7ms (Views: 6.8ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:18:11 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/rails.png\" for 127.0.0.1 at Sun Mar 31 06:24:32 -0700 2013\n\nNameError (undefined local variable or method `map' for #<ActionDispatch::Routing::Mapper:0x10347ebc8>):\n  config/routes.rb:2\n  config/routes.rb:1\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.7ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.1ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (18.2ms)\n\n\nStarted GET \"/\" for 127.0.0.1 at Sun Mar 31 06:24:41 -0700 2013\n\nActionController::RoutingError (No route matches [GET] \"/\"):\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/routing_error.erb within rescues/layout (0.6ms)\nConnecting to database specified by database.yml\nConnecting to database specified by database.yml\nConnecting to database specified by database.yml\nConnecting to database specified by database.yml\n\n\nStarted GET \"/\" for 127.0.0.1 at Sun Mar 31 06:28:03 -0700 2013\n\nActionDispatch::Session::SessionRestoreError (Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: uninitialized constant OpenID [NameError])\n):\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:64:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/cookie_store.rb:48:in `unpacked_cookie_data'\n  rack (1.4.5) lib/rack/session/cookie.rb:107:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:57:in `stale_session_check!'\n  actionpack (3.2.13) lib/action_dispatch/middleware/session/abstract_store.rb:53:in `extract_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:43:in `load_session_id!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:32:in `[]'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:267:in `current_session_id'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:273:in `session_exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `send'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:107:in `exists?'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:127:in `load_for_read!'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:64:in `key?'\n  actionpack (3.2.13) lib/action_dispatch/middleware/flash.rb:258:in `call'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:210:in `context'\n  rack (1.4.5) lib/rack/session/abstract/id.rb:205:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/cookies.rb:341:in `call'\n  activerecord (3.2.13) lib/active_record/query_cache.rb:64:in `call'\n  activerecord (3.2.13) lib/active_record/connection_adapters/abstract/connection_pool.rb:479:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:28:in `call'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `_run__397314981__call__4__callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:405:in `__run_callback'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:385:in `_run_call_callbacks'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `send'\n  activesupport (3.2.13) lib/active_support/callbacks.rb:81:in `run_callbacks'\n  actionpack (3.2.13) lib/action_dispatch/middleware/callbacks.rb:27:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/reloader.rb:65:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/remote_ip.rb:31:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/debug_exceptions.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/show_exceptions.rb:56:in `call'\n  railties (3.2.13) lib/rails/rack/logger.rb:32:in `call_app'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  activesupport (3.2.13) lib/active_support/tagged_logging.rb:22:in `tagged'\n  railties (3.2.13) lib/rails/rack/logger.rb:16:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/request_id.rb:22:in `call'\n  rack (1.4.5) lib/rack/methodoverride.rb:21:in `call'\n  rack (1.4.5) lib/rack/runtime.rb:17:in `call'\n  activesupport (3.2.13) lib/active_support/cache/strategy/local_cache.rb:72:in `call'\n  rack (1.4.5) lib/rack/lock.rb:15:in `call'\n  actionpack (3.2.13) lib/action_dispatch/middleware/static.rb:63:in `call'\n  railties (3.2.13) lib/rails/engine.rb:479:in `call'\n  railties (3.2.13) lib/rails/application.rb:223:in `call'\n  rack (1.4.5) lib/rack/content_length.rb:14:in `call'\n  railties (3.2.13) lib/rails/rack/log_tailer.rb:17:in `call'\n  rack (1.4.5) lib/rack/handler/webrick.rb:59:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'\n  /opt/local/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:95:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `each'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:92:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:23:in `start'\n  /opt/local/lib/ruby/1.8/webrick/server.rb:82:in `start'\n  rack (1.4.5) lib/rack/handler/webrick.rb:13:in `run'\n  rack (1.4.5) lib/rack/server.rb:268:in `start'\n  railties (3.2.13) lib/rails/commands/server.rb:70:in `start'\n  railties (3.2.13) lib/rails/commands.rb:55\n  railties (3.2.13) lib/rails/commands.rb:50:in `tap'\n  railties (3.2.13) lib/rails/commands.rb:50\n  script/rails:6:in `require'\n  script/rails:6\n\n\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.6ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (2.3ms)\n  Rendered /Users/marcel/.gem/ruby/1.8/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (27.7ms)\n\n\nStarted GET \"/\" for 127.0.0.1 at Sun Mar 31 06:28:16 -0700 2013\nProcessing by LoginController#index as HTML\n  Rendered login/index.html.erb within layouts/server (1.4ms)\nCompleted 200 OK in 188ms (Views: 169.7ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/user/marcel\" for 127.0.0.1 at Sun Mar 31 06:28:31 -0700 2013\nProcessing by ServerController#user_page as HTML\n  Parameters: {\"username\"=>\"marcel\"}\n  Rendered text template (0.0ms)\nCompleted 200 OK in 32ms (Views: 29.7ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/user/marcel\" for 127.0.0.1 at Sun Mar 31 06:28:53 -0700 2013\nProcessing by ServerController#user_page as HTML\n  Parameters: {\"username\"=>\"marcel\"}\n  Rendered text template (0.0ms)\nCompleted 200 OK in 3ms (Views: 1.2ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:29:00 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/consumer/start?openid_identifier=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel\" for 127.0.0.1 at Sun Mar 31 06:29:10 -0700 2013\nProcessing by ConsumerController#start as HTML\n  Parameters: {\"openid_identifier\"=>\"http://localhost:3009/user/marcel\"}\n\n\nStarted GET \"/user/marcel\" for 127.0.0.1 at Sun Mar 31 06:29:10 -0700 2013\nProcessing by ServerController#user_page as application/xrds+xml\n  Parameters: {\"username\"=>\"marcel\"}\n  Rendered text template (0.0ms)\nCompleted 200 OK in 1ms (Views: 0.5ms | ActiveRecord: 0.0ms)\nRedirected to http://localhost:3009/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\nCompleted 302 Found in 157ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/server?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%3A3010%2Fconsumer&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete\" for 127.0.0.1 at Sun Mar 31 06:29:10 -0700 2013\nProcessing by ServerController#index as HTML\n  Parameters: {\"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.mode\"=>\"checkid_setup\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/marcel\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.realm\"=>\"http://localhost:3010/consumer\", \"openid.claimed_id\"=>\"http://localhost:3009/user/marcel\"}\nSetting last_oidreq to <OpenID::Server::CheckIDRequest id:http://localhost:3009/user/marcel im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRendering decide template!\n  Rendered server/decide.html.erb within layouts/server (1.2ms)\nCompleted 200 OK in 7ms (Views: 3.8ms | ActiveRecord: 0.0ms)\n\n\nStarted POST \"/server/decision\" for 127.0.0.1 at Sun Mar 31 06:29:15 -0700 2013\nProcessing by ServerController#decision as HTML\n  Parameters: {\"authenticity_token\"=>\"5AZtwrdNxknr4gvSZ1lKzmmJqX+iJdHapyEW/jkB90M=\", \"yes\"=>\"yes\"}\nsession_id: a0903f7e5c643a4e0ee0334fcb19b4c6\nsession_id cookie: BAh7CyIuT3BlbklEOjpDb25zdW1lcjo6bGFzdF9yZXF1ZXN0ZWRfZW5kcG9pbnRvOiJPcGVuSUQ6Ok9wZW5JRFNlcnZpY2VFbmRwb2ludAw6DkBsb2NhbF9pZDA6GEBkaXNwbGF5X2lkZW50aWZpZXIwOg9AdHlwZV91cmlzWwgiLGh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wL3NpZ25vbiIhaHR0cDovL29wZW5pZC5uZXQvc2lnbm9uLzEuMCIfaHR0cDovL29wZW5pZC5uZXQvc3JlZy8xLjA6EEB1c2VkX3lhZGlzVDoQQHNlcnZlcl91cmwiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXI6EkBjYW5vbmljYWxfaWQwOhBAY2xhaW1lZF9pZCImaHR0cDovL2xvY2FsaG9zdDozMDA5L3VzZXIvbWFyY2VsIhBfY3NyZl90b2tlbiIxNUFadHdyZE54a25yNGd2U1oxbEt6bW1KcVgraUpkSGFweUVXL2prQjkwTT0iD3Nlc3Npb25faWQiJWEwOTAzZjdlNWM2NDNhNGUwZWUwMzM0ZmNiMTliNGM2Ij1PcGVuSUQ6OkNvbnN1bWVyOjpEaXNjb3ZlcmVkU2VydmljZXM6Ok9wZW5JRDo6Q29uc3VtZXI6Om86KU9wZW5JRDo6Q29uc3VtZXI6OkRpc2NvdmVyZWRTZXJ2aWNlcwk6DkBzZXJ2aWNlc1sAOg1AY3VycmVudEAHOg9AeWFkaXNfdXJsIiZodHRwOi8vbG9jYWxob3N0OjMwMDkvdXNlci9tYXJjZWw6EkBzdGFydGluZ191cmwiJmh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS91c2VyL21hcmNlbCIQbGFzdF9vaWRyZXFvOiNPcGVuSUQ6OlNlcnZlcjo6Q2hlY2tJRFJlcXVlc3QOOhFAb3BfZW5kcG9pbnQiIWh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS9zZXJ2ZXI6EEB0cnVzdF9yb290IiNodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXI6DUBtZXNzYWdlbzoUT3BlbklEOjpNZXNzYWdlCDoTQG9wZW5pZF9uc191cmkiJWh0dHA6Ly9zcGVjcy5vcGVuaWQubmV0L2F1dGgvMi4wOhBAbmFtZXNwYWNlc286GU9wZW5JRDo6TmFtZXNwYWNlTWFwCDoYQG5hbWVzcGFjZV90b19hbGlhc3sGIiVodHRwOi8vc3BlY3Mub3BlbmlkLm5ldC9hdXRoLzIuMDoTbnVsbF9uYW1lc3BhY2U6GEBhbGlhc190b19uYW1lc3BhY2V7BjsbQBw6GUBpbXBsaWNpdF9uYW1lc3BhY2VzWwA6CkBhcmdzew1bB0AcIgltb2RlIhJjaGVja2lkX3NldHVwWwdAHCIPY2xhaW1lZF9pZCImaHR0cDovL2xvY2FsaG9zdDozMDA5L3VzZXIvbWFyY2VsWwc6E2JhcmVfbmFtZXNwYWNlIg9jb250cm9sbGVyIgtzZXJ2ZXJbBzsfIgthY3Rpb24iCmluZGV4WwdAHCIRYXNzb2NfaGFuZGxlIiR7SE1BQy1TSEExfXs1MTU4MmQ5NH17Y2RrbzFnPT19WwdAHCIOcmV0dXJuX3RvIixodHRwOi8vbG9jYWxob3N0OjMwMTAvY29uc3VtZXIvY29tcGxldGVbB0AcIgpyZWFsbUAaWwdAHCINaWRlbnRpdHkiJmh0dHA6Ly9sb2NhbGhvc3Q6MzAwOS91c2VyL21hcmNlbDoPQHJldHVybl90b0A0OhJAYXNzb2NfaGFuZGxlQDE6D0BpbW1lZGlhdGVGOg5AaWRlbnRpdHlAOToKQG1vZGUiEmNoZWNraWRfc2V0dXA7DEAoIgpmbGFzaG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToNQGZsYXNoZXN7BjoLbm90aWNlIi9EbyB5b3UgdHJ1c3QgdGhpcyBzaXRlIHdpdGggeW91ciBpZGVudGl0eT86CkB1c2VkbzoIU2V0BjoKQGhhc2h7ADoMQGNsb3NlZEY6CUBub3cw--12967a41bad9d491f50492d6caaaef2b33acb4f6\nhello: \nlast_oidreq: <OpenID::Server::CheckIDRequest id:http://localhost:3009/user/marcel im:false tr:http://localhost:3010/consumer ah:{HMAC-SHA1}{51582d94}{cdko1g==}>\nRedirected to http://localhost:3010/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A29%3A15Zsn1mhg&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=XUa1u9njJyD6Ji1pcDegIvvLCpI%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\nCompleted 302 Found in 13ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer/complete?openid.assoc_handle=%7BHMAC-SHA1%7D%7B51582d94%7D%7Bcdko1g%3D%3D%7D&openid.claimed_id=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.identity=http%3A%2F%2Flocalhost%3A3009%2Fuser%2Fmarcel&openid.mode=id_res&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.op_endpoint=http%3A%2F%2Flocalhost%3A3009%2Fserver&openid.response_nonce=2013-03-31T13%3A29%3A15Zsn1mhg&openid.return_to=http%3A%2F%2Flocalhost%3A3010%2Fconsumer%2Fcomplete&openid.sig=XUa1u9njJyD6Ji1pcDegIvvLCpI%3D&openid.signed=assoc_handle%2Cclaimed_id%2Cidentity%2Cmode%2Cns%2Cop_endpoint%2Cresponse_nonce%2Creturn_to%2Csigned\" for 127.0.0.1 at Sun Mar 31 06:29:15 -0700 2013\nProcessing by ConsumerController#complete as HTML\n  Parameters: {\"openid.sig\"=>\"XUa1u9njJyD6Ji1pcDegIvvLCpI=\", \"openid.return_to\"=>\"http://localhost:3010/consumer/complete\", \"openid.op_endpoint\"=>\"http://localhost:3009/server\", \"openid.mode\"=>\"id_res\", \"openid.response_nonce\"=>\"2013-03-31T13:29:15Zsn1mhg\", \"openid.ns\"=>\"http://specs.openid.net/auth/2.0\", \"openid.identity\"=>\"http://localhost:3009/user/marcel\", \"openid.signed\"=>\"assoc_handle,claimed_id,identity,mode,ns,op_endpoint,response_nonce,return_to,signed\", \"openid.assoc_handle\"=>\"{HMAC-SHA1}{51582d94}{cdko1g==}\", \"openid.claimed_id\"=>\"http://localhost:3009/user/marcel\"}\nRedirected to http://localhost:3010/consumer\nCompleted 302 Found in 12ms (ActiveRecord: 0.0ms)\n\n\nStarted GET \"/consumer\" for 127.0.0.1 at Sun Mar 31 06:29:15 -0700 2013\nProcessing by ConsumerController#index as HTML\n  Rendered consumer/index.html.erb within layouts/application (1.4ms)\nCompleted 200 OK in 9ms (Views: 8.3ms | ActiveRecord: 0.0ms)\n\n\nStarted GET \"/assets/application.css?body=1\" for 127.0.0.1 at Sun Mar 31 06:29:15 -0700 2013\nServed asset /application.css - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/application.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:29:15 -0700 2013\nServed asset /application.js - 304 Not Modified (0ms)\n\n\nStarted GET \"/assets/jquery.js?body=1\" for 127.0.0.1 at Sun Mar 31 06:29:15 -0700 2013\nServed asset /jquery.js - 304 Not Modified (0ms)\n"
  },
  {
    "path": "examples/rails_openid/public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <h1>The page you were looking for doesn't exist.</h1>\n    <p>You may have mistyped the address or the page may have moved.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "examples/rails_openid/public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <h1>The change you wanted was rejected.</h1>\n    <p>Maybe you tried to change something you didn't have access to.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "examples/rails_openid/public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <style type=\"text/css\">\n    body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }\n    div.dialog {\n      width: 25em;\n      padding: 0 4em;\n      margin: 4em auto 0 auto;\n      border: 1px solid #ccc;\n      border-right-color: #999;\n      border-bottom-color: #999;\n    }\n    h1 { font-size: 100%; color: #f00; line-height: 1.5em; }\n  </style>\n</head>\n\n<body>\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <h1>We're sorry, but something went wrong.</h1>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "examples/rails_openid/public/dispatch.cgi",
    "content": "#!/usr/bin/ruby1.8\n\n#!/usr/local/bin/ruby\n\nrequire File.dirname(__FILE__) + \"/../config/environment\" unless defined?(RAILS_ROOT)\n\n# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:\n# \"/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher\" -- otherwise performance is severely impaired\nrequire \"dispatcher\"\n\nADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)\nDispatcher.dispatch"
  },
  {
    "path": "examples/rails_openid/public/dispatch.fcgi",
    "content": "#!/usr/bin/ruby1.8\n\n#!/usr/local/bin/ruby\n#\n# You may specify the path to the FastCGI crash log (a log of unhandled\n# exceptions which forced the FastCGI instance to exit, great for debugging)\n# and the number of requests to process before running garbage collection.\n#\n# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log\n# and the GC period is nil (turned off).  A reasonable number of requests\n# could range from 10-100 depending on the memory footprint of your app.\n#\n# Example:\n#   # Default log path, normal GC behavior.\n#   RailsFCGIHandler.process!\n#\n#   # Default log path, 50 requests between GC.\n#   RailsFCGIHandler.process! nil, 50\n#\n#   # Custom log path, normal GC behavior.\n#   RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'\n#\nrequire File.dirname(__FILE__) + \"/../config/environment\"\nrequire 'fcgi_handler'\n\nRailsFCGIHandler.process!\n"
  },
  {
    "path": "examples/rails_openid/public/dispatch.rb",
    "content": "#!/usr/bin/ruby1.8\n\n#!/usr/local/bin/ruby\n\nrequire File.dirname(__FILE__) + \"/../config/environment\" unless defined?(RAILS_ROOT)\n\n# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:\n# \"/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher\" -- otherwise performance is severely impaired\nrequire \"dispatcher\"\n\nADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)\nDispatcher.dispatch"
  },
  {
    "path": "examples/rails_openid/public/javascripts/application.js",
    "content": "// Place your application-specific JavaScript functions and classes here\n// This file is automatically included by javascript_include_tag :defaults\n"
  },
  {
    "path": "examples/rails_openid/public/javascripts/controls.js",
    "content": "// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n//           (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)\n//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)\n// Contributors:\n//  Richard Livsey\n//  Rahul Bhargava\n//  Rob Wills\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\n// Autocompleter.Base handles all the autocompletion functionality\n// that's independent of the data source for autocompletion. This\n// includes drawing the autocompletion menu, observing keyboard\n// and mouse events, and similar.\n//\n// Specific autocompleters need to provide, at the very least,\n// a getUpdatedChoices function that will be invoked every time\n// the text inside the monitored textbox changes. This method\n// should get the text for which to provide autocompletion by\n// invoking this.getToken(), NOT by directly accessing\n// this.element.value. This is to allow incremental tokenized\n// autocompletion. Specific auto-completion logic (AJAX, etc)\n// belongs in getUpdatedChoices.\n//\n// Tokenized incremental autocompletion is enabled automatically\n// when an autocompleter is instantiated with the 'tokens' option\n// in the options parameter, e.g.:\n// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });\n// will incrementally autocomplete with a comma as the token.\n// Additionally, ',' in the above example can be replaced with\n// a token array, e.g. { tokens: [',', '\\n'] } which\n// enables autocompletion on multiple tokens. This is most\n// useful when one of the tokens is \\n (a newline), as it\n// allows smart autocompletion after linebreaks.\n\nif(typeof Effect == 'undefined')\n  throw(\"controls.js requires including script.aculo.us' effects.js library\");\n\nvar Autocompleter = { };\nAutocompleter.Base = Class.create({\n  baseInitialize: function(element, update, options) {\n    element          = $(element);\n    this.element     = element;\n    this.update      = $(update);\n    this.hasFocus    = false;\n    this.changed     = false;\n    this.active      = false;\n    this.index       = 0;\n    this.entryCount  = 0;\n    this.oldElementValue = this.element.value;\n\n    if(this.setOptions)\n      this.setOptions(options);\n    else\n      this.options = options || { };\n\n    this.options.paramName    = this.options.paramName || this.element.name;\n    this.options.tokens       = this.options.tokens || [];\n    this.options.frequency    = this.options.frequency || 0.4;\n    this.options.minChars     = this.options.minChars || 1;\n    this.options.onShow       = this.options.onShow ||\n      function(element, update){\n        if(!update.style.position || update.style.position=='absolute') {\n          update.style.position = 'absolute';\n          Position.clone(element, update, {\n            setHeight: false,\n            offsetTop: element.offsetHeight\n          });\n        }\n        Effect.Appear(update,{duration:0.15});\n      };\n    this.options.onHide = this.options.onHide ||\n      function(element, update){ new Effect.Fade(update,{duration:0.15}) };\n\n    if(typeof(this.options.tokens) == 'string')\n      this.options.tokens = new Array(this.options.tokens);\n    // Force carriage returns as token delimiters anyway\n    if (!this.options.tokens.include('\\n'))\n      this.options.tokens.push('\\n');\n\n    this.observer = null;\n\n    this.element.setAttribute('autocomplete','off');\n\n    Element.hide(this.update);\n\n    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));\n    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));\n  },\n\n  show: function() {\n    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);\n    if(!this.iefix &&\n      (Prototype.Browser.IE) &&\n      (Element.getStyle(this.update, 'position')=='absolute')) {\n      new Insertion.After(this.update,\n       '<iframe id=\"' + this.update.id + '_iefix\" '+\n       'style=\"display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);\" ' +\n       'src=\"javascript:false;\" frameborder=\"0\" scrolling=\"no\"></iframe>');\n      this.iefix = $(this.update.id+'_iefix');\n    }\n    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);\n  },\n\n  fixIEOverlapping: function() {\n    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});\n    this.iefix.style.zIndex = 1;\n    this.update.style.zIndex = 2;\n    Element.show(this.iefix);\n  },\n\n  hide: function() {\n    this.stopIndicator();\n    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);\n    if(this.iefix) Element.hide(this.iefix);\n  },\n\n  startIndicator: function() {\n    if(this.options.indicator) Element.show(this.options.indicator);\n  },\n\n  stopIndicator: function() {\n    if(this.options.indicator) Element.hide(this.options.indicator);\n  },\n\n  onKeyPress: function(event) {\n    if(this.active)\n      switch(event.keyCode) {\n       case Event.KEY_TAB:\n       case Event.KEY_RETURN:\n         this.selectEntry();\n         Event.stop(event);\n       case Event.KEY_ESC:\n         this.hide();\n         this.active = false;\n         Event.stop(event);\n         return;\n       case Event.KEY_LEFT:\n       case Event.KEY_RIGHT:\n         return;\n       case Event.KEY_UP:\n         this.markPrevious();\n         this.render();\n         Event.stop(event);\n         return;\n       case Event.KEY_DOWN:\n         this.markNext();\n         this.render();\n         Event.stop(event);\n         return;\n      }\n     else\n       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||\n         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;\n\n    this.changed = true;\n    this.hasFocus = true;\n\n    if(this.observer) clearTimeout(this.observer);\n      this.observer =\n        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);\n  },\n\n  activate: function() {\n    this.changed = false;\n    this.hasFocus = true;\n    this.getUpdatedChoices();\n  },\n\n  onHover: function(event) {\n    var element = Event.findElement(event, 'LI');\n    if(this.index != element.autocompleteIndex)\n    {\n        this.index = element.autocompleteIndex;\n        this.render();\n    }\n    Event.stop(event);\n  },\n\n  onClick: function(event) {\n    var element = Event.findElement(event, 'LI');\n    this.index = element.autocompleteIndex;\n    this.selectEntry();\n    this.hide();\n  },\n\n  onBlur: function(event) {\n    // needed to make click events working\n    setTimeout(this.hide.bind(this), 250);\n    this.hasFocus = false;\n    this.active = false;\n  },\n\n  render: function() {\n    if(this.entryCount > 0) {\n      for (var i = 0; i < this.entryCount; i++)\n        this.index==i ?\n          Element.addClassName(this.getEntry(i),\"selected\") :\n          Element.removeClassName(this.getEntry(i),\"selected\");\n      if(this.hasFocus) {\n        this.show();\n        this.active = true;\n      }\n    } else {\n      this.active = false;\n      this.hide();\n    }\n  },\n\n  markPrevious: function() {\n    if(this.index > 0) this.index--;\n      else this.index = this.entryCount-1;\n    this.getEntry(this.index).scrollIntoView(true);\n  },\n\n  markNext: function() {\n    if(this.index < this.entryCount-1) this.index++;\n      else this.index = 0;\n    this.getEntry(this.index).scrollIntoView(false);\n  },\n\n  getEntry: function(index) {\n    return this.update.firstChild.childNodes[index];\n  },\n\n  getCurrentEntry: function() {\n    return this.getEntry(this.index);\n  },\n\n  selectEntry: function() {\n    this.active = false;\n    this.updateElement(this.getCurrentEntry());\n  },\n\n  updateElement: function(selectedElement) {\n    if (this.options.updateElement) {\n      this.options.updateElement(selectedElement);\n      return;\n    }\n    var value = '';\n    if (this.options.select) {\n      var nodes = $(selectedElement).select('.' + this.options.select) || [];\n      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);\n    } else\n      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');\n\n    var bounds = this.getTokenBounds();\n    if (bounds[0] != -1) {\n      var newValue = this.element.value.substr(0, bounds[0]);\n      var whitespace = this.element.value.substr(bounds[0]).match(/^\\s+/);\n      if (whitespace)\n        newValue += whitespace[0];\n      this.element.value = newValue + value + this.element.value.substr(bounds[1]);\n    } else {\n      this.element.value = value;\n    }\n    this.oldElementValue = this.element.value;\n    this.element.focus();\n\n    if (this.options.afterUpdateElement)\n      this.options.afterUpdateElement(this.element, selectedElement);\n  },\n\n  updateChoices: function(choices) {\n    if(!this.changed && this.hasFocus) {\n      this.update.innerHTML = choices;\n      Element.cleanWhitespace(this.update);\n      Element.cleanWhitespace(this.update.down());\n\n      if(this.update.firstChild && this.update.down().childNodes) {\n        this.entryCount =\n          this.update.down().childNodes.length;\n        for (var i = 0; i < this.entryCount; i++) {\n          var entry = this.getEntry(i);\n          entry.autocompleteIndex = i;\n          this.addObservers(entry);\n        }\n      } else {\n        this.entryCount = 0;\n      }\n\n      this.stopIndicator();\n      this.index = 0;\n\n      if(this.entryCount==1 && this.options.autoSelect) {\n        this.selectEntry();\n        this.hide();\n      } else {\n        this.render();\n      }\n    }\n  },\n\n  addObservers: function(element) {\n    Event.observe(element, \"mouseover\", this.onHover.bindAsEventListener(this));\n    Event.observe(element, \"click\", this.onClick.bindAsEventListener(this));\n  },\n\n  onObserverEvent: function() {\n    this.changed = false;\n    this.tokenBounds = null;\n    if(this.getToken().length>=this.options.minChars) {\n      this.getUpdatedChoices();\n    } else {\n      this.active = false;\n      this.hide();\n    }\n    this.oldElementValue = this.element.value;\n  },\n\n  getToken: function() {\n    var bounds = this.getTokenBounds();\n    return this.element.value.substring(bounds[0], bounds[1]).strip();\n  },\n\n  getTokenBounds: function() {\n    if (null != this.tokenBounds) return this.tokenBounds;\n    var value = this.element.value;\n    if (value.strip().empty()) return [-1, 0];\n    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);\n    var offset = (diff == this.oldElementValue.length ? 1 : 0);\n    var prevTokenPos = -1, nextTokenPos = value.length;\n    var tp;\n    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {\n      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);\n      if (tp > prevTokenPos) prevTokenPos = tp;\n      tp = value.indexOf(this.options.tokens[index], diff + offset);\n      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;\n    }\n    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);\n  }\n});\n\nAutocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {\n  var boundary = Math.min(newS.length, oldS.length);\n  for (var index = 0; index < boundary; ++index)\n    if (newS[index] != oldS[index])\n      return index;\n  return boundary;\n};\n\nAjax.Autocompleter = Class.create(Autocompleter.Base, {\n  initialize: function(element, update, url, options) {\n    this.baseInitialize(element, update, options);\n    this.options.asynchronous  = true;\n    this.options.onComplete    = this.onComplete.bind(this);\n    this.options.defaultParams = this.options.parameters || null;\n    this.url                   = url;\n  },\n\n  getUpdatedChoices: function() {\n    this.startIndicator();\n\n    var entry = encodeURIComponent(this.options.paramName) + '=' +\n      encodeURIComponent(this.getToken());\n\n    this.options.parameters = this.options.callback ?\n      this.options.callback(this.element, entry) : entry;\n\n    if(this.options.defaultParams)\n      this.options.parameters += '&' + this.options.defaultParams;\n\n    new Ajax.Request(this.url, this.options);\n  },\n\n  onComplete: function(request) {\n    this.updateChoices(request.responseText);\n  }\n});\n\n// The local array autocompleter. Used when you'd prefer to\n// inject an array of autocompletion options into the page, rather\n// than sending out Ajax queries, which can be quite slow sometimes.\n//\n// The constructor takes four parameters. The first two are, as usual,\n// the id of the monitored textbox, and id of the autocompletion menu.\n// The third is the array you want to autocomplete from, and the fourth\n// is the options block.\n//\n// Extra local autocompletion options:\n// - choices - How many autocompletion choices to offer\n//\n// - partialSearch - If false, the autocompleter will match entered\n//                    text only at the beginning of strings in the\n//                    autocomplete array. Defaults to true, which will\n//                    match text at the beginning of any *word* in the\n//                    strings in the autocomplete array. If you want to\n//                    search anywhere in the string, additionally set\n//                    the option fullSearch to true (default: off).\n//\n// - fullSsearch - Search anywhere in autocomplete array strings.\n//\n// - partialChars - How many characters to enter before triggering\n//                   a partial match (unlike minChars, which defines\n//                   how many characters are required to do any match\n//                   at all). Defaults to 2.\n//\n// - ignoreCase - Whether to ignore case when autocompleting.\n//                 Defaults to true.\n//\n// It's possible to pass in a custom function as the 'selector'\n// option, if you prefer to write your own autocompletion logic.\n// In that case, the other options above will not apply unless\n// you support them.\n\nAutocompleter.Local = Class.create(Autocompleter.Base, {\n  initialize: function(element, update, array, options) {\n    this.baseInitialize(element, update, options);\n    this.options.array = array;\n  },\n\n  getUpdatedChoices: function() {\n    this.updateChoices(this.options.selector(this));\n  },\n\n  setOptions: function(options) {\n    this.options = Object.extend({\n      choices: 10,\n      partialSearch: true,\n      partialChars: 2,\n      ignoreCase: true,\n      fullSearch: false,\n      selector: function(instance) {\n        var ret       = []; // Beginning matches\n        var partial   = []; // Inside matches\n        var entry     = instance.getToken();\n        var count     = 0;\n\n        for (var i = 0; i < instance.options.array.length &&\n          ret.length < instance.options.choices ; i++) {\n\n          var elem = instance.options.array[i];\n          var foundPos = instance.options.ignoreCase ?\n            elem.toLowerCase().indexOf(entry.toLowerCase()) :\n            elem.indexOf(entry);\n\n          while (foundPos != -1) {\n            if (foundPos == 0 && elem.length != entry.length) {\n              ret.push(\"<li><strong>\" + elem.substr(0, entry.length) + \"</strong>\" +\n                elem.substr(entry.length) + \"</li>\");\n              break;\n            } else if (entry.length >= instance.options.partialChars &&\n              instance.options.partialSearch && foundPos != -1) {\n              if (instance.options.fullSearch || /\\s/.test(elem.substr(foundPos-1,1))) {\n                partial.push(\"<li>\" + elem.substr(0, foundPos) + \"<strong>\" +\n                  elem.substr(foundPos, entry.length) + \"</strong>\" + elem.substr(\n                  foundPos + entry.length) + \"</li>\");\n                break;\n              }\n            }\n\n            foundPos = instance.options.ignoreCase ?\n              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :\n              elem.indexOf(entry, foundPos + 1);\n\n          }\n        }\n        if (partial.length)\n          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));\n        return \"<ul>\" + ret.join('') + \"</ul>\";\n      }\n    }, options || { });\n  }\n});\n\n// AJAX in-place editor and collection editor\n// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).\n\n// Use this if you notice weird scrolling problems on some browsers,\n// the DOM might be a bit confused when this gets called so do this\n// waits 1 ms (with setTimeout) until it does the activation\nField.scrollFreeActivate = function(field) {\n  setTimeout(function() {\n    Field.activate(field);\n  }, 1);\n};\n\nAjax.InPlaceEditor = Class.create({\n  initialize: function(element, url, options) {\n    this.url = url;\n    this.element = element = $(element);\n    this.prepareOptions();\n    this._controls = { };\n    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!\n    Object.extend(this.options, options || { });\n    if (!this.options.formId && this.element.id) {\n      this.options.formId = this.element.id + '-inplaceeditor';\n      if ($(this.options.formId))\n        this.options.formId = '';\n    }\n    if (this.options.externalControl)\n      this.options.externalControl = $(this.options.externalControl);\n    if (!this.options.externalControl)\n      this.options.externalControlOnly = false;\n    this._originalBackground = this.element.getStyle('background-color') || 'transparent';\n    this.element.title = this.options.clickToEditText;\n    this._boundCancelHandler = this.handleFormCancellation.bind(this);\n    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);\n    this._boundFailureHandler = this.handleAJAXFailure.bind(this);\n    this._boundSubmitHandler = this.handleFormSubmission.bind(this);\n    this._boundWrapperHandler = this.wrapUp.bind(this);\n    this.registerListeners();\n  },\n  checkForEscapeOrReturn: function(e) {\n    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;\n    if (Event.KEY_ESC == e.keyCode)\n      this.handleFormCancellation(e);\n    else if (Event.KEY_RETURN == e.keyCode)\n      this.handleFormSubmission(e);\n  },\n  createControl: function(mode, handler, extraClasses) {\n    var control = this.options[mode + 'Control'];\n    var text = this.options[mode + 'Text'];\n    if ('button' == control) {\n      var btn = document.createElement('input');\n      btn.type = 'submit';\n      btn.value = text;\n      btn.className = 'editor_' + mode + '_button';\n      if ('cancel' == mode)\n        btn.onclick = this._boundCancelHandler;\n      this._form.appendChild(btn);\n      this._controls[mode] = btn;\n    } else if ('link' == control) {\n      var link = document.createElement('a');\n      link.href = '#';\n      link.appendChild(document.createTextNode(text));\n      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;\n      link.className = 'editor_' + mode + '_link';\n      if (extraClasses)\n        link.className += ' ' + extraClasses;\n      this._form.appendChild(link);\n      this._controls[mode] = link;\n    }\n  },\n  createEditField: function() {\n    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());\n    var fld;\n    if (1 >= this.options.rows && !/\\r|\\n/.test(this.getText())) {\n      fld = document.createElement('input');\n      fld.type = 'text';\n      var size = this.options.size || this.options.cols || 0;\n      if (0 < size) fld.size = size;\n    } else {\n      fld = document.createElement('textarea');\n      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);\n      fld.cols = this.options.cols || 40;\n    }\n    fld.name = this.options.paramName;\n    fld.value = text; // No HTML breaks conversion anymore\n    fld.className = 'editor_field';\n    if (this.options.submitOnBlur)\n      fld.onblur = this._boundSubmitHandler;\n    this._controls.editor = fld;\n    if (this.options.loadTextURL)\n      this.loadExternalText();\n    this._form.appendChild(this._controls.editor);\n  },\n  createForm: function() {\n    var ipe = this;\n    function addText(mode, condition) {\n      var text = ipe.options['text' + mode + 'Controls'];\n      if (!text || condition === false) return;\n      ipe._form.appendChild(document.createTextNode(text));\n    };\n    this._form = $(document.createElement('form'));\n    this._form.id = this.options.formId;\n    this._form.addClassName(this.options.formClassName);\n    this._form.onsubmit = this._boundSubmitHandler;\n    this.createEditField();\n    if ('textarea' == this._controls.editor.tagName.toLowerCase())\n      this._form.appendChild(document.createElement('br'));\n    if (this.options.onFormCustomization)\n      this.options.onFormCustomization(this, this._form);\n    addText('Before', this.options.okControl || this.options.cancelControl);\n    this.createControl('ok', this._boundSubmitHandler);\n    addText('Between', this.options.okControl && this.options.cancelControl);\n    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');\n    addText('After', this.options.okControl || this.options.cancelControl);\n  },\n  destroy: function() {\n    if (this._oldInnerHTML)\n      this.element.innerHTML = this._oldInnerHTML;\n    this.leaveEditMode();\n    this.unregisterListeners();\n  },\n  enterEditMode: function(e) {\n    if (this._saving || this._editing) return;\n    this._editing = true;\n    this.triggerCallback('onEnterEditMode');\n    if (this.options.externalControl)\n      this.options.externalControl.hide();\n    this.element.hide();\n    this.createForm();\n    this.element.parentNode.insertBefore(this._form, this.element);\n    if (!this.options.loadTextURL)\n      this.postProcessEditField();\n    if (e) Event.stop(e);\n  },\n  enterHover: function(e) {\n    if (this.options.hoverClassName)\n      this.element.addClassName(this.options.hoverClassName);\n    if (this._saving) return;\n    this.triggerCallback('onEnterHover');\n  },\n  getText: function() {\n    return this.element.innerHTML.unescapeHTML();\n  },\n  handleAJAXFailure: function(transport) {\n    this.triggerCallback('onFailure', transport);\n    if (this._oldInnerHTML) {\n      this.element.innerHTML = this._oldInnerHTML;\n      this._oldInnerHTML = null;\n    }\n  },\n  handleFormCancellation: function(e) {\n    this.wrapUp();\n    if (e) Event.stop(e);\n  },\n  handleFormSubmission: function(e) {\n    var form = this._form;\n    var value = $F(this._controls.editor);\n    this.prepareSubmission();\n    var params = this.options.callback(form, value) || '';\n    if (Object.isString(params))\n      params = params.toQueryParams();\n    params.editorId = this.element.id;\n    if (this.options.htmlResponse) {\n      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);\n      Object.extend(options, {\n        parameters: params,\n        onComplete: this._boundWrapperHandler,\n        onFailure: this._boundFailureHandler\n      });\n      new Ajax.Updater({ success: this.element }, this.url, options);\n    } else {\n      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n      Object.extend(options, {\n        parameters: params,\n        onComplete: this._boundWrapperHandler,\n        onFailure: this._boundFailureHandler\n      });\n      new Ajax.Request(this.url, options);\n    }\n    if (e) Event.stop(e);\n  },\n  leaveEditMode: function() {\n    this.element.removeClassName(this.options.savingClassName);\n    this.removeForm();\n    this.leaveHover();\n    this.element.style.backgroundColor = this._originalBackground;\n    this.element.show();\n    if (this.options.externalControl)\n      this.options.externalControl.show();\n    this._saving = false;\n    this._editing = false;\n    this._oldInnerHTML = null;\n    this.triggerCallback('onLeaveEditMode');\n  },\n  leaveHover: function(e) {\n    if (this.options.hoverClassName)\n      this.element.removeClassName(this.options.hoverClassName);\n    if (this._saving) return;\n    this.triggerCallback('onLeaveHover');\n  },\n  loadExternalText: function() {\n    this._form.addClassName(this.options.loadingClassName);\n    this._controls.editor.disabled = true;\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        this._form.removeClassName(this.options.loadingClassName);\n        var text = transport.responseText;\n        if (this.options.stripLoadedTextTags)\n          text = text.stripTags();\n        this._controls.editor.value = text;\n        this._controls.editor.disabled = false;\n        this.postProcessEditField();\n      }.bind(this),\n      onFailure: this._boundFailureHandler\n    });\n    new Ajax.Request(this.options.loadTextURL, options);\n  },\n  postProcessEditField: function() {\n    var fpc = this.options.fieldPostCreation;\n    if (fpc)\n      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();\n  },\n  prepareOptions: function() {\n    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);\n    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);\n    [this._extraDefaultOptions].flatten().compact().each(function(defs) {\n      Object.extend(this.options, defs);\n    }.bind(this));\n  },\n  prepareSubmission: function() {\n    this._saving = true;\n    this.removeForm();\n    this.leaveHover();\n    this.showSaving();\n  },\n  registerListeners: function() {\n    this._listeners = { };\n    var listener;\n    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {\n      listener = this[pair.value].bind(this);\n      this._listeners[pair.key] = listener;\n      if (!this.options.externalControlOnly)\n        this.element.observe(pair.key, listener);\n      if (this.options.externalControl)\n        this.options.externalControl.observe(pair.key, listener);\n    }.bind(this));\n  },\n  removeForm: function() {\n    if (!this._form) return;\n    this._form.remove();\n    this._form = null;\n    this._controls = { };\n  },\n  showSaving: function() {\n    this._oldInnerHTML = this.element.innerHTML;\n    this.element.innerHTML = this.options.savingText;\n    this.element.addClassName(this.options.savingClassName);\n    this.element.style.backgroundColor = this._originalBackground;\n    this.element.show();\n  },\n  triggerCallback: function(cbName, arg) {\n    if ('function' == typeof this.options[cbName]) {\n      this.options[cbName](this, arg);\n    }\n  },\n  unregisterListeners: function() {\n    $H(this._listeners).each(function(pair) {\n      if (!this.options.externalControlOnly)\n        this.element.stopObserving(pair.key, pair.value);\n      if (this.options.externalControl)\n        this.options.externalControl.stopObserving(pair.key, pair.value);\n    }.bind(this));\n  },\n  wrapUp: function(transport) {\n    this.leaveEditMode();\n    // Can't use triggerCallback due to backward compatibility: requires\n    // binding + direct element\n    this._boundComplete(transport, this.element);\n  }\n});\n\nObject.extend(Ajax.InPlaceEditor.prototype, {\n  dispose: Ajax.InPlaceEditor.prototype.destroy\n});\n\nAjax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {\n  initialize: function($super, element, url, options) {\n    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;\n    $super(element, url, options);\n  },\n\n  createEditField: function() {\n    var list = document.createElement('select');\n    list.name = this.options.paramName;\n    list.size = 1;\n    this._controls.editor = list;\n    this._collection = this.options.collection || [];\n    if (this.options.loadCollectionURL)\n      this.loadCollection();\n    else\n      this.checkForExternalText();\n    this._form.appendChild(this._controls.editor);\n  },\n\n  loadCollection: function() {\n    this._form.addClassName(this.options.loadingClassName);\n    this.showLoadingText(this.options.loadingCollectionText);\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        var js = transport.responseText.strip();\n        if (!/^\\[.*\\]$/.test(js)) // TODO: improve sanity check\n          throw('Server returned an invalid collection representation.');\n        this._collection = eval(js);\n        this.checkForExternalText();\n      }.bind(this),\n      onFailure: this.onFailure\n    });\n    new Ajax.Request(this.options.loadCollectionURL, options);\n  },\n\n  showLoadingText: function(text) {\n    this._controls.editor.disabled = true;\n    var tempOption = this._controls.editor.firstChild;\n    if (!tempOption) {\n      tempOption = document.createElement('option');\n      tempOption.value = '';\n      this._controls.editor.appendChild(tempOption);\n      tempOption.selected = true;\n    }\n    tempOption.update((text || '').stripScripts().stripTags());\n  },\n\n  checkForExternalText: function() {\n    this._text = this.getText();\n    if (this.options.loadTextURL)\n      this.loadExternalText();\n    else\n      this.buildOptionList();\n  },\n\n  loadExternalText: function() {\n    this.showLoadingText(this.options.loadingText);\n    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);\n    Object.extend(options, {\n      parameters: 'editorId=' + encodeURIComponent(this.element.id),\n      onComplete: Prototype.emptyFunction,\n      onSuccess: function(transport) {\n        this._text = transport.responseText.strip();\n        this.buildOptionList();\n      }.bind(this),\n      onFailure: this.onFailure\n    });\n    new Ajax.Request(this.options.loadTextURL, options);\n  },\n\n  buildOptionList: function() {\n    this._form.removeClassName(this.options.loadingClassName);\n    this._collection = this._collection.map(function(entry) {\n      return 2 === entry.length ? entry : [entry, entry].flatten();\n    });\n    var marker = ('value' in this.options) ? this.options.value : this._text;\n    var textFound = this._collection.any(function(entry) {\n      return entry[0] == marker;\n    }.bind(this));\n    this._controls.editor.update('');\n    var option;\n    this._collection.each(function(entry, index) {\n      option = document.createElement('option');\n      option.value = entry[0];\n      option.selected = textFound ? entry[0] == marker : 0 == index;\n      option.appendChild(document.createTextNode(entry[1]));\n      this._controls.editor.appendChild(option);\n    }.bind(this));\n    this._controls.editor.disabled = false;\n    Field.scrollFreeActivate(this._controls.editor);\n  }\n});\n\n//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****\n//**** This only  exists for a while,  in order to  let ****\n//**** users adapt to  the new API.  Read up on the new ****\n//**** API and convert your code to it ASAP!            ****\n\nAjax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {\n  if (!options) return;\n  function fallback(name, expr) {\n    if (name in options || expr === undefined) return;\n    options[name] = expr;\n  };\n  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :\n    options.cancelLink == options.cancelButton == false ? false : undefined)));\n  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :\n    options.okLink == options.okButton == false ? false : undefined)));\n  fallback('highlightColor', options.highlightcolor);\n  fallback('highlightEndColor', options.highlightendcolor);\n};\n\nObject.extend(Ajax.InPlaceEditor, {\n  DefaultOptions: {\n    ajaxOptions: { },\n    autoRows: 3,                                // Use when multi-line w/ rows == 1\n    cancelControl: 'link',                      // 'link'|'button'|false\n    cancelText: 'cancel',\n    clickToEditText: 'Click to edit',\n    externalControl: null,                      // id|elt\n    externalControlOnly: false,\n    fieldPostCreation: 'activate',              // 'activate'|'focus'|false\n    formClassName: 'inplaceeditor-form',\n    formId: null,                               // id|elt\n    highlightColor: '#ffff99',\n    highlightEndColor: '#ffffff',\n    hoverClassName: '',\n    htmlResponse: true,\n    loadingClassName: 'inplaceeditor-loading',\n    loadingText: 'Loading...',\n    okControl: 'button',                        // 'link'|'button'|false\n    okText: 'ok',\n    paramName: 'value',\n    rows: 1,                                    // If 1 and multi-line, uses autoRows\n    savingClassName: 'inplaceeditor-saving',\n    savingText: 'Saving...',\n    size: 0,\n    stripLoadedTextTags: false,\n    submitOnBlur: false,\n    textAfterControls: '',\n    textBeforeControls: '',\n    textBetweenControls: ''\n  },\n  DefaultCallbacks: {\n    callback: function(form) {\n      return Form.serialize(form);\n    },\n    onComplete: function(transport, element) {\n      // For backward compatibility, this one is bound to the IPE, and passes\n      // the element directly.  It was too often customized, so we don't break it.\n      new Effect.Highlight(element, {\n        startcolor: this.options.highlightColor, keepBackgroundImage: true });\n    },\n    onEnterEditMode: null,\n    onEnterHover: function(ipe) {\n      ipe.element.style.backgroundColor = ipe.options.highlightColor;\n      if (ipe._effect)\n        ipe._effect.cancel();\n    },\n    onFailure: function(transport, ipe) {\n      alert('Error communication with the server: ' + transport.responseText.stripTags());\n    },\n    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.\n    onLeaveEditMode: null,\n    onLeaveHover: function(ipe) {\n      ipe._effect = new Effect.Highlight(ipe.element, {\n        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,\n        restorecolor: ipe._originalBackground, keepBackgroundImage: true\n      });\n    }\n  },\n  Listeners: {\n    click: 'enterEditMode',\n    keydown: 'checkForEscapeOrReturn',\n    mouseover: 'enterHover',\n    mouseout: 'leaveHover'\n  }\n});\n\nAjax.InPlaceCollectionEditor.DefaultOptions = {\n  loadingCollectionText: 'Loading options...'\n};\n\n// Delayed observer, like Form.Element.Observer,\n// but waits for delay after last key input\n// Ideal for live-search fields\n\nForm.Element.DelayedObserver = Class.create({\n  initialize: function(element, delay, callback) {\n    this.delay     = delay || 0.5;\n    this.element   = $(element);\n    this.callback  = callback;\n    this.timer     = null;\n    this.lastValue = $F(this.element);\n    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));\n  },\n  delayedListener: function(event) {\n    if(this.lastValue == $F(this.element)) return;\n    if(this.timer) clearTimeout(this.timer);\n    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);\n    this.lastValue = $F(this.element);\n  },\n  onTimerEvent: function() {\n    this.timer = null;\n    this.callback(this.element, $F(this.element));\n  }\n});"
  },
  {
    "path": "examples/rails_openid/public/javascripts/dragdrop.js",
    "content": "// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n//           (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\nif(Object.isUndefined(Effect))\n  throw(\"dragdrop.js requires including script.aculo.us' effects.js library\");\n\nvar Droppables = {\n  drops: [],\n\n  remove: function(element) {\n    this.drops = this.drops.reject(function(d) { return d.element==$(element) });\n  },\n\n  add: function(element) {\n    element = $(element);\n    var options = Object.extend({\n      greedy:     true,\n      hoverclass: null,\n      tree:       false\n    }, arguments[1] || { });\n\n    // cache containers\n    if(options.containment) {\n      options._containers = [];\n      var containment = options.containment;\n      if(Object.isArray(containment)) {\n        containment.each( function(c) { options._containers.push($(c)) });\n      } else {\n        options._containers.push($(containment));\n      }\n    }\n\n    if(options.accept) options.accept = [options.accept].flatten();\n\n    Element.makePositioned(element); // fix IE\n    options.element = element;\n\n    this.drops.push(options);\n  },\n\n  findDeepestChild: function(drops) {\n    deepest = drops[0];\n\n    for (i = 1; i < drops.length; ++i)\n      if (Element.isParent(drops[i].element, deepest.element))\n        deepest = drops[i];\n\n    return deepest;\n  },\n\n  isContained: function(element, drop) {\n    var containmentNode;\n    if(drop.tree) {\n      containmentNode = element.treeNode;\n    } else {\n      containmentNode = element.parentNode;\n    }\n    return drop._containers.detect(function(c) { return containmentNode == c });\n  },\n\n  isAffected: function(point, element, drop) {\n    return (\n      (drop.element!=element) &&\n      ((!drop._containers) ||\n        this.isContained(element, drop)) &&\n      ((!drop.accept) ||\n        (Element.classNames(element).detect(\n          function(v) { return drop.accept.include(v) } ) )) &&\n      Position.within(drop.element, point[0], point[1]) );\n  },\n\n  deactivate: function(drop) {\n    if(drop.hoverclass)\n      Element.removeClassName(drop.element, drop.hoverclass);\n    this.last_active = null;\n  },\n\n  activate: function(drop) {\n    if(drop.hoverclass)\n      Element.addClassName(drop.element, drop.hoverclass);\n    this.last_active = drop;\n  },\n\n  show: function(point, element) {\n    if(!this.drops.length) return;\n    var drop, affected = [];\n\n    this.drops.each( function(drop) {\n      if(Droppables.isAffected(point, element, drop))\n        affected.push(drop);\n    });\n\n    if(affected.length>0)\n      drop = Droppables.findDeepestChild(affected);\n\n    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);\n    if (drop) {\n      Position.within(drop.element, point[0], point[1]);\n      if(drop.onHover)\n        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));\n\n      if (drop != this.last_active) Droppables.activate(drop);\n    }\n  },\n\n  fire: function(event, element) {\n    if(!this.last_active) return;\n    Position.prepare();\n\n    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))\n      if (this.last_active.onDrop) {\n        this.last_active.onDrop(element, this.last_active.element, event);\n        return true;\n      }\n  },\n\n  reset: function() {\n    if(this.last_active)\n      this.deactivate(this.last_active);\n  }\n};\n\nvar Draggables = {\n  drags: [],\n  observers: [],\n\n  register: function(draggable) {\n    if(this.drags.length == 0) {\n      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);\n      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);\n      this.eventKeypress  = this.keyPress.bindAsEventListener(this);\n\n      Event.observe(document, \"mouseup\", this.eventMouseUp);\n      Event.observe(document, \"mousemove\", this.eventMouseMove);\n      Event.observe(document, \"keypress\", this.eventKeypress);\n    }\n    this.drags.push(draggable);\n  },\n\n  unregister: function(draggable) {\n    this.drags = this.drags.reject(function(d) { return d==draggable });\n    if(this.drags.length == 0) {\n      Event.stopObserving(document, \"mouseup\", this.eventMouseUp);\n      Event.stopObserving(document, \"mousemove\", this.eventMouseMove);\n      Event.stopObserving(document, \"keypress\", this.eventKeypress);\n    }\n  },\n\n  activate: function(draggable) {\n    if(draggable.options.delay) {\n      this._timeout = setTimeout(function() {\n        Draggables._timeout = null;\n        window.focus();\n        Draggables.activeDraggable = draggable;\n      }.bind(this), draggable.options.delay);\n    } else {\n      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari\n      this.activeDraggable = draggable;\n    }\n  },\n\n  deactivate: function() {\n    this.activeDraggable = null;\n  },\n\n  updateDrag: function(event) {\n    if(!this.activeDraggable) return;\n    var pointer = [Event.pointerX(event), Event.pointerY(event)];\n    // Mozilla-based browsers fire successive mousemove events with\n    // the same coordinates, prevent needless redrawing (moz bug?)\n    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;\n    this._lastPointer = pointer;\n\n    this.activeDraggable.updateDrag(event, pointer);\n  },\n\n  endDrag: function(event) {\n    if(this._timeout) {\n      clearTimeout(this._timeout);\n      this._timeout = null;\n    }\n    if(!this.activeDraggable) return;\n    this._lastPointer = null;\n    this.activeDraggable.endDrag(event);\n    this.activeDraggable = null;\n  },\n\n  keyPress: function(event) {\n    if(this.activeDraggable)\n      this.activeDraggable.keyPress(event);\n  },\n\n  addObserver: function(observer) {\n    this.observers.push(observer);\n    this._cacheObserverCallbacks();\n  },\n\n  removeObserver: function(element) {  // element instead of observer fixes mem leaks\n    this.observers = this.observers.reject( function(o) { return o.element==element });\n    this._cacheObserverCallbacks();\n  },\n\n  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'\n    if(this[eventName+'Count'] > 0)\n      this.observers.each( function(o) {\n        if(o[eventName]) o[eventName](eventName, draggable, event);\n      });\n    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);\n  },\n\n  _cacheObserverCallbacks: function() {\n    ['onStart','onEnd','onDrag'].each( function(eventName) {\n      Draggables[eventName+'Count'] = Draggables.observers.select(\n        function(o) { return o[eventName]; }\n      ).length;\n    });\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Draggable = Class.create({\n  initialize: function(element) {\n    var defaults = {\n      handle: false,\n      reverteffect: function(element, top_offset, left_offset) {\n        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;\n        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,\n          queue: {scope:'_draggable', position:'end'}\n        });\n      },\n      endeffect: function(element) {\n        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;\n        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,\n          queue: {scope:'_draggable', position:'end'},\n          afterFinish: function(){\n            Draggable._dragging[element] = false\n          }\n        });\n      },\n      zindex: 1000,\n      revert: false,\n      quiet: false,\n      scroll: false,\n      scrollSensitivity: 20,\n      scrollSpeed: 15,\n      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }\n      delay: 0\n    };\n\n    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))\n      Object.extend(defaults, {\n        starteffect: function(element) {\n          element._opacity = Element.getOpacity(element);\n          Draggable._dragging[element] = true;\n          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});\n        }\n      });\n\n    var options = Object.extend(defaults, arguments[1] || { });\n\n    this.element = $(element);\n\n    if(options.handle && Object.isString(options.handle))\n      this.handle = this.element.down('.'+options.handle, 0);\n\n    if(!this.handle) this.handle = $(options.handle);\n    if(!this.handle) this.handle = this.element;\n\n    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {\n      options.scroll = $(options.scroll);\n      this._isScrollChild = Element.childOf(this.element, options.scroll);\n    }\n\n    Element.makePositioned(this.element); // fix IE\n\n    this.options  = options;\n    this.dragging = false;\n\n    this.eventMouseDown = this.initDrag.bindAsEventListener(this);\n    Event.observe(this.handle, \"mousedown\", this.eventMouseDown);\n\n    Draggables.register(this);\n  },\n\n  destroy: function() {\n    Event.stopObserving(this.handle, \"mousedown\", this.eventMouseDown);\n    Draggables.unregister(this);\n  },\n\n  currentDelta: function() {\n    return([\n      parseInt(Element.getStyle(this.element,'left') || '0'),\n      parseInt(Element.getStyle(this.element,'top') || '0')]);\n  },\n\n  initDrag: function(event) {\n    if(!Object.isUndefined(Draggable._dragging[this.element]) &&\n      Draggable._dragging[this.element]) return;\n    if(Event.isLeftClick(event)) {\n      // abort on form elements, fixes a Firefox issue\n      var src = Event.element(event);\n      if((tag_name = src.tagName.toUpperCase()) && (\n        tag_name=='INPUT' ||\n        tag_name=='SELECT' ||\n        tag_name=='OPTION' ||\n        tag_name=='BUTTON' ||\n        tag_name=='TEXTAREA')) return;\n\n      var pointer = [Event.pointerX(event), Event.pointerY(event)];\n      var pos     = Position.cumulativeOffset(this.element);\n      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });\n\n      Draggables.activate(this);\n      Event.stop(event);\n    }\n  },\n\n  startDrag: function(event) {\n    this.dragging = true;\n    if(!this.delta)\n      this.delta = this.currentDelta();\n\n    if(this.options.zindex) {\n      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);\n      this.element.style.zIndex = this.options.zindex;\n    }\n\n    if(this.options.ghosting) {\n      this._clone = this.element.cloneNode(true);\n      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');\n      if (!this._originallyAbsolute)\n        Position.absolutize(this.element);\n      this.element.parentNode.insertBefore(this._clone, this.element);\n    }\n\n    if(this.options.scroll) {\n      if (this.options.scroll == window) {\n        var where = this._getWindowScroll(this.options.scroll);\n        this.originalScrollLeft = where.left;\n        this.originalScrollTop = where.top;\n      } else {\n        this.originalScrollLeft = this.options.scroll.scrollLeft;\n        this.originalScrollTop = this.options.scroll.scrollTop;\n      }\n    }\n\n    Draggables.notify('onStart', this, event);\n\n    if(this.options.starteffect) this.options.starteffect(this.element);\n  },\n\n  updateDrag: function(event, pointer) {\n    if(!this.dragging) this.startDrag(event);\n\n    if(!this.options.quiet){\n      Position.prepare();\n      Droppables.show(pointer, this.element);\n    }\n\n    Draggables.notify('onDrag', this, event);\n\n    this.draw(pointer);\n    if(this.options.change) this.options.change(this);\n\n    if(this.options.scroll) {\n      this.stopScrolling();\n\n      var p;\n      if (this.options.scroll == window) {\n        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }\n      } else {\n        p = Position.page(this.options.scroll);\n        p[0] += this.options.scroll.scrollLeft + Position.deltaX;\n        p[1] += this.options.scroll.scrollTop + Position.deltaY;\n        p.push(p[0]+this.options.scroll.offsetWidth);\n        p.push(p[1]+this.options.scroll.offsetHeight);\n      }\n      var speed = [0,0];\n      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);\n      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);\n      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);\n      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);\n      this.startScrolling(speed);\n    }\n\n    // fix AppleWebKit rendering\n    if(Prototype.Browser.WebKit) window.scrollBy(0,0);\n\n    Event.stop(event);\n  },\n\n  finishDrag: function(event, success) {\n    this.dragging = false;\n\n    if(this.options.quiet){\n      Position.prepare();\n      var pointer = [Event.pointerX(event), Event.pointerY(event)];\n      Droppables.show(pointer, this.element);\n    }\n\n    if(this.options.ghosting) {\n      if (!this._originallyAbsolute)\n        Position.relativize(this.element);\n      delete this._originallyAbsolute;\n      Element.remove(this._clone);\n      this._clone = null;\n    }\n\n    var dropped = false;\n    if(success) {\n      dropped = Droppables.fire(event, this.element);\n      if (!dropped) dropped = false;\n    }\n    if(dropped && this.options.onDropped) this.options.onDropped(this.element);\n    Draggables.notify('onEnd', this, event);\n\n    var revert = this.options.revert;\n    if(revert && Object.isFunction(revert)) revert = revert(this.element);\n\n    var d = this.currentDelta();\n    if(revert && this.options.reverteffect) {\n      if (dropped == 0 || revert != 'failure')\n        this.options.reverteffect(this.element,\n          d[1]-this.delta[1], d[0]-this.delta[0]);\n    } else {\n      this.delta = d;\n    }\n\n    if(this.options.zindex)\n      this.element.style.zIndex = this.originalZ;\n\n    if(this.options.endeffect)\n      this.options.endeffect(this.element);\n\n    Draggables.deactivate(this);\n    Droppables.reset();\n  },\n\n  keyPress: function(event) {\n    if(event.keyCode!=Event.KEY_ESC) return;\n    this.finishDrag(event, false);\n    Event.stop(event);\n  },\n\n  endDrag: function(event) {\n    if(!this.dragging) return;\n    this.stopScrolling();\n    this.finishDrag(event, true);\n    Event.stop(event);\n  },\n\n  draw: function(point) {\n    var pos = Position.cumulativeOffset(this.element);\n    if(this.options.ghosting) {\n      var r   = Position.realOffset(this.element);\n      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;\n    }\n\n    var d = this.currentDelta();\n    pos[0] -= d[0]; pos[1] -= d[1];\n\n    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {\n      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;\n      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;\n    }\n\n    var p = [0,1].map(function(i){\n      return (point[i]-pos[i]-this.offset[i])\n    }.bind(this));\n\n    if(this.options.snap) {\n      if(Object.isFunction(this.options.snap)) {\n        p = this.options.snap(p[0],p[1],this);\n      } else {\n      if(Object.isArray(this.options.snap)) {\n        p = p.map( function(v, i) {\n          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));\n      } else {\n        p = p.map( function(v) {\n          return (v/this.options.snap).round()*this.options.snap }.bind(this));\n      }\n    }}\n\n    var style = this.element.style;\n    if((!this.options.constraint) || (this.options.constraint=='horizontal'))\n      style.left = p[0] + \"px\";\n    if((!this.options.constraint) || (this.options.constraint=='vertical'))\n      style.top  = p[1] + \"px\";\n\n    if(style.visibility==\"hidden\") style.visibility = \"\"; // fix gecko rendering\n  },\n\n  stopScrolling: function() {\n    if(this.scrollInterval) {\n      clearInterval(this.scrollInterval);\n      this.scrollInterval = null;\n      Draggables._lastScrollPointer = null;\n    }\n  },\n\n  startScrolling: function(speed) {\n    if(!(speed[0] || speed[1])) return;\n    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];\n    this.lastScrolled = new Date();\n    this.scrollInterval = setInterval(this.scroll.bind(this), 10);\n  },\n\n  scroll: function() {\n    var current = new Date();\n    var delta = current - this.lastScrolled;\n    this.lastScrolled = current;\n    if(this.options.scroll == window) {\n      with (this._getWindowScroll(this.options.scroll)) {\n        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {\n          var d = delta / 1000;\n          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );\n        }\n      }\n    } else {\n      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;\n      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;\n    }\n\n    Position.prepare();\n    Droppables.show(Draggables._lastPointer, this.element);\n    Draggables.notify('onDrag', this);\n    if (this._isScrollChild) {\n      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);\n      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;\n      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;\n      if (Draggables._lastScrollPointer[0] < 0)\n        Draggables._lastScrollPointer[0] = 0;\n      if (Draggables._lastScrollPointer[1] < 0)\n        Draggables._lastScrollPointer[1] = 0;\n      this.draw(Draggables._lastScrollPointer);\n    }\n\n    if(this.options.change) this.options.change(this);\n  },\n\n  _getWindowScroll: function(w) {\n    var T, L, W, H;\n    with (w.document) {\n      if (w.document.documentElement && documentElement.scrollTop) {\n        T = documentElement.scrollTop;\n        L = documentElement.scrollLeft;\n      } else if (w.document.body) {\n        T = body.scrollTop;\n        L = body.scrollLeft;\n      }\n      if (w.innerWidth) {\n        W = w.innerWidth;\n        H = w.innerHeight;\n      } else if (w.document.documentElement && documentElement.clientWidth) {\n        W = documentElement.clientWidth;\n        H = documentElement.clientHeight;\n      } else {\n        W = body.offsetWidth;\n        H = body.offsetHeight;\n      }\n    }\n    return { top: T, left: L, width: W, height: H };\n  }\n});\n\nDraggable._dragging = { };\n\n/*--------------------------------------------------------------------------*/\n\nvar SortableObserver = Class.create({\n  initialize: function(element, observer) {\n    this.element   = $(element);\n    this.observer  = observer;\n    this.lastValue = Sortable.serialize(this.element);\n  },\n\n  onStart: function() {\n    this.lastValue = Sortable.serialize(this.element);\n  },\n\n  onEnd: function() {\n    Sortable.unmark();\n    if(this.lastValue != Sortable.serialize(this.element))\n      this.observer(this.element)\n  }\n});\n\nvar Sortable = {\n  SERIALIZE_RULE: /^[^_\\-](?:[A-Za-z0-9\\-\\_]*)[_](.*)$/,\n\n  sortables: { },\n\n  _findRootElement: function(element) {\n    while (element.tagName.toUpperCase() != \"BODY\") {\n      if(element.id && Sortable.sortables[element.id]) return element;\n      element = element.parentNode;\n    }\n  },\n\n  options: function(element) {\n    element = Sortable._findRootElement($(element));\n    if(!element) return;\n    return Sortable.sortables[element.id];\n  },\n\n  destroy: function(element){\n    element = $(element);\n    var s = Sortable.sortables[element.id];\n\n    if(s) {\n      Draggables.removeObserver(s.element);\n      s.droppables.each(function(d){ Droppables.remove(d) });\n      s.draggables.invoke('destroy');\n\n      delete Sortable.sortables[s.element.id];\n    }\n  },\n\n  create: function(element) {\n    element = $(element);\n    var options = Object.extend({\n      element:     element,\n      tag:         'li',       // assumes li children, override with tag: 'tagname'\n      dropOnEmpty: false,\n      tree:        false,\n      treeTag:     'ul',\n      overlap:     'vertical', // one of 'vertical', 'horizontal'\n      constraint:  'vertical', // one of 'vertical', 'horizontal', false\n      containment: element,    // also takes array of elements (or id's); or false\n      handle:      false,      // or a CSS class\n      only:        false,\n      delay:       0,\n      hoverclass:  null,\n      ghosting:    false,\n      quiet:       false,\n      scroll:      false,\n      scrollSensitivity: 20,\n      scrollSpeed: 15,\n      format:      this.SERIALIZE_RULE,\n\n      // these take arrays of elements or ids and can be\n      // used for better initialization performance\n      elements:    false,\n      handles:     false,\n\n      onChange:    Prototype.emptyFunction,\n      onUpdate:    Prototype.emptyFunction\n    }, arguments[1] || { });\n\n    // clear any old sortable with same element\n    this.destroy(element);\n\n    // build options for the draggables\n    var options_for_draggable = {\n      revert:      true,\n      quiet:       options.quiet,\n      scroll:      options.scroll,\n      scrollSpeed: options.scrollSpeed,\n      scrollSensitivity: options.scrollSensitivity,\n      delay:       options.delay,\n      ghosting:    options.ghosting,\n      constraint:  options.constraint,\n      handle:      options.handle };\n\n    if(options.starteffect)\n      options_for_draggable.starteffect = options.starteffect;\n\n    if(options.reverteffect)\n      options_for_draggable.reverteffect = options.reverteffect;\n    else\n      if(options.ghosting) options_for_draggable.reverteffect = function(element) {\n        element.style.top  = 0;\n        element.style.left = 0;\n      };\n\n    if(options.endeffect)\n      options_for_draggable.endeffect = options.endeffect;\n\n    if(options.zindex)\n      options_for_draggable.zindex = options.zindex;\n\n    // build options for the droppables\n    var options_for_droppable = {\n      overlap:     options.overlap,\n      containment: options.containment,\n      tree:        options.tree,\n      hoverclass:  options.hoverclass,\n      onHover:     Sortable.onHover\n    };\n\n    var options_for_tree = {\n      onHover:      Sortable.onEmptyHover,\n      overlap:      options.overlap,\n      containment:  options.containment,\n      hoverclass:   options.hoverclass\n    };\n\n    // fix for gecko engine\n    Element.cleanWhitespace(element);\n\n    options.draggables = [];\n    options.droppables = [];\n\n    // drop on empty handling\n    if(options.dropOnEmpty || options.tree) {\n      Droppables.add(element, options_for_tree);\n      options.droppables.push(element);\n    }\n\n    (options.elements || this.findElements(element, options) || []).each( function(e,i) {\n      var handle = options.handles ? $(options.handles[i]) :\n        (options.handle ? $(e).select('.' + options.handle)[0] : e);\n      options.draggables.push(\n        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));\n      Droppables.add(e, options_for_droppable);\n      if(options.tree) e.treeNode = element;\n      options.droppables.push(e);\n    });\n\n    if(options.tree) {\n      (Sortable.findTreeElements(element, options) || []).each( function(e) {\n        Droppables.add(e, options_for_tree);\n        e.treeNode = element;\n        options.droppables.push(e);\n      });\n    }\n\n    // keep reference\n    this.sortables[element.id] = options;\n\n    // for onupdate\n    Draggables.addObserver(new SortableObserver(element, options.onUpdate));\n\n  },\n\n  // return all suitable-for-sortable elements in a guaranteed order\n  findElements: function(element, options) {\n    return Element.findChildren(\n      element, options.only, options.tree ? true : false, options.tag);\n  },\n\n  findTreeElements: function(element, options) {\n    return Element.findChildren(\n      element, options.only, options.tree ? true : false, options.treeTag);\n  },\n\n  onHover: function(element, dropon, overlap) {\n    if(Element.isParent(dropon, element)) return;\n\n    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {\n      return;\n    } else if(overlap>0.5) {\n      Sortable.mark(dropon, 'before');\n      if(dropon.previousSibling != element) {\n        var oldParentNode = element.parentNode;\n        element.style.visibility = \"hidden\"; // fix gecko rendering\n        dropon.parentNode.insertBefore(element, dropon);\n        if(dropon.parentNode!=oldParentNode)\n          Sortable.options(oldParentNode).onChange(element);\n        Sortable.options(dropon.parentNode).onChange(element);\n      }\n    } else {\n      Sortable.mark(dropon, 'after');\n      var nextElement = dropon.nextSibling || null;\n      if(nextElement != element) {\n        var oldParentNode = element.parentNode;\n        element.style.visibility = \"hidden\"; // fix gecko rendering\n        dropon.parentNode.insertBefore(element, nextElement);\n        if(dropon.parentNode!=oldParentNode)\n          Sortable.options(oldParentNode).onChange(element);\n        Sortable.options(dropon.parentNode).onChange(element);\n      }\n    }\n  },\n\n  onEmptyHover: function(element, dropon, overlap) {\n    var oldParentNode = element.parentNode;\n    var droponOptions = Sortable.options(dropon);\n\n    if(!Element.isParent(dropon, element)) {\n      var index;\n\n      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});\n      var child = null;\n\n      if(children) {\n        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);\n\n        for (index = 0; index < children.length; index += 1) {\n          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {\n            offset -= Element.offsetSize (children[index], droponOptions.overlap);\n          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {\n            child = index + 1 < children.length ? children[index + 1] : null;\n            break;\n          } else {\n            child = children[index];\n            break;\n          }\n        }\n      }\n\n      dropon.insertBefore(element, child);\n\n      Sortable.options(oldParentNode).onChange(element);\n      droponOptions.onChange(element);\n    }\n  },\n\n  unmark: function() {\n    if(Sortable._marker) Sortable._marker.hide();\n  },\n\n  mark: function(dropon, position) {\n    // mark on ghosting only\n    var sortable = Sortable.options(dropon.parentNode);\n    if(sortable && !sortable.ghosting) return;\n\n    if(!Sortable._marker) {\n      Sortable._marker =\n        ($('dropmarker') || Element.extend(document.createElement('DIV'))).\n          hide().addClassName('dropmarker').setStyle({position:'absolute'});\n      document.getElementsByTagName(\"body\").item(0).appendChild(Sortable._marker);\n    }\n    var offsets = Position.cumulativeOffset(dropon);\n    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});\n\n    if(position=='after')\n      if(sortable.overlap == 'horizontal')\n        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});\n      else\n        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});\n\n    Sortable._marker.show();\n  },\n\n  _tree: function(element, options, parent) {\n    var children = Sortable.findElements(element, options) || [];\n\n    for (var i = 0; i < children.length; ++i) {\n      var match = children[i].id.match(options.format);\n\n      if (!match) continue;\n\n      var child = {\n        id: encodeURIComponent(match ? match[1] : null),\n        element: element,\n        parent: parent,\n        children: [],\n        position: parent.children.length,\n        container: $(children[i]).down(options.treeTag)\n      };\n\n      /* Get the element containing the children and recurse over it */\n      if (child.container)\n        this._tree(child.container, options, child);\n\n      parent.children.push (child);\n    }\n\n    return parent;\n  },\n\n  tree: function(element) {\n    element = $(element);\n    var sortableOptions = this.options(element);\n    var options = Object.extend({\n      tag: sortableOptions.tag,\n      treeTag: sortableOptions.treeTag,\n      only: sortableOptions.only,\n      name: element.id,\n      format: sortableOptions.format\n    }, arguments[1] || { });\n\n    var root = {\n      id: null,\n      parent: null,\n      children: [],\n      container: element,\n      position: 0\n    };\n\n    return Sortable._tree(element, options, root);\n  },\n\n  /* Construct a [i] index for a particular node */\n  _constructIndex: function(node) {\n    var index = '';\n    do {\n      if (node.id) index = '[' + node.position + ']' + index;\n    } while ((node = node.parent) != null);\n    return index;\n  },\n\n  sequence: function(element) {\n    element = $(element);\n    var options = Object.extend(this.options(element), arguments[1] || { });\n\n    return $(this.findElements(element, options) || []).map( function(item) {\n      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';\n    });\n  },\n\n  setSequence: function(element, new_sequence) {\n    element = $(element);\n    var options = Object.extend(this.options(element), arguments[2] || { });\n\n    var nodeMap = { };\n    this.findElements(element, options).each( function(n) {\n        if (n.id.match(options.format))\n            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];\n        n.parentNode.removeChild(n);\n    });\n\n    new_sequence.each(function(ident) {\n      var n = nodeMap[ident];\n      if (n) {\n        n[1].appendChild(n[0]);\n        delete nodeMap[ident];\n      }\n    });\n  },\n\n  serialize: function(element) {\n    element = $(element);\n    var options = Object.extend(Sortable.options(element), arguments[1] || { });\n    var name = encodeURIComponent(\n      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);\n\n    if (options.tree) {\n      return Sortable.tree(element, arguments[1]).children.map( function (item) {\n        return [name + Sortable._constructIndex(item) + \"[id]=\" +\n                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));\n      }).flatten().join('&');\n    } else {\n      return Sortable.sequence(element, arguments[1]).map( function(item) {\n        return name + \"[]=\" + encodeURIComponent(item);\n      }).join('&');\n    }\n  }\n};\n\n// Returns true if child is contained within element\nElement.isParent = function(child, element) {\n  if (!child.parentNode || child == element) return false;\n  if (child.parentNode == element) return true;\n  return Element.isParent(child.parentNode, element);\n};\n\nElement.findChildren = function(element, only, recursive, tagName) {\n  if(!element.hasChildNodes()) return null;\n  tagName = tagName.toUpperCase();\n  if(only) only = [only].flatten();\n  var elements = [];\n  $A(element.childNodes).each( function(e) {\n    if(e.tagName && e.tagName.toUpperCase()==tagName &&\n      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))\n        elements.push(e);\n    if(recursive) {\n      var grandchildren = Element.findChildren(e, only, recursive, tagName);\n      if(grandchildren) elements.push(grandchildren);\n    }\n  });\n\n  return (elements.length>0 ? elements.flatten() : []);\n};\n\nElement.offsetSize = function (element, type) {\n  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];\n};"
  },
  {
    "path": "examples/rails_openid/public/javascripts/effects.js",
    "content": "// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)\n// Contributors:\n//  Justin Palmer (http://encytemedia.com/)\n//  Mark Pilgrim (http://diveintomark.org/)\n//  Martin Bialasinki\n//\n// script.aculo.us is freely distributable under the terms of an MIT-style license.\n// For details, see the script.aculo.us web site: http://script.aculo.us/\n\n// converts rgb() and #xxx to #xxxxxx format,\n// returns self (or first argument) if not convertable\nString.prototype.parseColor = function() {\n  var color = '#';\n  if (this.slice(0,4) == 'rgb(') {\n    var cols = this.slice(4,this.length-1).split(',');\n    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);\n  } else {\n    if (this.slice(0,1) == '#') {\n      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();\n      if (this.length==7) color = this.toLowerCase();\n    }\n  }\n  return (color.length==7 ? color : (arguments[0] || this));\n};\n\n/*--------------------------------------------------------------------------*/\n\nElement.collectTextNodes = function(element) {\n  return $A($(element).childNodes).collect( function(node) {\n    return (node.nodeType==3 ? node.nodeValue :\n      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));\n  }).flatten().join('');\n};\n\nElement.collectTextNodesIgnoreClass = function(element, className) {\n  return $A($(element).childNodes).collect( function(node) {\n    return (node.nodeType==3 ? node.nodeValue :\n      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?\n        Element.collectTextNodesIgnoreClass(node, className) : ''));\n  }).flatten().join('');\n};\n\nElement.setContentZoom = function(element, percent) {\n  element = $(element);\n  element.setStyle({fontSize: (percent/100) + 'em'});\n  if (Prototype.Browser.WebKit) window.scrollBy(0,0);\n  return element;\n};\n\nElement.getInlineOpacity = function(element){\n  return $(element).style.opacity || '';\n};\n\nElement.forceRerendering = function(element) {\n  try {\n    element = $(element);\n    var n = document.createTextNode(' ');\n    element.appendChild(n);\n    element.removeChild(n);\n  } catch(e) { }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Effect = {\n  _elementDoesNotExistError: {\n    name: 'ElementDoesNotExistError',\n    message: 'The specified DOM element does not exist, but is required for this effect to operate'\n  },\n  Transitions: {\n    linear: Prototype.K,\n    sinoidal: function(pos) {\n      return (-Math.cos(pos*Math.PI)/2) + .5;\n    },\n    reverse: function(pos) {\n      return 1-pos;\n    },\n    flicker: function(pos) {\n      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;\n      return pos > 1 ? 1 : pos;\n    },\n    wobble: function(pos) {\n      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;\n    },\n    pulse: function(pos, pulses) {\n      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;\n    },\n    spring: function(pos) {\n      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));\n    },\n    none: function(pos) {\n      return 0;\n    },\n    full: function(pos) {\n      return 1;\n    }\n  },\n  DefaultOptions: {\n    duration:   1.0,   // seconds\n    fps:        100,   // 100= assume 66fps max.\n    sync:       false, // true for combining\n    from:       0.0,\n    to:         1.0,\n    delay:      0.0,\n    queue:      'parallel'\n  },\n  tagifyText: function(element) {\n    var tagifyStyle = 'position:relative';\n    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';\n\n    element = $(element);\n    $A(element.childNodes).each( function(child) {\n      if (child.nodeType==3) {\n        child.nodeValue.toArray().each( function(character) {\n          element.insertBefore(\n            new Element('span', {style: tagifyStyle}).update(\n              character == ' ' ? String.fromCharCode(160) : character),\n              child);\n        });\n        Element.remove(child);\n      }\n    });\n  },\n  multiple: function(element, effect) {\n    var elements;\n    if (((typeof element == 'object') ||\n        Object.isFunction(element)) &&\n       (element.length))\n      elements = element;\n    else\n      elements = $(element).childNodes;\n\n    var options = Object.extend({\n      speed: 0.1,\n      delay: 0.0\n    }, arguments[2] || { });\n    var masterDelay = options.delay;\n\n    $A(elements).each( function(element, index) {\n      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));\n    });\n  },\n  PAIRS: {\n    'slide':  ['SlideDown','SlideUp'],\n    'blind':  ['BlindDown','BlindUp'],\n    'appear': ['Appear','Fade']\n  },\n  toggle: function(element, effect) {\n    element = $(element);\n    effect = (effect || 'appear').toLowerCase();\n    var options = Object.extend({\n      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }\n    }, arguments[2] || { });\n    Effect[element.visible() ?\n      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);\n  }\n};\n\nEffect.DefaultOptions.transition = Effect.Transitions.sinoidal;\n\n/* ------------- core effects ------------- */\n\nEffect.ScopedQueue = Class.create(Enumerable, {\n  initialize: function() {\n    this.effects  = [];\n    this.interval = null;\n  },\n  _each: function(iterator) {\n    this.effects._each(iterator);\n  },\n  add: function(effect) {\n    var timestamp = new Date().getTime();\n\n    var position = Object.isString(effect.options.queue) ?\n      effect.options.queue : effect.options.queue.position;\n\n    switch(position) {\n      case 'front':\n        // move unstarted effects after this effect\n        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {\n            e.startOn  += effect.finishOn;\n            e.finishOn += effect.finishOn;\n          });\n        break;\n      case 'with-last':\n        timestamp = this.effects.pluck('startOn').max() || timestamp;\n        break;\n      case 'end':\n        // start effect after last queued effect has finished\n        timestamp = this.effects.pluck('finishOn').max() || timestamp;\n        break;\n    }\n\n    effect.startOn  += timestamp;\n    effect.finishOn += timestamp;\n\n    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))\n      this.effects.push(effect);\n\n    if (!this.interval)\n      this.interval = setInterval(this.loop.bind(this), 15);\n  },\n  remove: function(effect) {\n    this.effects = this.effects.reject(function(e) { return e==effect });\n    if (this.effects.length == 0) {\n      clearInterval(this.interval);\n      this.interval = null;\n    }\n  },\n  loop: function() {\n    var timePos = new Date().getTime();\n    for(var i=0, len=this.effects.length;i<len;i++)\n      this.effects[i] && this.effects[i].loop(timePos);\n  }\n});\n\nEffect.Queues = {\n  instances: $H(),\n  get: function(queueName) {\n    if (!Object.isString(queueName)) return queueName;\n\n    return this.instances.get(queueName) ||\n      this.instances.set(queueName, new Effect.ScopedQueue());\n  }\n};\nEffect.Queue = Effect.Queues.get('global');\n\nEffect.Base = Class.create({\n  position: null,\n  start: function(options) {\n    function codeForEvent(options,eventName){\n      return (\n        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +\n        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')\n      );\n    }\n    if (options && options.transition === false) options.transition = Effect.Transitions.linear;\n    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });\n    this.currentFrame = 0;\n    this.state        = 'idle';\n    this.startOn      = this.options.delay*1000;\n    this.finishOn     = this.startOn+(this.options.duration*1000);\n    this.fromToDelta  = this.options.to-this.options.from;\n    this.totalTime    = this.finishOn-this.startOn;\n    this.totalFrames  = this.options.fps*this.options.duration;\n\n    this.render = (function() {\n      function dispatch(effect, eventName) {\n        if (effect.options[eventName + 'Internal'])\n          effect.options[eventName + 'Internal'](effect);\n        if (effect.options[eventName])\n          effect.options[eventName](effect);\n      }\n\n      return function(pos) {\n        if (this.state === \"idle\") {\n          this.state = \"running\";\n          dispatch(this, 'beforeSetup');\n          if (this.setup) this.setup();\n          dispatch(this, 'afterSetup');\n        }\n        if (this.state === \"running\") {\n          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;\n          this.position = pos;\n          dispatch(this, 'beforeUpdate');\n          if (this.update) this.update(pos);\n          dispatch(this, 'afterUpdate');\n        }\n      };\n    })();\n\n    this.event('beforeStart');\n    if (!this.options.sync)\n      Effect.Queues.get(Object.isString(this.options.queue) ?\n        'global' : this.options.queue.scope).add(this);\n  },\n  loop: function(timePos) {\n    if (timePos >= this.startOn) {\n      if (timePos >= this.finishOn) {\n        this.render(1.0);\n        this.cancel();\n        this.event('beforeFinish');\n        if (this.finish) this.finish();\n        this.event('afterFinish');\n        return;\n      }\n      var pos   = (timePos - this.startOn) / this.totalTime,\n          frame = (pos * this.totalFrames).round();\n      if (frame > this.currentFrame) {\n        this.render(pos);\n        this.currentFrame = frame;\n      }\n    }\n  },\n  cancel: function() {\n    if (!this.options.sync)\n      Effect.Queues.get(Object.isString(this.options.queue) ?\n        'global' : this.options.queue.scope).remove(this);\n    this.state = 'finished';\n  },\n  event: function(eventName) {\n    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);\n    if (this.options[eventName]) this.options[eventName](this);\n  },\n  inspect: function() {\n    var data = $H();\n    for(property in this)\n      if (!Object.isFunction(this[property])) data.set(property, this[property]);\n    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';\n  }\n});\n\nEffect.Parallel = Class.create(Effect.Base, {\n  initialize: function(effects) {\n    this.effects = effects || [];\n    this.start(arguments[1]);\n  },\n  update: function(position) {\n    this.effects.invoke('render', position);\n  },\n  finish: function(position) {\n    this.effects.each( function(effect) {\n      effect.render(1.0);\n      effect.cancel();\n      effect.event('beforeFinish');\n      if (effect.finish) effect.finish(position);\n      effect.event('afterFinish');\n    });\n  }\n});\n\nEffect.Tween = Class.create(Effect.Base, {\n  initialize: function(object, from, to) {\n    object = Object.isString(object) ? $(object) : object;\n    var args = $A(arguments), method = args.last(),\n      options = args.length == 5 ? args[3] : null;\n    this.method = Object.isFunction(method) ? method.bind(object) :\n      Object.isFunction(object[method]) ? object[method].bind(object) :\n      function(value) { object[method] = value };\n    this.start(Object.extend({ from: from, to: to }, options || { }));\n  },\n  update: function(position) {\n    this.method(position);\n  }\n});\n\nEffect.Event = Class.create(Effect.Base, {\n  initialize: function() {\n    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));\n  },\n  update: Prototype.emptyFunction\n});\n\nEffect.Opacity = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    // make this work on IE on elements without 'layout'\n    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))\n      this.element.setStyle({zoom: 1});\n    var options = Object.extend({\n      from: this.element.getOpacity() || 0.0,\n      to:   1.0\n    }, arguments[1] || { });\n    this.start(options);\n  },\n  update: function(position) {\n    this.element.setOpacity(position);\n  }\n});\n\nEffect.Move = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      x:    0,\n      y:    0,\n      mode: 'relative'\n    }, arguments[1] || { });\n    this.start(options);\n  },\n  setup: function() {\n    this.element.makePositioned();\n    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');\n    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');\n    if (this.options.mode == 'absolute') {\n      this.options.x = this.options.x - this.originalLeft;\n      this.options.y = this.options.y - this.originalTop;\n    }\n  },\n  update: function(position) {\n    this.element.setStyle({\n      left: (this.options.x  * position + this.originalLeft).round() + 'px',\n      top:  (this.options.y  * position + this.originalTop).round()  + 'px'\n    });\n  }\n});\n\n// for backwards compatibility\nEffect.MoveBy = function(element, toTop, toLeft) {\n  return new Effect.Move(element,\n    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));\n};\n\nEffect.Scale = Class.create(Effect.Base, {\n  initialize: function(element, percent) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      scaleX: true,\n      scaleY: true,\n      scaleContent: true,\n      scaleFromCenter: false,\n      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values\n      scaleFrom: 100.0,\n      scaleTo:   percent\n    }, arguments[2] || { });\n    this.start(options);\n  },\n  setup: function() {\n    this.restoreAfterFinish = this.options.restoreAfterFinish || false;\n    this.elementPositioning = this.element.getStyle('position');\n\n    this.originalStyle = { };\n    ['top','left','width','height','fontSize'].each( function(k) {\n      this.originalStyle[k] = this.element.style[k];\n    }.bind(this));\n\n    this.originalTop  = this.element.offsetTop;\n    this.originalLeft = this.element.offsetLeft;\n\n    var fontSize = this.element.getStyle('font-size') || '100%';\n    ['em','px','%','pt'].each( function(fontSizeType) {\n      if (fontSize.indexOf(fontSizeType)>0) {\n        this.fontSize     = parseFloat(fontSize);\n        this.fontSizeType = fontSizeType;\n      }\n    }.bind(this));\n\n    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;\n\n    this.dims = null;\n    if (this.options.scaleMode=='box')\n      this.dims = [this.element.offsetHeight, this.element.offsetWidth];\n    if (/^content/.test(this.options.scaleMode))\n      this.dims = [this.element.scrollHeight, this.element.scrollWidth];\n    if (!this.dims)\n      this.dims = [this.options.scaleMode.originalHeight,\n                   this.options.scaleMode.originalWidth];\n  },\n  update: function(position) {\n    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);\n    if (this.options.scaleContent && this.fontSize)\n      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });\n    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);\n  },\n  finish: function(position) {\n    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);\n  },\n  setDimensions: function(height, width) {\n    var d = { };\n    if (this.options.scaleX) d.width = width.round() + 'px';\n    if (this.options.scaleY) d.height = height.round() + 'px';\n    if (this.options.scaleFromCenter) {\n      var topd  = (height - this.dims[0])/2;\n      var leftd = (width  - this.dims[1])/2;\n      if (this.elementPositioning == 'absolute') {\n        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';\n        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';\n      } else {\n        if (this.options.scaleY) d.top = -topd + 'px';\n        if (this.options.scaleX) d.left = -leftd + 'px';\n      }\n    }\n    this.element.setStyle(d);\n  }\n});\n\nEffect.Highlight = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });\n    this.start(options);\n  },\n  setup: function() {\n    // Prevent executing on elements not in the layout flow\n    if (this.element.getStyle('display')=='none') { this.cancel(); return; }\n    // Disable background image during the effect\n    this.oldStyle = { };\n    if (!this.options.keepBackgroundImage) {\n      this.oldStyle.backgroundImage = this.element.getStyle('background-image');\n      this.element.setStyle({backgroundImage: 'none'});\n    }\n    if (!this.options.endcolor)\n      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');\n    if (!this.options.restorecolor)\n      this.options.restorecolor = this.element.getStyle('background-color');\n    // init color calculations\n    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));\n    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));\n  },\n  update: function(position) {\n    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){\n      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });\n  },\n  finish: function() {\n    this.element.setStyle(Object.extend(this.oldStyle, {\n      backgroundColor: this.options.restorecolor\n    }));\n  }\n});\n\nEffect.ScrollTo = function(element) {\n  var options = arguments[1] || { },\n  scrollOffsets = document.viewport.getScrollOffsets(),\n  elementOffsets = $(element).cumulativeOffset();\n\n  if (options.offset) elementOffsets[1] += options.offset;\n\n  return new Effect.Tween(null,\n    scrollOffsets.top,\n    elementOffsets[1],\n    options,\n    function(p){ scrollTo(scrollOffsets.left, p.round()); }\n  );\n};\n\n/* ------------- combination effects ------------- */\n\nEffect.Fade = function(element) {\n  element = $(element);\n  var oldOpacity = element.getInlineOpacity();\n  var options = Object.extend({\n    from: element.getOpacity() || 1.0,\n    to:   0.0,\n    afterFinishInternal: function(effect) {\n      if (effect.options.to!=0) return;\n      effect.element.hide().setStyle({opacity: oldOpacity});\n    }\n  }, arguments[1] || { });\n  return new Effect.Opacity(element,options);\n};\n\nEffect.Appear = function(element) {\n  element = $(element);\n  var options = Object.extend({\n  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),\n  to:   1.0,\n  // force Safari to render floated elements properly\n  afterFinishInternal: function(effect) {\n    effect.element.forceRerendering();\n  },\n  beforeSetup: function(effect) {\n    effect.element.setOpacity(effect.options.from).show();\n  }}, arguments[1] || { });\n  return new Effect.Opacity(element,options);\n};\n\nEffect.Puff = function(element) {\n  element = $(element);\n  var oldStyle = {\n    opacity: element.getInlineOpacity(),\n    position: element.getStyle('position'),\n    top:  element.style.top,\n    left: element.style.left,\n    width: element.style.width,\n    height: element.style.height\n  };\n  return new Effect.Parallel(\n   [ new Effect.Scale(element, 200,\n      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),\n     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],\n     Object.extend({ duration: 1.0,\n      beforeSetupInternal: function(effect) {\n        Position.absolutize(effect.effects[0].element);\n      },\n      afterFinishInternal: function(effect) {\n         effect.effects[0].element.hide().setStyle(oldStyle); }\n     }, arguments[1] || { })\n   );\n};\n\nEffect.BlindUp = function(element) {\n  element = $(element);\n  element.makeClipping();\n  return new Effect.Scale(element, 0,\n    Object.extend({ scaleContent: false,\n      scaleX: false,\n      restoreAfterFinish: true,\n      afterFinishInternal: function(effect) {\n        effect.element.hide().undoClipping();\n      }\n    }, arguments[1] || { })\n  );\n};\n\nEffect.BlindDown = function(element) {\n  element = $(element);\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, 100, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    scaleFrom: 0,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makeClipping().setStyle({height: '0px'}).show();\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.undoClipping();\n    }\n  }, arguments[1] || { }));\n};\n\nEffect.SwitchOff = function(element) {\n  element = $(element);\n  var oldOpacity = element.getInlineOpacity();\n  return new Effect.Appear(element, Object.extend({\n    duration: 0.4,\n    from: 0,\n    transition: Effect.Transitions.flicker,\n    afterFinishInternal: function(effect) {\n      new Effect.Scale(effect.element, 1, {\n        duration: 0.3, scaleFromCenter: true,\n        scaleX: false, scaleContent: false, restoreAfterFinish: true,\n        beforeSetup: function(effect) {\n          effect.element.makePositioned().makeClipping();\n        },\n        afterFinishInternal: function(effect) {\n          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});\n        }\n      });\n    }\n  }, arguments[1] || { }));\n};\n\nEffect.DropOut = function(element) {\n  element = $(element);\n  var oldStyle = {\n    top: element.getStyle('top'),\n    left: element.getStyle('left'),\n    opacity: element.getInlineOpacity() };\n  return new Effect.Parallel(\n    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),\n      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],\n    Object.extend(\n      { duration: 0.5,\n        beforeSetup: function(effect) {\n          effect.effects[0].element.makePositioned();\n        },\n        afterFinishInternal: function(effect) {\n          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);\n        }\n      }, arguments[1] || { }));\n};\n\nEffect.Shake = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    distance: 20,\n    duration: 0.5\n  }, arguments[1] || {});\n  var distance = parseFloat(options.distance);\n  var split = parseFloat(options.duration) / 10.0;\n  var oldStyle = {\n    top: element.getStyle('top'),\n    left: element.getStyle('left') };\n    return new Effect.Move(element,\n      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {\n    new Effect.Move(effect.element,\n      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {\n        effect.element.undoPositioned().setStyle(oldStyle);\n  }}); }}); }}); }}); }}); }});\n};\n\nEffect.SlideDown = function(element) {\n  element = $(element).cleanWhitespace();\n  // SlideDown need to have the content of the element wrapped in a container element with fixed height!\n  var oldInnerBottom = element.down().getStyle('bottom');\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, 100, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    scaleFrom: window.opera ? 0 : 1,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makePositioned();\n      effect.element.down().makePositioned();\n      if (window.opera) effect.element.setStyle({top: ''});\n      effect.element.makeClipping().setStyle({height: '0px'}).show();\n    },\n    afterUpdateInternal: function(effect) {\n      effect.element.down().setStyle({bottom:\n        (effect.dims[0] - effect.element.clientHeight) + 'px' });\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.undoClipping().undoPositioned();\n      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }\n    }, arguments[1] || { })\n  );\n};\n\nEffect.SlideUp = function(element) {\n  element = $(element).cleanWhitespace();\n  var oldInnerBottom = element.down().getStyle('bottom');\n  var elementDimensions = element.getDimensions();\n  return new Effect.Scale(element, window.opera ? 0 : 1,\n   Object.extend({ scaleContent: false,\n    scaleX: false,\n    scaleMode: 'box',\n    scaleFrom: 100,\n    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},\n    restoreAfterFinish: true,\n    afterSetup: function(effect) {\n      effect.element.makePositioned();\n      effect.element.down().makePositioned();\n      if (window.opera) effect.element.setStyle({top: ''});\n      effect.element.makeClipping().show();\n    },\n    afterUpdateInternal: function(effect) {\n      effect.element.down().setStyle({bottom:\n        (effect.dims[0] - effect.element.clientHeight) + 'px' });\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.hide().undoClipping().undoPositioned();\n      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});\n    }\n   }, arguments[1] || { })\n  );\n};\n\n// Bug in opera makes the TD containing this element expand for a instance after finish\nEffect.Squish = function(element) {\n  return new Effect.Scale(element, window.opera ? 1 : 0, {\n    restoreAfterFinish: true,\n    beforeSetup: function(effect) {\n      effect.element.makeClipping();\n    },\n    afterFinishInternal: function(effect) {\n      effect.element.hide().undoClipping();\n    }\n  });\n};\n\nEffect.Grow = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    direction: 'center',\n    moveTransition: Effect.Transitions.sinoidal,\n    scaleTransition: Effect.Transitions.sinoidal,\n    opacityTransition: Effect.Transitions.full\n  }, arguments[1] || { });\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    height: element.style.height,\n    width: element.style.width,\n    opacity: element.getInlineOpacity() };\n\n  var dims = element.getDimensions();\n  var initialMoveX, initialMoveY;\n  var moveX, moveY;\n\n  switch (options.direction) {\n    case 'top-left':\n      initialMoveX = initialMoveY = moveX = moveY = 0;\n      break;\n    case 'top-right':\n      initialMoveX = dims.width;\n      initialMoveY = moveY = 0;\n      moveX = -dims.width;\n      break;\n    case 'bottom-left':\n      initialMoveX = moveX = 0;\n      initialMoveY = dims.height;\n      moveY = -dims.height;\n      break;\n    case 'bottom-right':\n      initialMoveX = dims.width;\n      initialMoveY = dims.height;\n      moveX = -dims.width;\n      moveY = -dims.height;\n      break;\n    case 'center':\n      initialMoveX = dims.width / 2;\n      initialMoveY = dims.height / 2;\n      moveX = -dims.width / 2;\n      moveY = -dims.height / 2;\n      break;\n  }\n\n  return new Effect.Move(element, {\n    x: initialMoveX,\n    y: initialMoveY,\n    duration: 0.01,\n    beforeSetup: function(effect) {\n      effect.element.hide().makeClipping().makePositioned();\n    },\n    afterFinishInternal: function(effect) {\n      new Effect.Parallel(\n        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),\n          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),\n          new Effect.Scale(effect.element, 100, {\n            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },\n            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})\n        ], Object.extend({\n             beforeSetup: function(effect) {\n               effect.effects[0].element.setStyle({height: '0px'}).show();\n             },\n             afterFinishInternal: function(effect) {\n               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);\n             }\n           }, options)\n      );\n    }\n  });\n};\n\nEffect.Shrink = function(element) {\n  element = $(element);\n  var options = Object.extend({\n    direction: 'center',\n    moveTransition: Effect.Transitions.sinoidal,\n    scaleTransition: Effect.Transitions.sinoidal,\n    opacityTransition: Effect.Transitions.none\n  }, arguments[1] || { });\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    height: element.style.height,\n    width: element.style.width,\n    opacity: element.getInlineOpacity() };\n\n  var dims = element.getDimensions();\n  var moveX, moveY;\n\n  switch (options.direction) {\n    case 'top-left':\n      moveX = moveY = 0;\n      break;\n    case 'top-right':\n      moveX = dims.width;\n      moveY = 0;\n      break;\n    case 'bottom-left':\n      moveX = 0;\n      moveY = dims.height;\n      break;\n    case 'bottom-right':\n      moveX = dims.width;\n      moveY = dims.height;\n      break;\n    case 'center':\n      moveX = dims.width / 2;\n      moveY = dims.height / 2;\n      break;\n  }\n\n  return new Effect.Parallel(\n    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),\n      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),\n      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })\n    ], Object.extend({\n         beforeStartInternal: function(effect) {\n           effect.effects[0].element.makePositioned().makeClipping();\n         },\n         afterFinishInternal: function(effect) {\n           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }\n       }, options)\n  );\n};\n\nEffect.Pulsate = function(element) {\n  element = $(element);\n  var options    = arguments[1] || { },\n    oldOpacity = element.getInlineOpacity(),\n    transition = options.transition || Effect.Transitions.linear,\n    reverser   = function(pos){\n      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);\n    };\n\n  return new Effect.Opacity(element,\n    Object.extend(Object.extend({  duration: 2.0, from: 0,\n      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }\n    }, options), {transition: reverser}));\n};\n\nEffect.Fold = function(element) {\n  element = $(element);\n  var oldStyle = {\n    top: element.style.top,\n    left: element.style.left,\n    width: element.style.width,\n    height: element.style.height };\n  element.makeClipping();\n  return new Effect.Scale(element, 5, Object.extend({\n    scaleContent: false,\n    scaleX: false,\n    afterFinishInternal: function(effect) {\n    new Effect.Scale(element, 1, {\n      scaleContent: false,\n      scaleY: false,\n      afterFinishInternal: function(effect) {\n        effect.element.hide().undoClipping().setStyle(oldStyle);\n      } });\n  }}, arguments[1] || { }));\n};\n\nEffect.Morph = Class.create(Effect.Base, {\n  initialize: function(element) {\n    this.element = $(element);\n    if (!this.element) throw(Effect._elementDoesNotExistError);\n    var options = Object.extend({\n      style: { }\n    }, arguments[1] || { });\n\n    if (!Object.isString(options.style)) this.style = $H(options.style);\n    else {\n      if (options.style.include(':'))\n        this.style = options.style.parseStyle();\n      else {\n        this.element.addClassName(options.style);\n        this.style = $H(this.element.getStyles());\n        this.element.removeClassName(options.style);\n        var css = this.element.getStyles();\n        this.style = this.style.reject(function(style) {\n          return style.value == css[style.key];\n        });\n        options.afterFinishInternal = function(effect) {\n          effect.element.addClassName(effect.options.style);\n          effect.transforms.each(function(transform) {\n            effect.element.style[transform.style] = '';\n          });\n        };\n      }\n    }\n    this.start(options);\n  },\n\n  setup: function(){\n    function parseColor(color){\n      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';\n      color = color.parseColor();\n      return $R(0,2).map(function(i){\n        return parseInt( color.slice(i*2+1,i*2+3), 16 );\n      });\n    }\n    this.transforms = this.style.map(function(pair){\n      var property = pair[0], value = pair[1], unit = null;\n\n      if (value.parseColor('#zzzzzz') != '#zzzzzz') {\n        value = value.parseColor();\n        unit  = 'color';\n      } else if (property == 'opacity') {\n        value = parseFloat(value);\n        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))\n          this.element.setStyle({zoom: 1});\n      } else if (Element.CSS_LENGTH.test(value)) {\n          var components = value.match(/^([\\+\\-]?[0-9\\.]+)(.*)$/);\n          value = parseFloat(components[1]);\n          unit = (components.length == 3) ? components[2] : null;\n      }\n\n      var originalValue = this.element.getStyle(property);\n      return {\n        style: property.camelize(),\n        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),\n        targetValue: unit=='color' ? parseColor(value) : value,\n        unit: unit\n      };\n    }.bind(this)).reject(function(transform){\n      return (\n        (transform.originalValue == transform.targetValue) ||\n        (\n          transform.unit != 'color' &&\n          (isNaN(transform.originalValue) || isNaN(transform.targetValue))\n        )\n      );\n    });\n  },\n  update: function(position) {\n    var style = { }, transform, i = this.transforms.length;\n    while(i--)\n      style[(transform = this.transforms[i]).style] =\n        transform.unit=='color' ? '#'+\n          (Math.round(transform.originalValue[0]+\n            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +\n          (Math.round(transform.originalValue[1]+\n            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +\n          (Math.round(transform.originalValue[2]+\n            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :\n        (transform.originalValue +\n          (transform.targetValue - transform.originalValue) * position).toFixed(3) +\n            (transform.unit === null ? '' : transform.unit);\n    this.element.setStyle(style, true);\n  }\n});\n\nEffect.Transform = Class.create({\n  initialize: function(tracks){\n    this.tracks  = [];\n    this.options = arguments[1] || { };\n    this.addTracks(tracks);\n  },\n  addTracks: function(tracks){\n    tracks.each(function(track){\n      track = $H(track);\n      var data = track.values().first();\n      this.tracks.push($H({\n        ids:     track.keys().first(),\n        effect:  Effect.Morph,\n        options: { style: data }\n      }));\n    }.bind(this));\n    return this;\n  },\n  play: function(){\n    return new Effect.Parallel(\n      this.tracks.map(function(track){\n        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');\n        var elements = [$(ids) || $$(ids)].flatten();\n        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });\n      }).flatten(),\n      this.options\n    );\n  }\n});\n\nElement.CSS_PROPERTIES = $w(\n  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +\n  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +\n  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +\n  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +\n  'fontSize fontWeight height left letterSpacing lineHeight ' +\n  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+\n  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +\n  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +\n  'right textIndent top width wordSpacing zIndex');\n\nElement.CSS_LENGTH = /^(([\\+\\-]?[0-9\\.]+)(em|ex|px|in|cm|mm|pt|pc|\\%))|0$/;\n\nString.__parseStyleElement = document.createElement('div');\nString.prototype.parseStyle = function(){\n  var style, styleRules = $H();\n  if (Prototype.Browser.WebKit)\n    style = new Element('div',{style:this}).style;\n  else {\n    String.__parseStyleElement.innerHTML = '<div style=\"' + this + '\"></div>';\n    style = String.__parseStyleElement.childNodes[0].style;\n  }\n\n  Element.CSS_PROPERTIES.each(function(property){\n    if (style[property]) styleRules.set(property, style[property]);\n  });\n\n  if (Prototype.Browser.IE && this.include('opacity'))\n    styleRules.set('opacity', this.match(/opacity:\\s*((?:0|1)?(?:\\.\\d*)?)/)[1]);\n\n  return styleRules;\n};\n\nif (document.defaultView && document.defaultView.getComputedStyle) {\n  Element.getStyles = function(element) {\n    var css = document.defaultView.getComputedStyle($(element), null);\n    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {\n      styles[property] = css[property];\n      return styles;\n    });\n  };\n} else {\n  Element.getStyles = function(element) {\n    element = $(element);\n    var css = element.currentStyle, styles;\n    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {\n      results[property] = css[property];\n      return results;\n    });\n    if (!styles.opacity) styles.opacity = element.getOpacity();\n    return styles;\n  };\n}\n\nEffect.Methods = {\n  morph: function(element, style) {\n    element = $(element);\n    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));\n    return element;\n  },\n  visualEffect: function(element, effect, options) {\n    element = $(element);\n    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);\n    new Effect[klass](element, options);\n    return element;\n  },\n  highlight: function(element, options) {\n    element = $(element);\n    new Effect.Highlight(element, options);\n    return element;\n  }\n};\n\n$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+\n  'pulsate shake puff squish switchOff dropOut').each(\n  function(effect) {\n    Effect.Methods[effect] = function(element, options){\n      element = $(element);\n      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);\n      return element;\n    };\n  }\n);\n\n$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(\n  function(f) { Effect.Methods[f] = Element[f]; }\n);\n\nElement.addMethods(Effect.Methods);"
  },
  {
    "path": "examples/rails_openid/public/javascripts/prototype.js",
    "content": "/*  Prototype JavaScript framework, version 1.6.0.3\n *  (c) 2005-2008 Sam Stephenson\n *\n *  Prototype is freely distributable under the terms of an MIT-style license.\n *  For details, see the Prototype web site: http://www.prototypejs.org/\n *\n *--------------------------------------------------------------------------*/\n\nvar Prototype = {\n  Version: '1.6.0.3',\n\n  Browser: {\n    IE:     !!(window.attachEvent &&\n      navigator.userAgent.indexOf('Opera') === -1),\n    Opera:  navigator.userAgent.indexOf('Opera') > -1,\n    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,\n    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&\n      navigator.userAgent.indexOf('KHTML') === -1,\n    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)\n  },\n\n  BrowserFeatures: {\n    XPath: !!document.evaluate,\n    SelectorsAPI: !!document.querySelector,\n    ElementExtensions: !!window.HTMLElement,\n    SpecificElementExtensions:\n      document.createElement('div')['__proto__'] &&\n      document.createElement('div')['__proto__'] !==\n        document.createElement('form')['__proto__']\n  },\n\n  ScriptFragment: '<script[^>]*>([\\\\S\\\\s]*?)<\\/script>',\n  JSONFilter: /^\\/\\*-secure-([\\s\\S]*)\\*\\/\\s*$/,\n\n  emptyFunction: function() { },\n  K: function(x) { return x }\n};\n\nif (Prototype.Browser.MobileSafari)\n  Prototype.BrowserFeatures.SpecificElementExtensions = false;\n\n\n/* Based on Alex Arnell's inheritance implementation. */\nvar Class = {\n  create: function() {\n    var parent = null, properties = $A(arguments);\n    if (Object.isFunction(properties[0]))\n      parent = properties.shift();\n\n    function klass() {\n      this.initialize.apply(this, arguments);\n    }\n\n    Object.extend(klass, Class.Methods);\n    klass.superclass = parent;\n    klass.subclasses = [];\n\n    if (parent) {\n      var subclass = function() { };\n      subclass.prototype = parent.prototype;\n      klass.prototype = new subclass;\n      parent.subclasses.push(klass);\n    }\n\n    for (var i = 0; i < properties.length; i++)\n      klass.addMethods(properties[i]);\n\n    if (!klass.prototype.initialize)\n      klass.prototype.initialize = Prototype.emptyFunction;\n\n    klass.prototype.constructor = klass;\n\n    return klass;\n  }\n};\n\nClass.Methods = {\n  addMethods: function(source) {\n    var ancestor   = this.superclass && this.superclass.prototype;\n    var properties = Object.keys(source);\n\n    if (!Object.keys({ toString: true }).length)\n      properties.push(\"toString\", \"valueOf\");\n\n    for (var i = 0, length = properties.length; i < length; i++) {\n      var property = properties[i], value = source[property];\n      if (ancestor && Object.isFunction(value) &&\n          value.argumentNames().first() == \"$super\") {\n        var method = value;\n        value = (function(m) {\n          return function() { return ancestor[m].apply(this, arguments) };\n        })(property).wrap(method);\n\n        value.valueOf = method.valueOf.bind(method);\n        value.toString = method.toString.bind(method);\n      }\n      this.prototype[property] = value;\n    }\n\n    return this;\n  }\n};\n\nvar Abstract = { };\n\nObject.extend = function(destination, source) {\n  for (var property in source)\n    destination[property] = source[property];\n  return destination;\n};\n\nObject.extend(Object, {\n  inspect: function(object) {\n    try {\n      if (Object.isUndefined(object)) return 'undefined';\n      if (object === null) return 'null';\n      return object.inspect ? object.inspect() : String(object);\n    } catch (e) {\n      if (e instanceof RangeError) return '...';\n      throw e;\n    }\n  },\n\n  toJSON: function(object) {\n    var type = typeof object;\n    switch (type) {\n      case 'undefined':\n      case 'function':\n      case 'unknown': return;\n      case 'boolean': return object.toString();\n    }\n\n    if (object === null) return 'null';\n    if (object.toJSON) return object.toJSON();\n    if (Object.isElement(object)) return;\n\n    var results = [];\n    for (var property in object) {\n      var value = Object.toJSON(object[property]);\n      if (!Object.isUndefined(value))\n        results.push(property.toJSON() + ': ' + value);\n    }\n\n    return '{' + results.join(', ') + '}';\n  },\n\n  toQueryString: function(object) {\n    return $H(object).toQueryString();\n  },\n\n  toHTML: function(object) {\n    return object && object.toHTML ? object.toHTML() : String.interpret(object);\n  },\n\n  keys: function(object) {\n    var keys = [];\n    for (var property in object)\n      keys.push(property);\n    return keys;\n  },\n\n  values: function(object) {\n    var values = [];\n    for (var property in object)\n      values.push(object[property]);\n    return values;\n  },\n\n  clone: function(object) {\n    return Object.extend({ }, object);\n  },\n\n  isElement: function(object) {\n    return !!(object && object.nodeType == 1);\n  },\n\n  isArray: function(object) {\n    return object != null && typeof object == \"object\" &&\n      'splice' in object && 'join' in object;\n  },\n\n  isHash: function(object) {\n    return object instanceof Hash;\n  },\n\n  isFunction: function(object) {\n    return typeof object == \"function\";\n  },\n\n  isString: function(object) {\n    return typeof object == \"string\";\n  },\n\n  isNumber: function(object) {\n    return typeof object == \"number\";\n  },\n\n  isUndefined: function(object) {\n    return typeof object == \"undefined\";\n  }\n});\n\nObject.extend(Function.prototype, {\n  argumentNames: function() {\n    var names = this.toString().match(/^[\\s\\(]*function[^(]*\\(([^\\)]*)\\)/)[1]\n      .replace(/\\s+/g, '').split(',');\n    return names.length == 1 && !names[0] ? [] : names;\n  },\n\n  bind: function() {\n    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;\n    var __method = this, args = $A(arguments), object = args.shift();\n    return function() {\n      return __method.apply(object, args.concat($A(arguments)));\n    }\n  },\n\n  bindAsEventListener: function() {\n    var __method = this, args = $A(arguments), object = args.shift();\n    return function(event) {\n      return __method.apply(object, [event || window.event].concat(args));\n    }\n  },\n\n  curry: function() {\n    if (!arguments.length) return this;\n    var __method = this, args = $A(arguments);\n    return function() {\n      return __method.apply(this, args.concat($A(arguments)));\n    }\n  },\n\n  delay: function() {\n    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;\n    return window.setTimeout(function() {\n      return __method.apply(__method, args);\n    }, timeout);\n  },\n\n  defer: function() {\n    var args = [0.01].concat($A(arguments));\n    return this.delay.apply(this, args);\n  },\n\n  wrap: function(wrapper) {\n    var __method = this;\n    return function() {\n      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));\n    }\n  },\n\n  methodize: function() {\n    if (this._methodized) return this._methodized;\n    var __method = this;\n    return this._methodized = function() {\n      return __method.apply(null, [this].concat($A(arguments)));\n    };\n  }\n});\n\nDate.prototype.toJSON = function() {\n  return '\"' + this.getUTCFullYear() + '-' +\n    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +\n    this.getUTCDate().toPaddedString(2) + 'T' +\n    this.getUTCHours().toPaddedString(2) + ':' +\n    this.getUTCMinutes().toPaddedString(2) + ':' +\n    this.getUTCSeconds().toPaddedString(2) + 'Z\"';\n};\n\nvar Try = {\n  these: function() {\n    var returnValue;\n\n    for (var i = 0, length = arguments.length; i < length; i++) {\n      var lambda = arguments[i];\n      try {\n        returnValue = lambda();\n        break;\n      } catch (e) { }\n    }\n\n    return returnValue;\n  }\n};\n\nRegExp.prototype.match = RegExp.prototype.test;\n\nRegExp.escape = function(str) {\n  return String(str).replace(/([.*+?^=!:${}()|[\\]\\/\\\\])/g, '\\\\$1');\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar PeriodicalExecuter = Class.create({\n  initialize: function(callback, frequency) {\n    this.callback = callback;\n    this.frequency = frequency;\n    this.currentlyExecuting = false;\n\n    this.registerCallback();\n  },\n\n  registerCallback: function() {\n    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);\n  },\n\n  execute: function() {\n    this.callback(this);\n  },\n\n  stop: function() {\n    if (!this.timer) return;\n    clearInterval(this.timer);\n    this.timer = null;\n  },\n\n  onTimerEvent: function() {\n    if (!this.currentlyExecuting) {\n      try {\n        this.currentlyExecuting = true;\n        this.execute();\n      } finally {\n        this.currentlyExecuting = false;\n      }\n    }\n  }\n});\nObject.extend(String, {\n  interpret: function(value) {\n    return value == null ? '' : String(value);\n  },\n  specialChar: {\n    '\\b': '\\\\b',\n    '\\t': '\\\\t',\n    '\\n': '\\\\n',\n    '\\f': '\\\\f',\n    '\\r': '\\\\r',\n    '\\\\': '\\\\\\\\'\n  }\n});\n\nObject.extend(String.prototype, {\n  gsub: function(pattern, replacement) {\n    var result = '', source = this, match;\n    replacement = arguments.callee.prepareReplacement(replacement);\n\n    while (source.length > 0) {\n      if (match = source.match(pattern)) {\n        result += source.slice(0, match.index);\n        result += String.interpret(replacement(match));\n        source  = source.slice(match.index + match[0].length);\n      } else {\n        result += source, source = '';\n      }\n    }\n    return result;\n  },\n\n  sub: function(pattern, replacement, count) {\n    replacement = this.gsub.prepareReplacement(replacement);\n    count = Object.isUndefined(count) ? 1 : count;\n\n    return this.gsub(pattern, function(match) {\n      if (--count < 0) return match[0];\n      return replacement(match);\n    });\n  },\n\n  scan: function(pattern, iterator) {\n    this.gsub(pattern, iterator);\n    return String(this);\n  },\n\n  truncate: function(length, truncation) {\n    length = length || 30;\n    truncation = Object.isUndefined(truncation) ? '...' : truncation;\n    return this.length > length ?\n      this.slice(0, length - truncation.length) + truncation : String(this);\n  },\n\n  strip: function() {\n    return this.replace(/^\\s+/, '').replace(/\\s+$/, '');\n  },\n\n  stripTags: function() {\n    return this.replace(/<\\/?[^>]+>/gi, '');\n  },\n\n  stripScripts: function() {\n    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');\n  },\n\n  extractScripts: function() {\n    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');\n    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');\n    return (this.match(matchAll) || []).map(function(scriptTag) {\n      return (scriptTag.match(matchOne) || ['', ''])[1];\n    });\n  },\n\n  evalScripts: function() {\n    return this.extractScripts().map(function(script) { return eval(script) });\n  },\n\n  escapeHTML: function() {\n    var self = arguments.callee;\n    self.text.data = this;\n    return self.div.innerHTML;\n  },\n\n  unescapeHTML: function() {\n    var div = new Element('div');\n    div.innerHTML = this.stripTags();\n    return div.childNodes[0] ? (div.childNodes.length > 1 ?\n      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :\n      div.childNodes[0].nodeValue) : '';\n  },\n\n  toQueryParams: function(separator) {\n    var match = this.strip().match(/([^?#]*)(#.*)?$/);\n    if (!match) return { };\n\n    return match[1].split(separator || '&').inject({ }, function(hash, pair) {\n      if ((pair = pair.split('='))[0]) {\n        var key = decodeURIComponent(pair.shift());\n        var value = pair.length > 1 ? pair.join('=') : pair[0];\n        if (value != undefined) value = decodeURIComponent(value);\n\n        if (key in hash) {\n          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];\n          hash[key].push(value);\n        }\n        else hash[key] = value;\n      }\n      return hash;\n    });\n  },\n\n  toArray: function() {\n    return this.split('');\n  },\n\n  succ: function() {\n    return this.slice(0, this.length - 1) +\n      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);\n  },\n\n  times: function(count) {\n    return count < 1 ? '' : new Array(count + 1).join(this);\n  },\n\n  camelize: function() {\n    var parts = this.split('-'), len = parts.length;\n    if (len == 1) return parts[0];\n\n    var camelized = this.charAt(0) == '-'\n      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)\n      : parts[0];\n\n    for (var i = 1; i < len; i++)\n      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);\n\n    return camelized;\n  },\n\n  capitalize: function() {\n    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();\n  },\n\n  underscore: function() {\n    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();\n  },\n\n  dasherize: function() {\n    return this.gsub(/_/,'-');\n  },\n\n  inspect: function(useDoubleQuotes) {\n    var escapedString = this.gsub(/[\\x00-\\x1f\\\\]/, function(match) {\n      var character = String.specialChar[match[0]];\n      return character ? character : '\\\\u00' + match[0].charCodeAt().toPaddedString(2, 16);\n    });\n    if (useDoubleQuotes) return '\"' + escapedString.replace(/\"/g, '\\\\\"') + '\"';\n    return \"'\" + escapedString.replace(/'/g, '\\\\\\'') + \"'\";\n  },\n\n  toJSON: function() {\n    return this.inspect(true);\n  },\n\n  unfilterJSON: function(filter) {\n    return this.sub(filter || Prototype.JSONFilter, '#{1}');\n  },\n\n  isJSON: function() {\n    var str = this;\n    if (str.blank()) return false;\n    str = this.replace(/\\\\./g, '@').replace(/\"[^\"\\\\\\n\\r]*\"/g, '');\n    return (/^[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]*$/).test(str);\n  },\n\n  evalJSON: function(sanitize) {\n    var json = this.unfilterJSON();\n    try {\n      if (!sanitize || json.isJSON()) return eval('(' + json + ')');\n    } catch (e) { }\n    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());\n  },\n\n  include: function(pattern) {\n    return this.indexOf(pattern) > -1;\n  },\n\n  startsWith: function(pattern) {\n    return this.indexOf(pattern) === 0;\n  },\n\n  endsWith: function(pattern) {\n    var d = this.length - pattern.length;\n    return d >= 0 && this.lastIndexOf(pattern) === d;\n  },\n\n  empty: function() {\n    return this == '';\n  },\n\n  blank: function() {\n    return /^\\s*$/.test(this);\n  },\n\n  interpolate: function(object, pattern) {\n    return new Template(this, pattern).evaluate(object);\n  }\n});\n\nif (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {\n  escapeHTML: function() {\n    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n  },\n  unescapeHTML: function() {\n    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');\n  }\n});\n\nString.prototype.gsub.prepareReplacement = function(replacement) {\n  if (Object.isFunction(replacement)) return replacement;\n  var template = new Template(replacement);\n  return function(match) { return template.evaluate(match) };\n};\n\nString.prototype.parseQuery = String.prototype.toQueryParams;\n\nObject.extend(String.prototype.escapeHTML, {\n  div:  document.createElement('div'),\n  text: document.createTextNode('')\n});\n\nString.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);\n\nvar Template = Class.create({\n  initialize: function(template, pattern) {\n    this.template = template.toString();\n    this.pattern = pattern || Template.Pattern;\n  },\n\n  evaluate: function(object) {\n    if (Object.isFunction(object.toTemplateReplacements))\n      object = object.toTemplateReplacements();\n\n    return this.template.gsub(this.pattern, function(match) {\n      if (object == null) return '';\n\n      var before = match[1] || '';\n      if (before == '\\\\') return match[2];\n\n      var ctx = object, expr = match[3];\n      var pattern = /^([^.[]+|\\[((?:.*?[^\\\\])?)\\])(\\.|\\[|$)/;\n      match = pattern.exec(expr);\n      if (match == null) return before;\n\n      while (match != null) {\n        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\\\\\]', ']') : match[1];\n        ctx = ctx[comp];\n        if (null == ctx || '' == match[3]) break;\n        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);\n        match = pattern.exec(expr);\n      }\n\n      return before + String.interpret(ctx);\n    });\n  }\n});\nTemplate.Pattern = /(^|.|\\r|\\n)(#\\{(.*?)\\})/;\n\nvar $break = { };\n\nvar Enumerable = {\n  each: function(iterator, context) {\n    var index = 0;\n    try {\n      this._each(function(value) {\n        iterator.call(context, value, index++);\n      });\n    } catch (e) {\n      if (e != $break) throw e;\n    }\n    return this;\n  },\n\n  eachSlice: function(number, iterator, context) {\n    var index = -number, slices = [], array = this.toArray();\n    if (number < 1) return array;\n    while ((index += number) < array.length)\n      slices.push(array.slice(index, index+number));\n    return slices.collect(iterator, context);\n  },\n\n  all: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result = true;\n    this.each(function(value, index) {\n      result = result && !!iterator.call(context, value, index);\n      if (!result) throw $break;\n    });\n    return result;\n  },\n\n  any: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result = false;\n    this.each(function(value, index) {\n      if (result = !!iterator.call(context, value, index))\n        throw $break;\n    });\n    return result;\n  },\n\n  collect: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var results = [];\n    this.each(function(value, index) {\n      results.push(iterator.call(context, value, index));\n    });\n    return results;\n  },\n\n  detect: function(iterator, context) {\n    var result;\n    this.each(function(value, index) {\n      if (iterator.call(context, value, index)) {\n        result = value;\n        throw $break;\n      }\n    });\n    return result;\n  },\n\n  findAll: function(iterator, context) {\n    var results = [];\n    this.each(function(value, index) {\n      if (iterator.call(context, value, index))\n        results.push(value);\n    });\n    return results;\n  },\n\n  grep: function(filter, iterator, context) {\n    iterator = iterator || Prototype.K;\n    var results = [];\n\n    if (Object.isString(filter))\n      filter = new RegExp(filter);\n\n    this.each(function(value, index) {\n      if (filter.match(value))\n        results.push(iterator.call(context, value, index));\n    });\n    return results;\n  },\n\n  include: function(object) {\n    if (Object.isFunction(this.indexOf))\n      if (this.indexOf(object) != -1) return true;\n\n    var found = false;\n    this.each(function(value) {\n      if (value == object) {\n        found = true;\n        throw $break;\n      }\n    });\n    return found;\n  },\n\n  inGroupsOf: function(number, fillWith) {\n    fillWith = Object.isUndefined(fillWith) ? null : fillWith;\n    return this.eachSlice(number, function(slice) {\n      while(slice.length < number) slice.push(fillWith);\n      return slice;\n    });\n  },\n\n  inject: function(memo, iterator, context) {\n    this.each(function(value, index) {\n      memo = iterator.call(context, memo, value, index);\n    });\n    return memo;\n  },\n\n  invoke: function(method) {\n    var args = $A(arguments).slice(1);\n    return this.map(function(value) {\n      return value[method].apply(value, args);\n    });\n  },\n\n  max: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result;\n    this.each(function(value, index) {\n      value = iterator.call(context, value, index);\n      if (result == null || value >= result)\n        result = value;\n    });\n    return result;\n  },\n\n  min: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var result;\n    this.each(function(value, index) {\n      value = iterator.call(context, value, index);\n      if (result == null || value < result)\n        result = value;\n    });\n    return result;\n  },\n\n  partition: function(iterator, context) {\n    iterator = iterator || Prototype.K;\n    var trues = [], falses = [];\n    this.each(function(value, index) {\n      (iterator.call(context, value, index) ?\n        trues : falses).push(value);\n    });\n    return [trues, falses];\n  },\n\n  pluck: function(property) {\n    var results = [];\n    this.each(function(value) {\n      results.push(value[property]);\n    });\n    return results;\n  },\n\n  reject: function(iterator, context) {\n    var results = [];\n    this.each(function(value, index) {\n      if (!iterator.call(context, value, index))\n        results.push(value);\n    });\n    return results;\n  },\n\n  sortBy: function(iterator, context) {\n    return this.map(function(value, index) {\n      return {\n        value: value,\n        criteria: iterator.call(context, value, index)\n      };\n    }).sort(function(left, right) {\n      var a = left.criteria, b = right.criteria;\n      return a < b ? -1 : a > b ? 1 : 0;\n    }).pluck('value');\n  },\n\n  toArray: function() {\n    return this.map();\n  },\n\n  zip: function() {\n    var iterator = Prototype.K, args = $A(arguments);\n    if (Object.isFunction(args.last()))\n      iterator = args.pop();\n\n    var collections = [this].concat(args).map($A);\n    return this.map(function(value, index) {\n      return iterator(collections.pluck(index));\n    });\n  },\n\n  size: function() {\n    return this.toArray().length;\n  },\n\n  inspect: function() {\n    return '#<Enumerable:' + this.toArray().inspect() + '>';\n  }\n};\n\nObject.extend(Enumerable, {\n  map:     Enumerable.collect,\n  find:    Enumerable.detect,\n  select:  Enumerable.findAll,\n  filter:  Enumerable.findAll,\n  member:  Enumerable.include,\n  entries: Enumerable.toArray,\n  every:   Enumerable.all,\n  some:    Enumerable.any\n});\nfunction $A(iterable) {\n  if (!iterable) return [];\n  if (iterable.toArray) return iterable.toArray();\n  var length = iterable.length || 0, results = new Array(length);\n  while (length--) results[length] = iterable[length];\n  return results;\n}\n\nif (Prototype.Browser.WebKit) {\n  $A = function(iterable) {\n    if (!iterable) return [];\n    // In Safari, only use the `toArray` method if it's not a NodeList.\n    // A NodeList is a function, has an function `item` property, and a numeric\n    // `length` property. Adapted from Google Doctype.\n    if (!(typeof iterable === 'function' && typeof iterable.length ===\n        'number' && typeof iterable.item === 'function') && iterable.toArray)\n      return iterable.toArray();\n    var length = iterable.length || 0, results = new Array(length);\n    while (length--) results[length] = iterable[length];\n    return results;\n  };\n}\n\nArray.from = $A;\n\nObject.extend(Array.prototype, Enumerable);\n\nif (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;\n\nObject.extend(Array.prototype, {\n  _each: function(iterator) {\n    for (var i = 0, length = this.length; i < length; i++)\n      iterator(this[i]);\n  },\n\n  clear: function() {\n    this.length = 0;\n    return this;\n  },\n\n  first: function() {\n    return this[0];\n  },\n\n  last: function() {\n    return this[this.length - 1];\n  },\n\n  compact: function() {\n    return this.select(function(value) {\n      return value != null;\n    });\n  },\n\n  flatten: function() {\n    return this.inject([], function(array, value) {\n      return array.concat(Object.isArray(value) ?\n        value.flatten() : [value]);\n    });\n  },\n\n  without: function() {\n    var values = $A(arguments);\n    return this.select(function(value) {\n      return !values.include(value);\n    });\n  },\n\n  reverse: function(inline) {\n    return (inline !== false ? this : this.toArray())._reverse();\n  },\n\n  reduce: function() {\n    return this.length > 1 ? this : this[0];\n  },\n\n  uniq: function(sorted) {\n    return this.inject([], function(array, value, index) {\n      if (0 == index || (sorted ? array.last() != value : !array.include(value)))\n        array.push(value);\n      return array;\n    });\n  },\n\n  intersect: function(array) {\n    return this.uniq().findAll(function(item) {\n      return array.detect(function(value) { return item === value });\n    });\n  },\n\n  clone: function() {\n    return [].concat(this);\n  },\n\n  size: function() {\n    return this.length;\n  },\n\n  inspect: function() {\n    return '[' + this.map(Object.inspect).join(', ') + ']';\n  },\n\n  toJSON: function() {\n    var results = [];\n    this.each(function(object) {\n      var value = Object.toJSON(object);\n      if (!Object.isUndefined(value)) results.push(value);\n    });\n    return '[' + results.join(', ') + ']';\n  }\n});\n\n// use native browser JS 1.6 implementation if available\nif (Object.isFunction(Array.prototype.forEach))\n  Array.prototype._each = Array.prototype.forEach;\n\nif (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {\n  i || (i = 0);\n  var length = this.length;\n  if (i < 0) i = length + i;\n  for (; i < length; i++)\n    if (this[i] === item) return i;\n  return -1;\n};\n\nif (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {\n  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;\n  var n = this.slice(0, i).reverse().indexOf(item);\n  return (n < 0) ? n : i - n - 1;\n};\n\nArray.prototype.toArray = Array.prototype.clone;\n\nfunction $w(string) {\n  if (!Object.isString(string)) return [];\n  string = string.strip();\n  return string ? string.split(/\\s+/) : [];\n}\n\nif (Prototype.Browser.Opera){\n  Array.prototype.concat = function() {\n    var array = [];\n    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);\n    for (var i = 0, length = arguments.length; i < length; i++) {\n      if (Object.isArray(arguments[i])) {\n        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)\n          array.push(arguments[i][j]);\n      } else {\n        array.push(arguments[i]);\n      }\n    }\n    return array;\n  };\n}\nObject.extend(Number.prototype, {\n  toColorPart: function() {\n    return this.toPaddedString(2, 16);\n  },\n\n  succ: function() {\n    return this + 1;\n  },\n\n  times: function(iterator, context) {\n    $R(0, this, true).each(iterator, context);\n    return this;\n  },\n\n  toPaddedString: function(length, radix) {\n    var string = this.toString(radix || 10);\n    return '0'.times(length - string.length) + string;\n  },\n\n  toJSON: function() {\n    return isFinite(this) ? this.toString() : 'null';\n  }\n});\n\n$w('abs round ceil floor').each(function(method){\n  Number.prototype[method] = Math[method].methodize();\n});\nfunction $H(object) {\n  return new Hash(object);\n};\n\nvar Hash = Class.create(Enumerable, (function() {\n\n  function toQueryPair(key, value) {\n    if (Object.isUndefined(value)) return key;\n    return key + '=' + encodeURIComponent(String.interpret(value));\n  }\n\n  return {\n    initialize: function(object) {\n      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);\n    },\n\n    _each: function(iterator) {\n      for (var key in this._object) {\n        var value = this._object[key], pair = [key, value];\n        pair.key = key;\n        pair.value = value;\n        iterator(pair);\n      }\n    },\n\n    set: function(key, value) {\n      return this._object[key] = value;\n    },\n\n    get: function(key) {\n      // simulating poorly supported hasOwnProperty\n      if (this._object[key] !== Object.prototype[key])\n        return this._object[key];\n    },\n\n    unset: function(key) {\n      var value = this._object[key];\n      delete this._object[key];\n      return value;\n    },\n\n    toObject: function() {\n      return Object.clone(this._object);\n    },\n\n    keys: function() {\n      return this.pluck('key');\n    },\n\n    values: function() {\n      return this.pluck('value');\n    },\n\n    index: function(value) {\n      var match = this.detect(function(pair) {\n        return pair.value === value;\n      });\n      return match && match.key;\n    },\n\n    merge: function(object) {\n      return this.clone().update(object);\n    },\n\n    update: function(object) {\n      return new Hash(object).inject(this, function(result, pair) {\n        result.set(pair.key, pair.value);\n        return result;\n      });\n    },\n\n    toQueryString: function() {\n      return this.inject([], function(results, pair) {\n        var key = encodeURIComponent(pair.key), values = pair.value;\n\n        if (values && typeof values == 'object') {\n          if (Object.isArray(values))\n            return results.concat(values.map(toQueryPair.curry(key)));\n        } else results.push(toQueryPair(key, values));\n        return results;\n      }).join('&');\n    },\n\n    inspect: function() {\n      return '#<Hash:{' + this.map(function(pair) {\n        return pair.map(Object.inspect).join(': ');\n      }).join(', ') + '}>';\n    },\n\n    toJSON: function() {\n      return Object.toJSON(this.toObject());\n    },\n\n    clone: function() {\n      return new Hash(this);\n    }\n  }\n})());\n\nHash.prototype.toTemplateReplacements = Hash.prototype.toObject;\nHash.from = $H;\nvar ObjectRange = Class.create(Enumerable, {\n  initialize: function(start, end, exclusive) {\n    this.start = start;\n    this.end = end;\n    this.exclusive = exclusive;\n  },\n\n  _each: function(iterator) {\n    var value = this.start;\n    while (this.include(value)) {\n      iterator(value);\n      value = value.succ();\n    }\n  },\n\n  include: function(value) {\n    if (value < this.start)\n      return false;\n    if (this.exclusive)\n      return value < this.end;\n    return value <= this.end;\n  }\n});\n\nvar $R = function(start, end, exclusive) {\n  return new ObjectRange(start, end, exclusive);\n};\n\nvar Ajax = {\n  getTransport: function() {\n    return Try.these(\n      function() {return new XMLHttpRequest()},\n      function() {return new ActiveXObject('Msxml2.XMLHTTP')},\n      function() {return new ActiveXObject('Microsoft.XMLHTTP')}\n    ) || false;\n  },\n\n  activeRequestCount: 0\n};\n\nAjax.Responders = {\n  responders: [],\n\n  _each: function(iterator) {\n    this.responders._each(iterator);\n  },\n\n  register: function(responder) {\n    if (!this.include(responder))\n      this.responders.push(responder);\n  },\n\n  unregister: function(responder) {\n    this.responders = this.responders.without(responder);\n  },\n\n  dispatch: function(callback, request, transport, json) {\n    this.each(function(responder) {\n      if (Object.isFunction(responder[callback])) {\n        try {\n          responder[callback].apply(responder, [request, transport, json]);\n        } catch (e) { }\n      }\n    });\n  }\n};\n\nObject.extend(Ajax.Responders, Enumerable);\n\nAjax.Responders.register({\n  onCreate:   function() { Ajax.activeRequestCount++ },\n  onComplete: function() { Ajax.activeRequestCount-- }\n});\n\nAjax.Base = Class.create({\n  initialize: function(options) {\n    this.options = {\n      method:       'post',\n      asynchronous: true,\n      contentType:  'application/x-www-form-urlencoded',\n      encoding:     'UTF-8',\n      parameters:   '',\n      evalJSON:     true,\n      evalJS:       true\n    };\n    Object.extend(this.options, options || { });\n\n    this.options.method = this.options.method.toLowerCase();\n\n    if (Object.isString(this.options.parameters))\n      this.options.parameters = this.options.parameters.toQueryParams();\n    else if (Object.isHash(this.options.parameters))\n      this.options.parameters = this.options.parameters.toObject();\n  }\n});\n\nAjax.Request = Class.create(Ajax.Base, {\n  _complete: false,\n\n  initialize: function($super, url, options) {\n    $super(options);\n    this.transport = Ajax.getTransport();\n    this.request(url);\n  },\n\n  request: function(url) {\n    this.url = url;\n    this.method = this.options.method;\n    var params = Object.clone(this.options.parameters);\n\n    if (!['get', 'post'].include(this.method)) {\n      // simulate other verbs over post\n      params['_method'] = this.method;\n      this.method = 'post';\n    }\n\n    this.parameters = params;\n\n    if (params = Object.toQueryString(params)) {\n      // when GET, append parameters to URL\n      if (this.method == 'get')\n        this.url += (this.url.include('?') ? '&' : '?') + params;\n      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))\n        params += '&_=';\n    }\n\n    try {\n      var response = new Ajax.Response(this);\n      if (this.options.onCreate) this.options.onCreate(response);\n      Ajax.Responders.dispatch('onCreate', this, response);\n\n      this.transport.open(this.method.toUpperCase(), this.url,\n        this.options.asynchronous);\n\n      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);\n\n      this.transport.onreadystatechange = this.onStateChange.bind(this);\n      this.setRequestHeaders();\n\n      this.body = this.method == 'post' ? (this.options.postBody || params) : null;\n      this.transport.send(this.body);\n\n      /* Force Firefox to handle ready state 4 for synchronous requests */\n      if (!this.options.asynchronous && this.transport.overrideMimeType)\n        this.onStateChange();\n\n    }\n    catch (e) {\n      this.dispatchException(e);\n    }\n  },\n\n  onStateChange: function() {\n    var readyState = this.transport.readyState;\n    if (readyState > 1 && !((readyState == 4) && this._complete))\n      this.respondToReadyState(this.transport.readyState);\n  },\n\n  setRequestHeaders: function() {\n    var headers = {\n      'X-Requested-With': 'XMLHttpRequest',\n      'X-Prototype-Version': Prototype.Version,\n      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'\n    };\n\n    if (this.method == 'post') {\n      headers['Content-type'] = this.options.contentType +\n        (this.options.encoding ? '; charset=' + this.options.encoding : '');\n\n      /* Force \"Connection: close\" for older Mozilla browsers to work\n       * around a bug where XMLHttpRequest sends an incorrect\n       * Content-length header. See Mozilla Bugzilla #246651.\n       */\n      if (this.transport.overrideMimeType &&\n          (navigator.userAgent.match(/Gecko\\/(\\d{4})/) || [0,2005])[1] < 2005)\n            headers['Connection'] = 'close';\n    }\n\n    // user-defined headers\n    if (typeof this.options.requestHeaders == 'object') {\n      var extras = this.options.requestHeaders;\n\n      if (Object.isFunction(extras.push))\n        for (var i = 0, length = extras.length; i < length; i += 2)\n          headers[extras[i]] = extras[i+1];\n      else\n        $H(extras).each(function(pair) { headers[pair.key] = pair.value });\n    }\n\n    for (var name in headers)\n      this.transport.setRequestHeader(name, headers[name]);\n  },\n\n  success: function() {\n    var status = this.getStatus();\n    return !status || (status >= 200 && status < 300);\n  },\n\n  getStatus: function() {\n    try {\n      return this.transport.status || 0;\n    } catch (e) { return 0 }\n  },\n\n  respondToReadyState: function(readyState) {\n    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);\n\n    if (state == 'Complete') {\n      try {\n        this._complete = true;\n        (this.options['on' + response.status]\n         || this.options['on' + (this.success() ? 'Success' : 'Failure')]\n         || Prototype.emptyFunction)(response, response.headerJSON);\n      } catch (e) {\n        this.dispatchException(e);\n      }\n\n      var contentType = response.getHeader('Content-type');\n      if (this.options.evalJS == 'force'\n          || (this.options.evalJS && this.isSameOrigin() && contentType\n          && contentType.match(/^\\s*(text|application)\\/(x-)?(java|ecma)script(;.*)?\\s*$/i)))\n        this.evalResponse();\n    }\n\n    try {\n      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);\n      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);\n    } catch (e) {\n      this.dispatchException(e);\n    }\n\n    if (state == 'Complete') {\n      // avoid memory leak in MSIE: clean up\n      this.transport.onreadystatechange = Prototype.emptyFunction;\n    }\n  },\n\n  isSameOrigin: function() {\n    var m = this.url.match(/^\\s*https?:\\/\\/[^\\/]*/);\n    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({\n      protocol: location.protocol,\n      domain: document.domain,\n      port: location.port ? ':' + location.port : ''\n    }));\n  },\n\n  getHeader: function(name) {\n    try {\n      return this.transport.getResponseHeader(name) || null;\n    } catch (e) { return null }\n  },\n\n  evalResponse: function() {\n    try {\n      return eval((this.transport.responseText || '').unfilterJSON());\n    } catch (e) {\n      this.dispatchException(e);\n    }\n  },\n\n  dispatchException: function(exception) {\n    (this.options.onException || Prototype.emptyFunction)(this, exception);\n    Ajax.Responders.dispatch('onException', this, exception);\n  }\n});\n\nAjax.Request.Events =\n  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];\n\nAjax.Response = Class.create({\n  initialize: function(request){\n    this.request = request;\n    var transport  = this.transport  = request.transport,\n        readyState = this.readyState = transport.readyState;\n\n    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {\n      this.status       = this.getStatus();\n      this.statusText   = this.getStatusText();\n      this.responseText = String.interpret(transport.responseText);\n      this.headerJSON   = this._getHeaderJSON();\n    }\n\n    if(readyState == 4) {\n      var xml = transport.responseXML;\n      this.responseXML  = Object.isUndefined(xml) ? null : xml;\n      this.responseJSON = this._getResponseJSON();\n    }\n  },\n\n  status:      0,\n  statusText: '',\n\n  getStatus: Ajax.Request.prototype.getStatus,\n\n  getStatusText: function() {\n    try {\n      return this.transport.statusText || '';\n    } catch (e) { return '' }\n  },\n\n  getHeader: Ajax.Request.prototype.getHeader,\n\n  getAllHeaders: function() {\n    try {\n      return this.getAllResponseHeaders();\n    } catch (e) { return null }\n  },\n\n  getResponseHeader: function(name) {\n    return this.transport.getResponseHeader(name);\n  },\n\n  getAllResponseHeaders: function() {\n    return this.transport.getAllResponseHeaders();\n  },\n\n  _getHeaderJSON: function() {\n    var json = this.getHeader('X-JSON');\n    if (!json) return null;\n    json = decodeURIComponent(escape(json));\n    try {\n      return json.evalJSON(this.request.options.sanitizeJSON ||\n        !this.request.isSameOrigin());\n    } catch (e) {\n      this.request.dispatchException(e);\n    }\n  },\n\n  _getResponseJSON: function() {\n    var options = this.request.options;\n    if (!options.evalJSON || (options.evalJSON != 'force' &&\n      !(this.getHeader('Content-type') || '').include('application/json')) ||\n        this.responseText.blank())\n          return null;\n    try {\n      return this.responseText.evalJSON(options.sanitizeJSON ||\n        !this.request.isSameOrigin());\n    } catch (e) {\n      this.request.dispatchException(e);\n    }\n  }\n});\n\nAjax.Updater = Class.create(Ajax.Request, {\n  initialize: function($super, container, url, options) {\n    this.container = {\n      success: (container.success || container),\n      failure: (container.failure || (container.success ? null : container))\n    };\n\n    options = Object.clone(options);\n    var onComplete = options.onComplete;\n    options.onComplete = (function(response, json) {\n      this.updateContent(response.responseText);\n      if (Object.isFunction(onComplete)) onComplete(response, json);\n    }).bind(this);\n\n    $super(url, options);\n  },\n\n  updateContent: function(responseText) {\n    var receiver = this.container[this.success() ? 'success' : 'failure'],\n        options = this.options;\n\n    if (!options.evalScripts) responseText = responseText.stripScripts();\n\n    if (receiver = $(receiver)) {\n      if (options.insertion) {\n        if (Object.isString(options.insertion)) {\n          var insertion = { }; insertion[options.insertion] = responseText;\n          receiver.insert(insertion);\n        }\n        else options.insertion(receiver, responseText);\n      }\n      else receiver.update(responseText);\n    }\n  }\n});\n\nAjax.PeriodicalUpdater = Class.create(Ajax.Base, {\n  initialize: function($super, container, url, options) {\n    $super(options);\n    this.onComplete = this.options.onComplete;\n\n    this.frequency = (this.options.frequency || 2);\n    this.decay = (this.options.decay || 1);\n\n    this.updater = { };\n    this.container = container;\n    this.url = url;\n\n    this.start();\n  },\n\n  start: function() {\n    this.options.onComplete = this.updateComplete.bind(this);\n    this.onTimerEvent();\n  },\n\n  stop: function() {\n    this.updater.options.onComplete = undefined;\n    clearTimeout(this.timer);\n    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);\n  },\n\n  updateComplete: function(response) {\n    if (this.options.decay) {\n      this.decay = (response.responseText == this.lastText ?\n        this.decay * this.options.decay : 1);\n\n      this.lastText = response.responseText;\n    }\n    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);\n  },\n\n  onTimerEvent: function() {\n    this.updater = new Ajax.Updater(this.container, this.url, this.options);\n  }\n});\nfunction $(element) {\n  if (arguments.length > 1) {\n    for (var i = 0, elements = [], length = arguments.length; i < length; i++)\n      elements.push($(arguments[i]));\n    return elements;\n  }\n  if (Object.isString(element))\n    element = document.getElementById(element);\n  return Element.extend(element);\n}\n\nif (Prototype.BrowserFeatures.XPath) {\n  document._getElementsByXPath = function(expression, parentElement) {\n    var results = [];\n    var query = document.evaluate(expression, $(parentElement) || document,\n      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n    for (var i = 0, length = query.snapshotLength; i < length; i++)\n      results.push(Element.extend(query.snapshotItem(i)));\n    return results;\n  };\n}\n\n/*--------------------------------------------------------------------------*/\n\nif (!window.Node) var Node = { };\n\nif (!Node.ELEMENT_NODE) {\n  // DOM level 2 ECMAScript Language Binding\n  Object.extend(Node, {\n    ELEMENT_NODE: 1,\n    ATTRIBUTE_NODE: 2,\n    TEXT_NODE: 3,\n    CDATA_SECTION_NODE: 4,\n    ENTITY_REFERENCE_NODE: 5,\n    ENTITY_NODE: 6,\n    PROCESSING_INSTRUCTION_NODE: 7,\n    COMMENT_NODE: 8,\n    DOCUMENT_NODE: 9,\n    DOCUMENT_TYPE_NODE: 10,\n    DOCUMENT_FRAGMENT_NODE: 11,\n    NOTATION_NODE: 12\n  });\n}\n\n(function() {\n  var element = this.Element;\n  this.Element = function(tagName, attributes) {\n    attributes = attributes || { };\n    tagName = tagName.toLowerCase();\n    var cache = Element.cache;\n    if (Prototype.Browser.IE && attributes.name) {\n      tagName = '<' + tagName + ' name=\"' + attributes.name + '\">';\n      delete attributes.name;\n      return Element.writeAttribute(document.createElement(tagName), attributes);\n    }\n    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));\n    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);\n  };\n  Object.extend(this.Element, element || { });\n  if (element) this.Element.prototype = element.prototype;\n}).call(window);\n\nElement.cache = { };\n\nElement.Methods = {\n  visible: function(element) {\n    return $(element).style.display != 'none';\n  },\n\n  toggle: function(element) {\n    element = $(element);\n    Element[Element.visible(element) ? 'hide' : 'show'](element);\n    return element;\n  },\n\n  hide: function(element) {\n    element = $(element);\n    element.style.display = 'none';\n    return element;\n  },\n\n  show: function(element) {\n    element = $(element);\n    element.style.display = '';\n    return element;\n  },\n\n  remove: function(element) {\n    element = $(element);\n    element.parentNode.removeChild(element);\n    return element;\n  },\n\n  update: function(element, content) {\n    element = $(element);\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) return element.update().insert(content);\n    content = Object.toHTML(content);\n    element.innerHTML = content.stripScripts();\n    content.evalScripts.bind(content).defer();\n    return element;\n  },\n\n  replace: function(element, content) {\n    element = $(element);\n    if (content && content.toElement) content = content.toElement();\n    else if (!Object.isElement(content)) {\n      content = Object.toHTML(content);\n      var range = element.ownerDocument.createRange();\n      range.selectNode(element);\n      content.evalScripts.bind(content).defer();\n      content = range.createContextualFragment(content.stripScripts());\n    }\n    element.parentNode.replaceChild(content, element);\n    return element;\n  },\n\n  insert: function(element, insertions) {\n    element = $(element);\n\n    if (Object.isString(insertions) || Object.isNumber(insertions) ||\n        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))\n          insertions = {bottom:insertions};\n\n    var content, insert, tagName, childNodes;\n\n    for (var position in insertions) {\n      content  = insertions[position];\n      position = position.toLowerCase();\n      insert = Element._insertionTranslations[position];\n\n      if (content && content.toElement) content = content.toElement();\n      if (Object.isElement(content)) {\n        insert(element, content);\n        continue;\n      }\n\n      content = Object.toHTML(content);\n\n      tagName = ((position == 'before' || position == 'after')\n        ? element.parentNode : element).tagName.toUpperCase();\n\n      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());\n\n      if (position == 'top' || position == 'after') childNodes.reverse();\n      childNodes.each(insert.curry(element));\n\n      content.evalScripts.bind(content).defer();\n    }\n\n    return element;\n  },\n\n  wrap: function(element, wrapper, attributes) {\n    element = $(element);\n    if (Object.isElement(wrapper))\n      $(wrapper).writeAttribute(attributes || { });\n    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);\n    else wrapper = new Element('div', wrapper);\n    if (element.parentNode)\n      element.parentNode.replaceChild(wrapper, element);\n    wrapper.appendChild(element);\n    return wrapper;\n  },\n\n  inspect: function(element) {\n    element = $(element);\n    var result = '<' + element.tagName.toLowerCase();\n    $H({'id': 'id', 'className': 'class'}).each(function(pair) {\n      var property = pair.first(), attribute = pair.last();\n      var value = (element[property] || '').toString();\n      if (value) result += ' ' + attribute + '=' + value.inspect(true);\n    });\n    return result + '>';\n  },\n\n  recursivelyCollect: function(element, property) {\n    element = $(element);\n    var elements = [];\n    while (element = element[property])\n      if (element.nodeType == 1)\n        elements.push(Element.extend(element));\n    return elements;\n  },\n\n  ancestors: function(element) {\n    return $(element).recursivelyCollect('parentNode');\n  },\n\n  descendants: function(element) {\n    return $(element).select(\"*\");\n  },\n\n  firstDescendant: function(element) {\n    element = $(element).firstChild;\n    while (element && element.nodeType != 1) element = element.nextSibling;\n    return $(element);\n  },\n\n  immediateDescendants: function(element) {\n    if (!(element = $(element).firstChild)) return [];\n    while (element && element.nodeType != 1) element = element.nextSibling;\n    if (element) return [element].concat($(element).nextSiblings());\n    return [];\n  },\n\n  previousSiblings: function(element) {\n    return $(element).recursivelyCollect('previousSibling');\n  },\n\n  nextSiblings: function(element) {\n    return $(element).recursivelyCollect('nextSibling');\n  },\n\n  siblings: function(element) {\n    element = $(element);\n    return element.previousSiblings().reverse().concat(element.nextSiblings());\n  },\n\n  match: function(element, selector) {\n    if (Object.isString(selector))\n      selector = new Selector(selector);\n    return selector.match($(element));\n  },\n\n  up: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(element.parentNode);\n    var ancestors = element.ancestors();\n    return Object.isNumber(expression) ? ancestors[expression] :\n      Selector.findElement(ancestors, expression, index);\n  },\n\n  down: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return element.firstDescendant();\n    return Object.isNumber(expression) ? element.descendants()[expression] :\n      Element.select(element, expression)[index || 0];\n  },\n\n  previous: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));\n    var previousSiblings = element.previousSiblings();\n    return Object.isNumber(expression) ? previousSiblings[expression] :\n      Selector.findElement(previousSiblings, expression, index);\n  },\n\n  next: function(element, expression, index) {\n    element = $(element);\n    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));\n    var nextSiblings = element.nextSiblings();\n    return Object.isNumber(expression) ? nextSiblings[expression] :\n      Selector.findElement(nextSiblings, expression, index);\n  },\n\n  select: function() {\n    var args = $A(arguments), element = $(args.shift());\n    return Selector.findChildElements(element, args);\n  },\n\n  adjacent: function() {\n    var args = $A(arguments), element = $(args.shift());\n    return Selector.findChildElements(element.parentNode, args).without(element);\n  },\n\n  identify: function(element) {\n    element = $(element);\n    var id = element.readAttribute('id'), self = arguments.callee;\n    if (id) return id;\n    do { id = 'anonymous_element_' + self.counter++ } while ($(id));\n    element.writeAttribute('id', id);\n    return id;\n  },\n\n  readAttribute: function(element, name) {\n    element = $(element);\n    if (Prototype.Browser.IE) {\n      var t = Element._attributeTranslations.read;\n      if (t.values[name]) return t.values[name](element, name);\n      if (t.names[name]) name = t.names[name];\n      if (name.include(':')) {\n        return (!element.attributes || !element.attributes[name]) ? null :\n         element.attributes[name].value;\n      }\n    }\n    return element.getAttribute(name);\n  },\n\n  writeAttribute: function(element, name, value) {\n    element = $(element);\n    var attributes = { }, t = Element._attributeTranslations.write;\n\n    if (typeof name == 'object') attributes = name;\n    else attributes[name] = Object.isUndefined(value) ? true : value;\n\n    for (var attr in attributes) {\n      name = t.names[attr] || attr;\n      value = attributes[attr];\n      if (t.values[attr]) name = t.values[attr](element, value);\n      if (value === false || value === null)\n        element.removeAttribute(name);\n      else if (value === true)\n        element.setAttribute(name, name);\n      else element.setAttribute(name, value);\n    }\n    return element;\n  },\n\n  getHeight: function(element) {\n    return $(element).getDimensions().height;\n  },\n\n  getWidth: function(element) {\n    return $(element).getDimensions().width;\n  },\n\n  classNames: function(element) {\n    return new Element.ClassNames(element);\n  },\n\n  hasClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    var elementClassName = element.className;\n    return (elementClassName.length > 0 && (elementClassName == className ||\n      new RegExp(\"(^|\\\\s)\" + className + \"(\\\\s|$)\").test(elementClassName)));\n  },\n\n  addClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    if (!element.hasClassName(className))\n      element.className += (element.className ? ' ' : '') + className;\n    return element;\n  },\n\n  removeClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    element.className = element.className.replace(\n      new RegExp(\"(^|\\\\s+)\" + className + \"(\\\\s+|$)\"), ' ').strip();\n    return element;\n  },\n\n  toggleClassName: function(element, className) {\n    if (!(element = $(element))) return;\n    return element[element.hasClassName(className) ?\n      'removeClassName' : 'addClassName'](className);\n  },\n\n  // removes whitespace-only text node children\n  cleanWhitespace: function(element) {\n    element = $(element);\n    var node = element.firstChild;\n    while (node) {\n      var nextNode = node.nextSibling;\n      if (node.nodeType == 3 && !/\\S/.test(node.nodeValue))\n        element.removeChild(node);\n      node = nextNode;\n    }\n    return element;\n  },\n\n  empty: function(element) {\n    return $(element).innerHTML.blank();\n  },\n\n  descendantOf: function(element, ancestor) {\n    element = $(element), ancestor = $(ancestor);\n\n    if (element.compareDocumentPosition)\n      return (element.compareDocumentPosition(ancestor) & 8) === 8;\n\n    if (ancestor.contains)\n      return ancestor.contains(element) && ancestor !== element;\n\n    while (element = element.parentNode)\n      if (element == ancestor) return true;\n\n    return false;\n  },\n\n  scrollTo: function(element) {\n    element = $(element);\n    var pos = element.cumulativeOffset();\n    window.scrollTo(pos[0], pos[1]);\n    return element;\n  },\n\n  getStyle: function(element, style) {\n    element = $(element);\n    style = style == 'float' ? 'cssFloat' : style.camelize();\n    var value = element.style[style];\n    if (!value || value == 'auto') {\n      var css = document.defaultView.getComputedStyle(element, null);\n      value = css ? css[style] : null;\n    }\n    if (style == 'opacity') return value ? parseFloat(value) : 1.0;\n    return value == 'auto' ? null : value;\n  },\n\n  getOpacity: function(element) {\n    return $(element).getStyle('opacity');\n  },\n\n  setStyle: function(element, styles) {\n    element = $(element);\n    var elementStyle = element.style, match;\n    if (Object.isString(styles)) {\n      element.style.cssText += ';' + styles;\n      return styles.include('opacity') ?\n        element.setOpacity(styles.match(/opacity:\\s*(\\d?\\.?\\d*)/)[1]) : element;\n    }\n    for (var property in styles)\n      if (property == 'opacity') element.setOpacity(styles[property]);\n      else\n        elementStyle[(property == 'float' || property == 'cssFloat') ?\n          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :\n            property] = styles[property];\n\n    return element;\n  },\n\n  setOpacity: function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1 || value === '') ? '' :\n      (value < 0.00001) ? 0 : value;\n    return element;\n  },\n\n  getDimensions: function(element) {\n    element = $(element);\n    var display = element.getStyle('display');\n    if (display != 'none' && display != null) // Safari bug\n      return {width: element.offsetWidth, height: element.offsetHeight};\n\n    // All *Width and *Height properties give 0 on elements with display none,\n    // so enable the element temporarily\n    var els = element.style;\n    var originalVisibility = els.visibility;\n    var originalPosition = els.position;\n    var originalDisplay = els.display;\n    els.visibility = 'hidden';\n    els.position = 'absolute';\n    els.display = 'block';\n    var originalWidth = element.clientWidth;\n    var originalHeight = element.clientHeight;\n    els.display = originalDisplay;\n    els.position = originalPosition;\n    els.visibility = originalVisibility;\n    return {width: originalWidth, height: originalHeight};\n  },\n\n  makePositioned: function(element) {\n    element = $(element);\n    var pos = Element.getStyle(element, 'position');\n    if (pos == 'static' || !pos) {\n      element._madePositioned = true;\n      element.style.position = 'relative';\n      // Opera returns the offset relative to the positioning context, when an\n      // element is position relative but top and left have not been defined\n      if (Prototype.Browser.Opera) {\n        element.style.top = 0;\n        element.style.left = 0;\n      }\n    }\n    return element;\n  },\n\n  undoPositioned: function(element) {\n    element = $(element);\n    if (element._madePositioned) {\n      element._madePositioned = undefined;\n      element.style.position =\n        element.style.top =\n        element.style.left =\n        element.style.bottom =\n        element.style.right = '';\n    }\n    return element;\n  },\n\n  makeClipping: function(element) {\n    element = $(element);\n    if (element._overflow) return element;\n    element._overflow = Element.getStyle(element, 'overflow') || 'auto';\n    if (element._overflow !== 'hidden')\n      element.style.overflow = 'hidden';\n    return element;\n  },\n\n  undoClipping: function(element) {\n    element = $(element);\n    if (!element._overflow) return element;\n    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;\n    element._overflow = null;\n    return element;\n  },\n\n  cumulativeOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      element = element.offsetParent;\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  positionedOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      element = element.offsetParent;\n      if (element) {\n        if (element.tagName.toUpperCase() == 'BODY') break;\n        var p = Element.getStyle(element, 'position');\n        if (p !== 'static') break;\n      }\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  absolutize: function(element) {\n    element = $(element);\n    if (element.getStyle('position') == 'absolute') return element;\n    // Position.prepare(); // To be done manually by Scripty when it needs it.\n\n    var offsets = element.positionedOffset();\n    var top     = offsets[1];\n    var left    = offsets[0];\n    var width   = element.clientWidth;\n    var height  = element.clientHeight;\n\n    element._originalLeft   = left - parseFloat(element.style.left  || 0);\n    element._originalTop    = top  - parseFloat(element.style.top || 0);\n    element._originalWidth  = element.style.width;\n    element._originalHeight = element.style.height;\n\n    element.style.position = 'absolute';\n    element.style.top    = top + 'px';\n    element.style.left   = left + 'px';\n    element.style.width  = width + 'px';\n    element.style.height = height + 'px';\n    return element;\n  },\n\n  relativize: function(element) {\n    element = $(element);\n    if (element.getStyle('position') == 'relative') return element;\n    // Position.prepare(); // To be done manually by Scripty when it needs it.\n\n    element.style.position = 'relative';\n    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);\n    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);\n\n    element.style.top    = top + 'px';\n    element.style.left   = left + 'px';\n    element.style.height = element._originalHeight;\n    element.style.width  = element._originalWidth;\n    return element;\n  },\n\n  cumulativeScrollOffset: function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.scrollTop  || 0;\n      valueL += element.scrollLeft || 0;\n      element = element.parentNode;\n    } while (element);\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  getOffsetParent: function(element) {\n    if (element.offsetParent) return $(element.offsetParent);\n    if (element == document.body) return $(element);\n\n    while ((element = element.parentNode) && element != document.body)\n      if (Element.getStyle(element, 'position') != 'static')\n        return $(element);\n\n    return $(document.body);\n  },\n\n  viewportOffset: function(forElement) {\n    var valueT = 0, valueL = 0;\n\n    var element = forElement;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n\n      // Safari fix\n      if (element.offsetParent == document.body &&\n        Element.getStyle(element, 'position') == 'absolute') break;\n\n    } while (element = element.offsetParent);\n\n    element = forElement;\n    do {\n      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {\n        valueT -= element.scrollTop  || 0;\n        valueL -= element.scrollLeft || 0;\n      }\n    } while (element = element.parentNode);\n\n    return Element._returnOffset(valueL, valueT);\n  },\n\n  clonePosition: function(element, source) {\n    var options = Object.extend({\n      setLeft:    true,\n      setTop:     true,\n      setWidth:   true,\n      setHeight:  true,\n      offsetTop:  0,\n      offsetLeft: 0\n    }, arguments[2] || { });\n\n    // find page position of source\n    source = $(source);\n    var p = source.viewportOffset();\n\n    // find coordinate system to use\n    element = $(element);\n    var delta = [0, 0];\n    var parent = null;\n    // delta [0,0] will do fine with position: fixed elements,\n    // position:absolute needs offsetParent deltas\n    if (Element.getStyle(element, 'position') == 'absolute') {\n      parent = element.getOffsetParent();\n      delta = parent.viewportOffset();\n    }\n\n    // correct by body offsets (fixes Safari)\n    if (parent == document.body) {\n      delta[0] -= document.body.offsetLeft;\n      delta[1] -= document.body.offsetTop;\n    }\n\n    // set position\n    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';\n    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';\n    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';\n    if (options.setHeight) element.style.height = source.offsetHeight + 'px';\n    return element;\n  }\n};\n\nElement.Methods.identify.counter = 1;\n\nObject.extend(Element.Methods, {\n  getElementsBySelector: Element.Methods.select,\n  childElements: Element.Methods.immediateDescendants\n});\n\nElement._attributeTranslations = {\n  write: {\n    names: {\n      className: 'class',\n      htmlFor:   'for'\n    },\n    values: { }\n  }\n};\n\nif (Prototype.Browser.Opera) {\n  Element.Methods.getStyle = Element.Methods.getStyle.wrap(\n    function(proceed, element, style) {\n      switch (style) {\n        case 'left': case 'top': case 'right': case 'bottom':\n          if (proceed(element, 'position') === 'static') return null;\n        case 'height': case 'width':\n          // returns '0px' for hidden elements; we want it to return null\n          if (!Element.visible(element)) return null;\n\n          // returns the border-box dimensions rather than the content-box\n          // dimensions, so we subtract padding and borders from the value\n          var dim = parseInt(proceed(element, style), 10);\n\n          if (dim !== element['offset' + style.capitalize()])\n            return dim + 'px';\n\n          var properties;\n          if (style === 'height') {\n            properties = ['border-top-width', 'padding-top',\n             'padding-bottom', 'border-bottom-width'];\n          }\n          else {\n            properties = ['border-left-width', 'padding-left',\n             'padding-right', 'border-right-width'];\n          }\n          return properties.inject(dim, function(memo, property) {\n            var val = proceed(element, property);\n            return val === null ? memo : memo - parseInt(val, 10);\n          }) + 'px';\n        default: return proceed(element, style);\n      }\n    }\n  );\n\n  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(\n    function(proceed, element, attribute) {\n      if (attribute === 'title') return element.title;\n      return proceed(element, attribute);\n    }\n  );\n}\n\nelse if (Prototype.Browser.IE) {\n  // IE doesn't report offsets correctly for static elements, so we change them\n  // to \"relative\" to get the values, then change them back.\n  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(\n    function(proceed, element) {\n      element = $(element);\n      // IE throws an error if element is not in document\n      try { element.offsetParent }\n      catch(e) { return $(document.body) }\n      var position = element.getStyle('position');\n      if (position !== 'static') return proceed(element);\n      element.setStyle({ position: 'relative' });\n      var value = proceed(element);\n      element.setStyle({ position: position });\n      return value;\n    }\n  );\n\n  $w('positionedOffset viewportOffset').each(function(method) {\n    Element.Methods[method] = Element.Methods[method].wrap(\n      function(proceed, element) {\n        element = $(element);\n        try { element.offsetParent }\n        catch(e) { return Element._returnOffset(0,0) }\n        var position = element.getStyle('position');\n        if (position !== 'static') return proceed(element);\n        // Trigger hasLayout on the offset parent so that IE6 reports\n        // accurate offsetTop and offsetLeft values for position: fixed.\n        var offsetParent = element.getOffsetParent();\n        if (offsetParent && offsetParent.getStyle('position') === 'fixed')\n          offsetParent.setStyle({ zoom: 1 });\n        element.setStyle({ position: 'relative' });\n        var value = proceed(element);\n        element.setStyle({ position: position });\n        return value;\n      }\n    );\n  });\n\n  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(\n    function(proceed, element) {\n      try { element.offsetParent }\n      catch(e) { return Element._returnOffset(0,0) }\n      return proceed(element);\n    }\n  );\n\n  Element.Methods.getStyle = function(element, style) {\n    element = $(element);\n    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();\n    var value = element.style[style];\n    if (!value && element.currentStyle) value = element.currentStyle[style];\n\n    if (style == 'opacity') {\n      if (value = (element.getStyle('filter') || '').match(/alpha\\(opacity=(.*)\\)/))\n        if (value[1]) return parseFloat(value[1]) / 100;\n      return 1.0;\n    }\n\n    if (value == 'auto') {\n      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))\n        return element['offset' + style.capitalize()] + 'px';\n      return null;\n    }\n    return value;\n  };\n\n  Element.Methods.setOpacity = function(element, value) {\n    function stripAlpha(filter){\n      return filter.replace(/alpha\\([^\\)]*\\)/gi,'');\n    }\n    element = $(element);\n    var currentStyle = element.currentStyle;\n    if ((currentStyle && !currentStyle.hasLayout) ||\n      (!currentStyle && element.style.zoom == 'normal'))\n        element.style.zoom = 1;\n\n    var filter = element.getStyle('filter'), style = element.style;\n    if (value == 1 || value === '') {\n      (filter = stripAlpha(filter)) ?\n        style.filter = filter : style.removeAttribute('filter');\n      return element;\n    } else if (value < 0.00001) value = 0;\n    style.filter = stripAlpha(filter) +\n      'alpha(opacity=' + (value * 100) + ')';\n    return element;\n  };\n\n  Element._attributeTranslations = {\n    read: {\n      names: {\n        'class': 'className',\n        'for':   'htmlFor'\n      },\n      values: {\n        _getAttr: function(element, attribute) {\n          return element.getAttribute(attribute, 2);\n        },\n        _getAttrNode: function(element, attribute) {\n          var node = element.getAttributeNode(attribute);\n          return node ? node.value : \"\";\n        },\n        _getEv: function(element, attribute) {\n          attribute = element.getAttribute(attribute);\n          return attribute ? attribute.toString().slice(23, -2) : null;\n        },\n        _flag: function(element, attribute) {\n          return $(element).hasAttribute(attribute) ? attribute : null;\n        },\n        style: function(element) {\n          return element.style.cssText.toLowerCase();\n        },\n        title: function(element) {\n          return element.title;\n        }\n      }\n    }\n  };\n\n  Element._attributeTranslations.write = {\n    names: Object.extend({\n      cellpadding: 'cellPadding',\n      cellspacing: 'cellSpacing'\n    }, Element._attributeTranslations.read.names),\n    values: {\n      checked: function(element, value) {\n        element.checked = !!value;\n      },\n\n      style: function(element, value) {\n        element.style.cssText = value ? value : '';\n      }\n    }\n  };\n\n  Element._attributeTranslations.has = {};\n\n  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +\n      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {\n    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;\n    Element._attributeTranslations.has[attr.toLowerCase()] = attr;\n  });\n\n  (function(v) {\n    Object.extend(v, {\n      href:        v._getAttr,\n      src:         v._getAttr,\n      type:        v._getAttr,\n      action:      v._getAttrNode,\n      disabled:    v._flag,\n      checked:     v._flag,\n      readonly:    v._flag,\n      multiple:    v._flag,\n      onload:      v._getEv,\n      onunload:    v._getEv,\n      onclick:     v._getEv,\n      ondblclick:  v._getEv,\n      onmousedown: v._getEv,\n      onmouseup:   v._getEv,\n      onmouseover: v._getEv,\n      onmousemove: v._getEv,\n      onmouseout:  v._getEv,\n      onfocus:     v._getEv,\n      onblur:      v._getEv,\n      onkeypress:  v._getEv,\n      onkeydown:   v._getEv,\n      onkeyup:     v._getEv,\n      onsubmit:    v._getEv,\n      onreset:     v._getEv,\n      onselect:    v._getEv,\n      onchange:    v._getEv\n    });\n  })(Element._attributeTranslations.read.values);\n}\n\nelse if (Prototype.Browser.Gecko && /rv:1\\.8\\.0/.test(navigator.userAgent)) {\n  Element.Methods.setOpacity = function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1) ? 0.999999 :\n      (value === '') ? '' : (value < 0.00001) ? 0 : value;\n    return element;\n  };\n}\n\nelse if (Prototype.Browser.WebKit) {\n  Element.Methods.setOpacity = function(element, value) {\n    element = $(element);\n    element.style.opacity = (value == 1 || value === '') ? '' :\n      (value < 0.00001) ? 0 : value;\n\n    if (value == 1)\n      if(element.tagName.toUpperCase() == 'IMG' && element.width) {\n        element.width++; element.width--;\n      } else try {\n        var n = document.createTextNode(' ');\n        element.appendChild(n);\n        element.removeChild(n);\n      } catch (e) { }\n\n    return element;\n  };\n\n  // Safari returns margins on body which is incorrect if the child is absolutely\n  // positioned.  For performance reasons, redefine Element#cumulativeOffset for\n  // KHTML/WebKit only.\n  Element.Methods.cumulativeOffset = function(element) {\n    var valueT = 0, valueL = 0;\n    do {\n      valueT += element.offsetTop  || 0;\n      valueL += element.offsetLeft || 0;\n      if (element.offsetParent == document.body)\n        if (Element.getStyle(element, 'position') == 'absolute') break;\n\n      element = element.offsetParent;\n    } while (element);\n\n    return Element._returnOffset(valueL, valueT);\n  };\n}\n\nif (Prototype.Browser.IE || Prototype.Browser.Opera) {\n  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements\n  Element.Methods.update = function(element, content) {\n    element = $(element);\n\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) return element.update().insert(content);\n\n    content = Object.toHTML(content);\n    var tagName = element.tagName.toUpperCase();\n\n    if (tagName in Element._insertionTranslations.tags) {\n      $A(element.childNodes).each(function(node) { element.removeChild(node) });\n      Element._getContentFromAnonymousElement(tagName, content.stripScripts())\n        .each(function(node) { element.appendChild(node) });\n    }\n    else element.innerHTML = content.stripScripts();\n\n    content.evalScripts.bind(content).defer();\n    return element;\n  };\n}\n\nif ('outerHTML' in document.createElement('div')) {\n  Element.Methods.replace = function(element, content) {\n    element = $(element);\n\n    if (content && content.toElement) content = content.toElement();\n    if (Object.isElement(content)) {\n      element.parentNode.replaceChild(content, element);\n      return element;\n    }\n\n    content = Object.toHTML(content);\n    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();\n\n    if (Element._insertionTranslations.tags[tagName]) {\n      var nextSibling = element.next();\n      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());\n      parent.removeChild(element);\n      if (nextSibling)\n        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });\n      else\n        fragments.each(function(node) { parent.appendChild(node) });\n    }\n    else element.outerHTML = content.stripScripts();\n\n    content.evalScripts.bind(content).defer();\n    return element;\n  };\n}\n\nElement._returnOffset = function(l, t) {\n  var result = [l, t];\n  result.left = l;\n  result.top = t;\n  return result;\n};\n\nElement._getContentFromAnonymousElement = function(tagName, html) {\n  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];\n  if (t) {\n    div.innerHTML = t[0] + html + t[1];\n    t[2].times(function() { div = div.firstChild });\n  } else div.innerHTML = html;\n  return $A(div.childNodes);\n};\n\nElement._insertionTranslations = {\n  before: function(element, node) {\n    element.parentNode.insertBefore(node, element);\n  },\n  top: function(element, node) {\n    element.insertBefore(node, element.firstChild);\n  },\n  bottom: function(element, node) {\n    element.appendChild(node);\n  },\n  after: function(element, node) {\n    element.parentNode.insertBefore(node, element.nextSibling);\n  },\n  tags: {\n    TABLE:  ['<table>',                '</table>',                   1],\n    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],\n    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],\n    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],\n    SELECT: ['<select>',               '</select>',                  1]\n  }\n};\n\n(function() {\n  Object.extend(this.tags, {\n    THEAD: this.tags.TBODY,\n    TFOOT: this.tags.TBODY,\n    TH:    this.tags.TD\n  });\n}).call(Element._insertionTranslations);\n\nElement.Methods.Simulated = {\n  hasAttribute: function(element, attribute) {\n    attribute = Element._attributeTranslations.has[attribute] || attribute;\n    var node = $(element).getAttributeNode(attribute);\n    return !!(node && node.specified);\n  }\n};\n\nElement.Methods.ByTag = { };\n\nObject.extend(Element, Element.Methods);\n\nif (!Prototype.BrowserFeatures.ElementExtensions &&\n    document.createElement('div')['__proto__']) {\n  window.HTMLElement = { };\n  window.HTMLElement.prototype = document.createElement('div')['__proto__'];\n  Prototype.BrowserFeatures.ElementExtensions = true;\n}\n\nElement.extend = (function() {\n  if (Prototype.BrowserFeatures.SpecificElementExtensions)\n    return Prototype.K;\n\n  var Methods = { }, ByTag = Element.Methods.ByTag;\n\n  var extend = Object.extend(function(element) {\n    if (!element || element._extendedByPrototype ||\n        element.nodeType != 1 || element == window) return element;\n\n    var methods = Object.clone(Methods),\n      tagName = element.tagName.toUpperCase(), property, value;\n\n    // extend methods for specific tags\n    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);\n\n    for (property in methods) {\n      value = methods[property];\n      if (Object.isFunction(value) && !(property in element))\n        element[property] = value.methodize();\n    }\n\n    element._extendedByPrototype = Prototype.emptyFunction;\n    return element;\n\n  }, {\n    refresh: function() {\n      // extend methods for all tags (Safari doesn't need this)\n      if (!Prototype.BrowserFeatures.ElementExtensions) {\n        Object.extend(Methods, Element.Methods);\n        Object.extend(Methods, Element.Methods.Simulated);\n      }\n    }\n  });\n\n  extend.refresh();\n  return extend;\n})();\n\nElement.hasAttribute = function(element, attribute) {\n  if (element.hasAttribute) return element.hasAttribute(attribute);\n  return Element.Methods.Simulated.hasAttribute(element, attribute);\n};\n\nElement.addMethods = function(methods) {\n  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;\n\n  if (!methods) {\n    Object.extend(Form, Form.Methods);\n    Object.extend(Form.Element, Form.Element.Methods);\n    Object.extend(Element.Methods.ByTag, {\n      \"FORM\":     Object.clone(Form.Methods),\n      \"INPUT\":    Object.clone(Form.Element.Methods),\n      \"SELECT\":   Object.clone(Form.Element.Methods),\n      \"TEXTAREA\": Object.clone(Form.Element.Methods)\n    });\n  }\n\n  if (arguments.length == 2) {\n    var tagName = methods;\n    methods = arguments[1];\n  }\n\n  if (!tagName) Object.extend(Element.Methods, methods || { });\n  else {\n    if (Object.isArray(tagName)) tagName.each(extend);\n    else extend(tagName);\n  }\n\n  function extend(tagName) {\n    tagName = tagName.toUpperCase();\n    if (!Element.Methods.ByTag[tagName])\n      Element.Methods.ByTag[tagName] = { };\n    Object.extend(Element.Methods.ByTag[tagName], methods);\n  }\n\n  function copy(methods, destination, onlyIfAbsent) {\n    onlyIfAbsent = onlyIfAbsent || false;\n    for (var property in methods) {\n      var value = methods[property];\n      if (!Object.isFunction(value)) continue;\n      if (!onlyIfAbsent || !(property in destination))\n        destination[property] = value.methodize();\n    }\n  }\n\n  function findDOMClass(tagName) {\n    var klass;\n    var trans = {\n      \"OPTGROUP\": \"OptGroup\", \"TEXTAREA\": \"TextArea\", \"P\": \"Paragraph\",\n      \"FIELDSET\": \"FieldSet\", \"UL\": \"UList\", \"OL\": \"OList\", \"DL\": \"DList\",\n      \"DIR\": \"Directory\", \"H1\": \"Heading\", \"H2\": \"Heading\", \"H3\": \"Heading\",\n      \"H4\": \"Heading\", \"H5\": \"Heading\", \"H6\": \"Heading\", \"Q\": \"Quote\",\n      \"INS\": \"Mod\", \"DEL\": \"Mod\", \"A\": \"Anchor\", \"IMG\": \"Image\", \"CAPTION\":\n      \"TableCaption\", \"COL\": \"TableCol\", \"COLGROUP\": \"TableCol\", \"THEAD\":\n      \"TableSection\", \"TFOOT\": \"TableSection\", \"TBODY\": \"TableSection\", \"TR\":\n      \"TableRow\", \"TH\": \"TableCell\", \"TD\": \"TableCell\", \"FRAMESET\":\n      \"FrameSet\", \"IFRAME\": \"IFrame\"\n    };\n    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';\n    if (window[klass]) return window[klass];\n    klass = 'HTML' + tagName + 'Element';\n    if (window[klass]) return window[klass];\n    klass = 'HTML' + tagName.capitalize() + 'Element';\n    if (window[klass]) return window[klass];\n\n    window[klass] = { };\n    window[klass].prototype = document.createElement(tagName)['__proto__'];\n    return window[klass];\n  }\n\n  if (F.ElementExtensions) {\n    copy(Element.Methods, HTMLElement.prototype);\n    copy(Element.Methods.Simulated, HTMLElement.prototype, true);\n  }\n\n  if (F.SpecificElementExtensions) {\n    for (var tag in Element.Methods.ByTag) {\n      var klass = findDOMClass(tag);\n      if (Object.isUndefined(klass)) continue;\n      copy(T[tag], klass.prototype);\n    }\n  }\n\n  Object.extend(Element, Element.Methods);\n  delete Element.ByTag;\n\n  if (Element.extend.refresh) Element.extend.refresh();\n  Element.cache = { };\n};\n\ndocument.viewport = {\n  getDimensions: function() {\n    var dimensions = { }, B = Prototype.Browser;\n    $w('width height').each(function(d) {\n      var D = d.capitalize();\n      if (B.WebKit && !document.evaluate) {\n        // Safari <3.0 needs self.innerWidth/Height\n        dimensions[d] = self['inner' + D];\n      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {\n        // Opera <9.5 needs document.body.clientWidth/Height\n        dimensions[d] = document.body['client' + D]\n      } else {\n        dimensions[d] = document.documentElement['client' + D];\n      }\n    });\n    return dimensions;\n  },\n\n  getWidth: function() {\n    return this.getDimensions().width;\n  },\n\n  getHeight: function() {\n    return this.getDimensions().height;\n  },\n\n  getScrollOffsets: function() {\n    return Element._returnOffset(\n      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,\n      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);\n  }\n};\n/* Portions of the Selector class are derived from Jack Slocum's DomQuery,\n * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style\n * license.  Please see http://www.yui-ext.com/ for more information. */\n\nvar Selector = Class.create({\n  initialize: function(expression) {\n    this.expression = expression.strip();\n\n    if (this.shouldUseSelectorsAPI()) {\n      this.mode = 'selectorsAPI';\n    } else if (this.shouldUseXPath()) {\n      this.mode = 'xpath';\n      this.compileXPathMatcher();\n    } else {\n      this.mode = \"normal\";\n      this.compileMatcher();\n    }\n\n  },\n\n  shouldUseXPath: function() {\n    if (!Prototype.BrowserFeatures.XPath) return false;\n\n    var e = this.expression;\n\n    // Safari 3 chokes on :*-of-type and :empty\n    if (Prototype.Browser.WebKit &&\n     (e.include(\"-of-type\") || e.include(\":empty\")))\n      return false;\n\n    // XPath can't do namespaced attributes, nor can it read\n    // the \"checked\" property from DOM nodes\n    if ((/(\\[[\\w-]*?:|:checked)/).test(e))\n      return false;\n\n    return true;\n  },\n\n  shouldUseSelectorsAPI: function() {\n    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;\n\n    if (!Selector._div) Selector._div = new Element('div');\n\n    // Make sure the browser treats the selector as valid. Test on an\n    // isolated element to minimize cost of this check.\n    try {\n      Selector._div.querySelector(this.expression);\n    } catch(e) {\n      return false;\n    }\n\n    return true;\n  },\n\n  compileMatcher: function() {\n    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,\n        c = Selector.criteria, le, p, m;\n\n    if (Selector._cache[e]) {\n      this.matcher = Selector._cache[e];\n      return;\n    }\n\n    this.matcher = [\"this.matcher = function(root) {\",\n                    \"var r = root, h = Selector.handlers, c = false, n;\"];\n\n    while (e && le != e && (/\\S/).test(e)) {\n      le = e;\n      for (var i in ps) {\n        p = ps[i];\n        if (m = e.match(p)) {\n          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :\n            new Template(c[i]).evaluate(m));\n          e = e.replace(m[0], '');\n          break;\n        }\n      }\n    }\n\n    this.matcher.push(\"return h.unique(n);\\n}\");\n    eval(this.matcher.join('\\n'));\n    Selector._cache[this.expression] = this.matcher;\n  },\n\n  compileXPathMatcher: function() {\n    var e = this.expression, ps = Selector.patterns,\n        x = Selector.xpath, le, m;\n\n    if (Selector._cache[e]) {\n      this.xpath = Selector._cache[e]; return;\n    }\n\n    this.matcher = ['.//*'];\n    while (e && le != e && (/\\S/).test(e)) {\n      le = e;\n      for (var i in ps) {\n        if (m = e.match(ps[i])) {\n          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :\n            new Template(x[i]).evaluate(m));\n          e = e.replace(m[0], '');\n          break;\n        }\n      }\n    }\n\n    this.xpath = this.matcher.join('');\n    Selector._cache[this.expression] = this.xpath;\n  },\n\n  findElements: function(root) {\n    root = root || document;\n    var e = this.expression, results;\n\n    switch (this.mode) {\n      case 'selectorsAPI':\n        // querySelectorAll queries document-wide, then filters to descendants\n        // of the context element. That's not what we want.\n        // Add an explicit context to the selector if necessary.\n        if (root !== document) {\n          var oldId = root.id, id = $(root).identify();\n          e = \"#\" + id + \" \" + e;\n        }\n\n        results = $A(root.querySelectorAll(e)).map(Element.extend);\n        root.id = oldId;\n\n        return results;\n      case 'xpath':\n        return document._getElementsByXPath(this.xpath, root);\n      default:\n       return this.matcher(root);\n    }\n  },\n\n  match: function(element) {\n    this.tokens = [];\n\n    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;\n    var le, p, m;\n\n    while (e && le !== e && (/\\S/).test(e)) {\n      le = e;\n      for (var i in ps) {\n        p = ps[i];\n        if (m = e.match(p)) {\n          // use the Selector.assertions methods unless the selector\n          // is too complex.\n          if (as[i]) {\n            this.tokens.push([i, Object.clone(m)]);\n            e = e.replace(m[0], '');\n          } else {\n            // reluctantly do a document-wide search\n            // and look for a match in the array\n            return this.findElements(document).include(element);\n          }\n        }\n      }\n    }\n\n    var match = true, name, matches;\n    for (var i = 0, token; token = this.tokens[i]; i++) {\n      name = token[0], matches = token[1];\n      if (!Selector.assertions[name](element, matches)) {\n        match = false; break;\n      }\n    }\n\n    return match;\n  },\n\n  toString: function() {\n    return this.expression;\n  },\n\n  inspect: function() {\n    return \"#<Selector:\" + this.expression.inspect() + \">\";\n  }\n});\n\nObject.extend(Selector, {\n  _cache: { },\n\n  xpath: {\n    descendant:   \"//*\",\n    child:        \"/*\",\n    adjacent:     \"/following-sibling::*[1]\",\n    laterSibling: '/following-sibling::*',\n    tagName:      function(m) {\n      if (m[1] == '*') return '';\n      return \"[local-name()='\" + m[1].toLowerCase() +\n             \"' or local-name()='\" + m[1].toUpperCase() + \"']\";\n    },\n    className:    \"[contains(concat(' ', @class, ' '), ' #{1} ')]\",\n    id:           \"[@id='#{1}']\",\n    attrPresence: function(m) {\n      m[1] = m[1].toLowerCase();\n      return new Template(\"[@#{1}]\").evaluate(m);\n    },\n    attr: function(m) {\n      m[1] = m[1].toLowerCase();\n      m[3] = m[5] || m[6];\n      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);\n    },\n    pseudo: function(m) {\n      var h = Selector.xpath.pseudos[m[1]];\n      if (!h) return '';\n      if (Object.isFunction(h)) return h(m);\n      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);\n    },\n    operators: {\n      '=':  \"[@#{1}='#{3}']\",\n      '!=': \"[@#{1}!='#{3}']\",\n      '^=': \"[starts-with(@#{1}, '#{3}')]\",\n      '$=': \"[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']\",\n      '*=': \"[contains(@#{1}, '#{3}')]\",\n      '~=': \"[contains(concat(' ', @#{1}, ' '), ' #{3} ')]\",\n      '|=': \"[contains(concat('-', @#{1}, '-'), '-#{3}-')]\"\n    },\n    pseudos: {\n      'first-child': '[not(preceding-sibling::*)]',\n      'last-child':  '[not(following-sibling::*)]',\n      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',\n      'empty':       \"[count(*) = 0 and (count(text()) = 0)]\",\n      'checked':     \"[@checked]\",\n      'disabled':    \"[(@disabled) and (@type!='hidden')]\",\n      'enabled':     \"[not(@disabled) and (@type!='hidden')]\",\n      'not': function(m) {\n        var e = m[6], p = Selector.patterns,\n            x = Selector.xpath, le, v;\n\n        var exclusion = [];\n        while (e && le != e && (/\\S/).test(e)) {\n          le = e;\n          for (var i in p) {\n            if (m = e.match(p[i])) {\n              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);\n              exclusion.push(\"(\" + v.substring(1, v.length - 1) + \")\");\n              e = e.replace(m[0], '');\n              break;\n            }\n          }\n        }\n        return \"[not(\" + exclusion.join(\" and \") + \")]\";\n      },\n      'nth-child':      function(m) {\n        return Selector.xpath.pseudos.nth(\"(count(./preceding-sibling::*) + 1) \", m);\n      },\n      'nth-last-child': function(m) {\n        return Selector.xpath.pseudos.nth(\"(count(./following-sibling::*) + 1) \", m);\n      },\n      'nth-of-type':    function(m) {\n        return Selector.xpath.pseudos.nth(\"position() \", m);\n      },\n      'nth-last-of-type': function(m) {\n        return Selector.xpath.pseudos.nth(\"(last() + 1 - position()) \", m);\n      },\n      'first-of-type':  function(m) {\n        m[6] = \"1\"; return Selector.xpath.pseudos['nth-of-type'](m);\n      },\n      'last-of-type':   function(m) {\n        m[6] = \"1\"; return Selector.xpath.pseudos['nth-last-of-type'](m);\n      },\n      'only-of-type':   function(m) {\n        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);\n      },\n      nth: function(fragment, m) {\n        var mm, formula = m[6], predicate;\n        if (formula == 'even') formula = '2n+0';\n        if (formula == 'odd')  formula = '2n+1';\n        if (mm = formula.match(/^(\\d+)$/)) // digit only\n          return '[' + fragment + \"= \" + mm[1] + ']';\n        if (mm = formula.match(/^(-?\\d*)?n(([+-])(\\d+))?/)) { // an+b\n          if (mm[1] == \"-\") mm[1] = -1;\n          var a = mm[1] ? Number(mm[1]) : 1;\n          var b = mm[2] ? Number(mm[2]) : 0;\n          predicate = \"[((#{fragment} - #{b}) mod #{a} = 0) and \" +\n          \"((#{fragment} - #{b}) div #{a} >= 0)]\";\n          return new Template(predicate).evaluate({\n            fragment: fragment, a: a, b: b });\n        }\n      }\n    }\n  },\n\n  criteria: {\n    tagName:      'n = h.tagName(n, r, \"#{1}\", c);      c = false;',\n    className:    'n = h.className(n, r, \"#{1}\", c);    c = false;',\n    id:           'n = h.id(n, r, \"#{1}\", c);           c = false;',\n    attrPresence: 'n = h.attrPresence(n, r, \"#{1}\", c); c = false;',\n    attr: function(m) {\n      m[3] = (m[5] || m[6]);\n      return new Template('n = h.attr(n, r, \"#{1}\", \"#{3}\", \"#{2}\", c); c = false;').evaluate(m);\n    },\n    pseudo: function(m) {\n      if (m[6]) m[6] = m[6].replace(/\"/g, '\\\\\"');\n      return new Template('n = h.pseudo(n, \"#{1}\", \"#{6}\", r, c); c = false;').evaluate(m);\n    },\n    descendant:   'c = \"descendant\";',\n    child:        'c = \"child\";',\n    adjacent:     'c = \"adjacent\";',\n    laterSibling: 'c = \"laterSibling\";'\n  },\n\n  patterns: {\n    // combinators must be listed first\n    // (and descendant needs to be last combinator)\n    laterSibling: /^\\s*~\\s*/,\n    child:        /^\\s*>\\s*/,\n    adjacent:     /^\\s*\\+\\s*/,\n    descendant:   /^\\s/,\n\n    // selectors follow\n    tagName:      /^\\s*(\\*|[\\w\\-]+)(\\b|$)?/,\n    id:           /^#([\\w\\-\\*]+)(\\b|$)/,\n    className:    /^\\.([\\w\\-\\*]+)(\\b|$)/,\n    pseudo:\n/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\\((.*?)\\))?(\\b|$|(?=\\s|[:+~>]))/,\n    attrPresence: /^\\[((?:[\\w]+:)?[\\w]+)\\]/,\n    attr:         /\\[((?:[\\w-]*:)?[\\w-]+)\\s*(?:([!^$*~|]?=)\\s*((['\"])([^\\4]*?)\\4|([^'\"][^\\]]*?)))?\\]/\n  },\n\n  // for Selector.match and Element#match\n  assertions: {\n    tagName: function(element, matches) {\n      return matches[1].toUpperCase() == element.tagName.toUpperCase();\n    },\n\n    className: function(element, matches) {\n      return Element.hasClassName(element, matches[1]);\n    },\n\n    id: function(element, matches) {\n      return element.id === matches[1];\n    },\n\n    attrPresence: function(element, matches) {\n      return Element.hasAttribute(element, matches[1]);\n    },\n\n    attr: function(element, matches) {\n      var nodeValue = Element.readAttribute(element, matches[1]);\n      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);\n    }\n  },\n\n  handlers: {\n    // UTILITY FUNCTIONS\n    // joins two collections\n    concat: function(a, b) {\n      for (var i = 0, node; node = b[i]; i++)\n        a.push(node);\n      return a;\n    },\n\n    // marks an array of nodes for counting\n    mark: function(nodes) {\n      var _true = Prototype.emptyFunction;\n      for (var i = 0, node; node = nodes[i]; i++)\n        node._countedByPrototype = _true;\n      return nodes;\n    },\n\n    unmark: function(nodes) {\n      for (var i = 0, node; node = nodes[i]; i++)\n        node._countedByPrototype = undefined;\n      return nodes;\n    },\n\n    // mark each child node with its position (for nth calls)\n    // \"ofType\" flag indicates whether we're indexing for nth-of-type\n    // rather than nth-child\n    index: function(parentNode, reverse, ofType) {\n      parentNode._countedByPrototype = Prototype.emptyFunction;\n      if (reverse) {\n        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {\n          var node = nodes[i];\n          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;\n        }\n      } else {\n        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)\n          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;\n      }\n    },\n\n    // filters out duplicates and extends all nodes\n    unique: function(nodes) {\n      if (nodes.length == 0) return nodes;\n      var results = [], n;\n      for (var i = 0, l = nodes.length; i < l; i++)\n        if (!(n = nodes[i])._countedByPrototype) {\n          n._countedByPrototype = Prototype.emptyFunction;\n          results.push(Element.extend(n));\n        }\n      return Selector.handlers.unmark(results);\n    },\n\n    // COMBINATOR FUNCTIONS\n    descendant: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        h.concat(results, node.getElementsByTagName('*'));\n      return results;\n    },\n\n    child: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        for (var j = 0, child; child = node.childNodes[j]; j++)\n          if (child.nodeType == 1 && child.tagName != '!') results.push(child);\n      }\n      return results;\n    },\n\n    adjacent: function(nodes) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        var next = this.nextElementSibling(node);\n        if (next) results.push(next);\n      }\n      return results;\n    },\n\n    laterSibling: function(nodes) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        h.concat(results, Element.nextSiblings(node));\n      return results;\n    },\n\n    nextElementSibling: function(node) {\n      while (node = node.nextSibling)\n        if (node.nodeType == 1) return node;\n      return null;\n    },\n\n    previousElementSibling: function(node) {\n      while (node = node.previousSibling)\n        if (node.nodeType == 1) return node;\n      return null;\n    },\n\n    // TOKEN FUNCTIONS\n    tagName: function(nodes, root, tagName, combinator) {\n      var uTagName = tagName.toUpperCase();\n      var results = [], h = Selector.handlers;\n      if (nodes) {\n        if (combinator) {\n          // fastlane for ordinary descendant combinators\n          if (combinator == \"descendant\") {\n            for (var i = 0, node; node = nodes[i]; i++)\n              h.concat(results, node.getElementsByTagName(tagName));\n            return results;\n          } else nodes = this[combinator](nodes);\n          if (tagName == \"*\") return nodes;\n        }\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.tagName.toUpperCase() === uTagName) results.push(node);\n        return results;\n      } else return root.getElementsByTagName(tagName);\n    },\n\n    id: function(nodes, root, id, combinator) {\n      var targetNode = $(id), h = Selector.handlers;\n      if (!targetNode) return [];\n      if (!nodes && root == document) return [targetNode];\n      if (nodes) {\n        if (combinator) {\n          if (combinator == 'child') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (targetNode.parentNode == node) return [targetNode];\n          } else if (combinator == 'descendant') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (Element.descendantOf(targetNode, node)) return [targetNode];\n          } else if (combinator == 'adjacent') {\n            for (var i = 0, node; node = nodes[i]; i++)\n              if (Selector.handlers.previousElementSibling(targetNode) == node)\n                return [targetNode];\n          } else nodes = h[combinator](nodes);\n        }\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node == targetNode) return [targetNode];\n        return [];\n      }\n      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];\n    },\n\n    className: function(nodes, root, className, combinator) {\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      return Selector.handlers.byClassName(nodes, root, className);\n    },\n\n    byClassName: function(nodes, root, className) {\n      if (!nodes) nodes = Selector.handlers.descendant([root]);\n      var needle = ' ' + className + ' ';\n      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {\n        nodeClassName = node.className;\n        if (nodeClassName.length == 0) continue;\n        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))\n          results.push(node);\n      }\n      return results;\n    },\n\n    attrPresence: function(nodes, root, attr, combinator) {\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      var results = [];\n      for (var i = 0, node; node = nodes[i]; i++)\n        if (Element.hasAttribute(node, attr)) results.push(node);\n      return results;\n    },\n\n    attr: function(nodes, root, attr, value, operator, combinator) {\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      var handler = Selector.operators[operator], results = [];\n      for (var i = 0, node; node = nodes[i]; i++) {\n        var nodeValue = Element.readAttribute(node, attr);\n        if (nodeValue === null) continue;\n        if (handler(nodeValue, value)) results.push(node);\n      }\n      return results;\n    },\n\n    pseudo: function(nodes, name, value, root, combinator) {\n      if (nodes && combinator) nodes = this[combinator](nodes);\n      if (!nodes) nodes = root.getElementsByTagName(\"*\");\n      return Selector.pseudos[name](nodes, value, root);\n    }\n  },\n\n  pseudos: {\n    'first-child': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (Selector.handlers.previousElementSibling(node)) continue;\n          results.push(node);\n      }\n      return results;\n    },\n    'last-child': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        if (Selector.handlers.nextElementSibling(node)) continue;\n          results.push(node);\n      }\n      return results;\n    },\n    'only-child': function(nodes, value, root) {\n      var h = Selector.handlers;\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))\n          results.push(node);\n      return results;\n    },\n    'nth-child':        function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root);\n    },\n    'nth-last-child':   function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, true);\n    },\n    'nth-of-type':      function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, false, true);\n    },\n    'nth-last-of-type': function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, formula, root, true, true);\n    },\n    'first-of-type':    function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, \"1\", root, false, true);\n    },\n    'last-of-type':     function(nodes, formula, root) {\n      return Selector.pseudos.nth(nodes, \"1\", root, true, true);\n    },\n    'only-of-type':     function(nodes, formula, root) {\n      var p = Selector.pseudos;\n      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);\n    },\n\n    // handles the an+b logic\n    getIndices: function(a, b, total) {\n      if (a == 0) return b > 0 ? [b] : [];\n      return $R(1, total).inject([], function(memo, i) {\n        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);\n        return memo;\n      });\n    },\n\n    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type\n    nth: function(nodes, formula, root, reverse, ofType) {\n      if (nodes.length == 0) return [];\n      if (formula == 'even') formula = '2n+0';\n      if (formula == 'odd')  formula = '2n+1';\n      var h = Selector.handlers, results = [], indexed = [], m;\n      h.mark(nodes);\n      for (var i = 0, node; node = nodes[i]; i++) {\n        if (!node.parentNode._countedByPrototype) {\n          h.index(node.parentNode, reverse, ofType);\n          indexed.push(node.parentNode);\n        }\n      }\n      if (formula.match(/^\\d+$/)) { // just a number\n        formula = Number(formula);\n        for (var i = 0, node; node = nodes[i]; i++)\n          if (node.nodeIndex == formula) results.push(node);\n      } else if (m = formula.match(/^(-?\\d*)?n(([+-])(\\d+))?/)) { // an+b\n        if (m[1] == \"-\") m[1] = -1;\n        var a = m[1] ? Number(m[1]) : 1;\n        var b = m[2] ? Number(m[2]) : 0;\n        var indices = Selector.pseudos.getIndices(a, b, nodes.length);\n        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {\n          for (var j = 0; j < l; j++)\n            if (node.nodeIndex == indices[j]) results.push(node);\n        }\n      }\n      h.unmark(nodes);\n      h.unmark(indexed);\n      return results;\n    },\n\n    'empty': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++) {\n        // IE treats comments as element nodes\n        if (node.tagName == '!' || node.firstChild) continue;\n        results.push(node);\n      }\n      return results;\n    },\n\n    'not': function(nodes, selector, root) {\n      var h = Selector.handlers, selectorType, m;\n      var exclusions = new Selector(selector).findElements(root);\n      h.mark(exclusions);\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!node._countedByPrototype) results.push(node);\n      h.unmark(exclusions);\n      return results;\n    },\n\n    'enabled': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (!node.disabled && (!node.type || node.type !== 'hidden'))\n          results.push(node);\n      return results;\n    },\n\n    'disabled': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (node.disabled) results.push(node);\n      return results;\n    },\n\n    'checked': function(nodes, value, root) {\n      for (var i = 0, results = [], node; node = nodes[i]; i++)\n        if (node.checked) results.push(node);\n      return results;\n    }\n  },\n\n  operators: {\n    '=':  function(nv, v) { return nv == v; },\n    '!=': function(nv, v) { return nv != v; },\n    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },\n    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },\n    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },\n    '$=': function(nv, v) { return nv.endsWith(v); },\n    '*=': function(nv, v) { return nv.include(v); },\n    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },\n    '|=': function(nv, v) { return ('-' + (nv || \"\").toUpperCase() +\n     '-').include('-' + (v || \"\").toUpperCase() + '-'); }\n  },\n\n  split: function(expression) {\n    var expressions = [];\n    expression.scan(/(([\\w#:.~>+()\\s-]+|\\*|\\[.*?\\])+)\\s*(,|$)/, function(m) {\n      expressions.push(m[1].strip());\n    });\n    return expressions;\n  },\n\n  matchElements: function(elements, expression) {\n    var matches = $$(expression), h = Selector.handlers;\n    h.mark(matches);\n    for (var i = 0, results = [], element; element = elements[i]; i++)\n      if (element._countedByPrototype) results.push(element);\n    h.unmark(matches);\n    return results;\n  },\n\n  findElement: function(elements, expression, index) {\n    if (Object.isNumber(expression)) {\n      index = expression; expression = false;\n    }\n    return Selector.matchElements(elements, expression || '*')[index || 0];\n  },\n\n  findChildElements: function(element, expressions) {\n    expressions = Selector.split(expressions.join(','));\n    var results = [], h = Selector.handlers;\n    for (var i = 0, l = expressions.length, selector; i < l; i++) {\n      selector = new Selector(expressions[i].strip());\n      h.concat(results, selector.findElements(element));\n    }\n    return (l > 1) ? h.unique(results) : results;\n  }\n});\n\nif (Prototype.Browser.IE) {\n  Object.extend(Selector.handlers, {\n    // IE returns comment nodes on getElementsByTagName(\"*\").\n    // Filter them out.\n    concat: function(a, b) {\n      for (var i = 0, node; node = b[i]; i++)\n        if (node.tagName !== \"!\") a.push(node);\n      return a;\n    },\n\n    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.\n    unmark: function(nodes) {\n      for (var i = 0, node; node = nodes[i]; i++)\n        node.removeAttribute('_countedByPrototype');\n      return nodes;\n    }\n  });\n}\n\nfunction $$() {\n  return Selector.findChildElements(document, $A(arguments));\n}\nvar Form = {\n  reset: function(form) {\n    $(form).reset();\n    return form;\n  },\n\n  serializeElements: function(elements, options) {\n    if (typeof options != 'object') options = { hash: !!options };\n    else if (Object.isUndefined(options.hash)) options.hash = true;\n    var key, value, submitted = false, submit = options.submit;\n\n    var data = elements.inject({ }, function(result, element) {\n      if (!element.disabled && element.name) {\n        key = element.name; value = $(element).getValue();\n        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&\n            submit !== false && (!submit || key == submit) && (submitted = true)))) {\n          if (key in result) {\n            // a key is already present; construct an array of values\n            if (!Object.isArray(result[key])) result[key] = [result[key]];\n            result[key].push(value);\n          }\n          else result[key] = value;\n        }\n      }\n      return result;\n    });\n\n    return options.hash ? data : Object.toQueryString(data);\n  }\n};\n\nForm.Methods = {\n  serialize: function(form, options) {\n    return Form.serializeElements(Form.getElements(form), options);\n  },\n\n  getElements: function(form) {\n    return $A($(form).getElementsByTagName('*')).inject([],\n      function(elements, child) {\n        if (Form.Element.Serializers[child.tagName.toLowerCase()])\n          elements.push(Element.extend(child));\n        return elements;\n      }\n    );\n  },\n\n  getInputs: function(form, typeName, name) {\n    form = $(form);\n    var inputs = form.getElementsByTagName('input');\n\n    if (!typeName && !name) return $A(inputs).map(Element.extend);\n\n    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {\n      var input = inputs[i];\n      if ((typeName && input.type != typeName) || (name && input.name != name))\n        continue;\n      matchingInputs.push(Element.extend(input));\n    }\n\n    return matchingInputs;\n  },\n\n  disable: function(form) {\n    form = $(form);\n    Form.getElements(form).invoke('disable');\n    return form;\n  },\n\n  enable: function(form) {\n    form = $(form);\n    Form.getElements(form).invoke('enable');\n    return form;\n  },\n\n  findFirstElement: function(form) {\n    var elements = $(form).getElements().findAll(function(element) {\n      return 'hidden' != element.type && !element.disabled;\n    });\n    var firstByIndex = elements.findAll(function(element) {\n      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;\n    }).sortBy(function(element) { return element.tabIndex }).first();\n\n    return firstByIndex ? firstByIndex : elements.find(function(element) {\n      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());\n    });\n  },\n\n  focusFirstElement: function(form) {\n    form = $(form);\n    form.findFirstElement().activate();\n    return form;\n  },\n\n  request: function(form, options) {\n    form = $(form), options = Object.clone(options || { });\n\n    var params = options.parameters, action = form.readAttribute('action') || '';\n    if (action.blank()) action = window.location.href;\n    options.parameters = form.serialize(true);\n\n    if (params) {\n      if (Object.isString(params)) params = params.toQueryParams();\n      Object.extend(options.parameters, params);\n    }\n\n    if (form.hasAttribute('method') && !options.method)\n      options.method = form.method;\n\n    return new Ajax.Request(action, options);\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nForm.Element = {\n  focus: function(element) {\n    $(element).focus();\n    return element;\n  },\n\n  select: function(element) {\n    $(element).select();\n    return element;\n  }\n};\n\nForm.Element.Methods = {\n  serialize: function(element) {\n    element = $(element);\n    if (!element.disabled && element.name) {\n      var value = element.getValue();\n      if (value != undefined) {\n        var pair = { };\n        pair[element.name] = value;\n        return Object.toQueryString(pair);\n      }\n    }\n    return '';\n  },\n\n  getValue: function(element) {\n    element = $(element);\n    var method = element.tagName.toLowerCase();\n    return Form.Element.Serializers[method](element);\n  },\n\n  setValue: function(element, value) {\n    element = $(element);\n    var method = element.tagName.toLowerCase();\n    Form.Element.Serializers[method](element, value);\n    return element;\n  },\n\n  clear: function(element) {\n    $(element).value = '';\n    return element;\n  },\n\n  present: function(element) {\n    return $(element).value != '';\n  },\n\n  activate: function(element) {\n    element = $(element);\n    try {\n      element.focus();\n      if (element.select && (element.tagName.toLowerCase() != 'input' ||\n          !['button', 'reset', 'submit'].include(element.type)))\n        element.select();\n    } catch (e) { }\n    return element;\n  },\n\n  disable: function(element) {\n    element = $(element);\n    element.disabled = true;\n    return element;\n  },\n\n  enable: function(element) {\n    element = $(element);\n    element.disabled = false;\n    return element;\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nvar Field = Form.Element;\nvar $F = Form.Element.Methods.getValue;\n\n/*--------------------------------------------------------------------------*/\n\nForm.Element.Serializers = {\n  input: function(element, value) {\n    switch (element.type.toLowerCase()) {\n      case 'checkbox':\n      case 'radio':\n        return Form.Element.Serializers.inputSelector(element, value);\n      default:\n        return Form.Element.Serializers.textarea(element, value);\n    }\n  },\n\n  inputSelector: function(element, value) {\n    if (Object.isUndefined(value)) return element.checked ? element.value : null;\n    else element.checked = !!value;\n  },\n\n  textarea: function(element, value) {\n    if (Object.isUndefined(value)) return element.value;\n    else element.value = value;\n  },\n\n  select: function(element, value) {\n    if (Object.isUndefined(value))\n      return this[element.type == 'select-one' ?\n        'selectOne' : 'selectMany'](element);\n    else {\n      var opt, currentValue, single = !Object.isArray(value);\n      for (var i = 0, length = element.length; i < length; i++) {\n        opt = element.options[i];\n        currentValue = this.optionValue(opt);\n        if (single) {\n          if (currentValue == value) {\n            opt.selected = true;\n            return;\n          }\n        }\n        else opt.selected = value.include(currentValue);\n      }\n    }\n  },\n\n  selectOne: function(element) {\n    var index = element.selectedIndex;\n    return index >= 0 ? this.optionValue(element.options[index]) : null;\n  },\n\n  selectMany: function(element) {\n    var values, length = element.length;\n    if (!length) return null;\n\n    for (var i = 0, values = []; i < length; i++) {\n      var opt = element.options[i];\n      if (opt.selected) values.push(this.optionValue(opt));\n    }\n    return values;\n  },\n\n  optionValue: function(opt) {\n    // extend element because hasAttribute may not be native\n    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nAbstract.TimedObserver = Class.create(PeriodicalExecuter, {\n  initialize: function($super, element, frequency, callback) {\n    $super(callback, frequency);\n    this.element   = $(element);\n    this.lastValue = this.getValue();\n  },\n\n  execute: function() {\n    var value = this.getValue();\n    if (Object.isString(this.lastValue) && Object.isString(value) ?\n        this.lastValue != value : String(this.lastValue) != String(value)) {\n      this.callback(this.element, value);\n      this.lastValue = value;\n    }\n  }\n});\n\nForm.Element.Observer = Class.create(Abstract.TimedObserver, {\n  getValue: function() {\n    return Form.Element.getValue(this.element);\n  }\n});\n\nForm.Observer = Class.create(Abstract.TimedObserver, {\n  getValue: function() {\n    return Form.serialize(this.element);\n  }\n});\n\n/*--------------------------------------------------------------------------*/\n\nAbstract.EventObserver = Class.create({\n  initialize: function(element, callback) {\n    this.element  = $(element);\n    this.callback = callback;\n\n    this.lastValue = this.getValue();\n    if (this.element.tagName.toLowerCase() == 'form')\n      this.registerFormCallbacks();\n    else\n      this.registerCallback(this.element);\n  },\n\n  onElementEvent: function() {\n    var value = this.getValue();\n    if (this.lastValue != value) {\n      this.callback(this.element, value);\n      this.lastValue = value;\n    }\n  },\n\n  registerFormCallbacks: function() {\n    Form.getElements(this.element).each(this.registerCallback, this);\n  },\n\n  registerCallback: function(element) {\n    if (element.type) {\n      switch (element.type.toLowerCase()) {\n        case 'checkbox':\n        case 'radio':\n          Event.observe(element, 'click', this.onElementEvent.bind(this));\n          break;\n        default:\n          Event.observe(element, 'change', this.onElementEvent.bind(this));\n          break;\n      }\n    }\n  }\n});\n\nForm.Element.EventObserver = Class.create(Abstract.EventObserver, {\n  getValue: function() {\n    return Form.Element.getValue(this.element);\n  }\n});\n\nForm.EventObserver = Class.create(Abstract.EventObserver, {\n  getValue: function() {\n    return Form.serialize(this.element);\n  }\n});\nif (!window.Event) var Event = { };\n\nObject.extend(Event, {\n  KEY_BACKSPACE: 8,\n  KEY_TAB:       9,\n  KEY_RETURN:   13,\n  KEY_ESC:      27,\n  KEY_LEFT:     37,\n  KEY_UP:       38,\n  KEY_RIGHT:    39,\n  KEY_DOWN:     40,\n  KEY_DELETE:   46,\n  KEY_HOME:     36,\n  KEY_END:      35,\n  KEY_PAGEUP:   33,\n  KEY_PAGEDOWN: 34,\n  KEY_INSERT:   45,\n\n  cache: { },\n\n  relatedTarget: function(event) {\n    var element;\n    switch(event.type) {\n      case 'mouseover': element = event.fromElement; break;\n      case 'mouseout':  element = event.toElement;   break;\n      default: return null;\n    }\n    return Element.extend(element);\n  }\n});\n\nEvent.Methods = (function() {\n  var isButton;\n\n  if (Prototype.Browser.IE) {\n    var buttonMap = { 0: 1, 1: 4, 2: 2 };\n    isButton = function(event, code) {\n      return event.button == buttonMap[code];\n    };\n\n  } else if (Prototype.Browser.WebKit) {\n    isButton = function(event, code) {\n      switch (code) {\n        case 0: return event.which == 1 && !event.metaKey;\n        case 1: return event.which == 1 && event.metaKey;\n        default: return false;\n      }\n    };\n\n  } else {\n    isButton = function(event, code) {\n      return event.which ? (event.which === code + 1) : (event.button === code);\n    };\n  }\n\n  return {\n    isLeftClick:   function(event) { return isButton(event, 0) },\n    isMiddleClick: function(event) { return isButton(event, 1) },\n    isRightClick:  function(event) { return isButton(event, 2) },\n\n    element: function(event) {\n      event = Event.extend(event);\n\n      var node          = event.target,\n          type          = event.type,\n          currentTarget = event.currentTarget;\n\n      if (currentTarget && currentTarget.tagName) {\n        // Firefox screws up the \"click\" event when moving between radio buttons\n        // via arrow keys. It also screws up the \"load\" and \"error\" events on images,\n        // reporting the document as the target instead of the original image.\n        if (type === 'load' || type === 'error' ||\n          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'\n            && currentTarget.type === 'radio'))\n              node = currentTarget;\n      }\n      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;\n      return Element.extend(node);\n    },\n\n    findElement: function(event, expression) {\n      var element = Event.element(event);\n      if (!expression) return element;\n      var elements = [element].concat(element.ancestors());\n      return Selector.findElement(elements, expression, 0);\n    },\n\n    pointer: function(event) {\n      var docElement = document.documentElement,\n      body = document.body || { scrollLeft: 0, scrollTop: 0 };\n      return {\n        x: event.pageX || (event.clientX +\n          (docElement.scrollLeft || body.scrollLeft) -\n          (docElement.clientLeft || 0)),\n        y: event.pageY || (event.clientY +\n          (docElement.scrollTop || body.scrollTop) -\n          (docElement.clientTop || 0))\n      };\n    },\n\n    pointerX: function(event) { return Event.pointer(event).x },\n    pointerY: function(event) { return Event.pointer(event).y },\n\n    stop: function(event) {\n      Event.extend(event);\n      event.preventDefault();\n      event.stopPropagation();\n      event.stopped = true;\n    }\n  };\n})();\n\nEvent.extend = (function() {\n  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {\n    m[name] = Event.Methods[name].methodize();\n    return m;\n  });\n\n  if (Prototype.Browser.IE) {\n    Object.extend(methods, {\n      stopPropagation: function() { this.cancelBubble = true },\n      preventDefault:  function() { this.returnValue = false },\n      inspect: function() { return \"[object Event]\" }\n    });\n\n    return function(event) {\n      if (!event) return false;\n      if (event._extendedByPrototype) return event;\n\n      event._extendedByPrototype = Prototype.emptyFunction;\n      var pointer = Event.pointer(event);\n      Object.extend(event, {\n        target: event.srcElement,\n        relatedTarget: Event.relatedTarget(event),\n        pageX:  pointer.x,\n        pageY:  pointer.y\n      });\n      return Object.extend(event, methods);\n    };\n\n  } else {\n    Event.prototype = Event.prototype || document.createEvent(\"HTMLEvents\")['__proto__'];\n    Object.extend(Event.prototype, methods);\n    return Prototype.K;\n  }\n})();\n\nObject.extend(Event, (function() {\n  var cache = Event.cache;\n\n  function getEventID(element) {\n    if (element._prototypeEventID) return element._prototypeEventID[0];\n    arguments.callee.id = arguments.callee.id || 1;\n    return element._prototypeEventID = [++arguments.callee.id];\n  }\n\n  function getDOMEventName(eventName) {\n    if (eventName && eventName.include(':')) return \"dataavailable\";\n    return eventName;\n  }\n\n  function getCacheForID(id) {\n    return cache[id] = cache[id] || { };\n  }\n\n  function getWrappersForEventName(id, eventName) {\n    var c = getCacheForID(id);\n    return c[eventName] = c[eventName] || [];\n  }\n\n  function createWrapper(element, eventName, handler) {\n    var id = getEventID(element);\n    var c = getWrappersForEventName(id, eventName);\n    if (c.pluck(\"handler\").include(handler)) return false;\n\n    var wrapper = function(event) {\n      if (!Event || !Event.extend ||\n        (event.eventName && event.eventName != eventName))\n          return false;\n\n      Event.extend(event);\n      handler.call(element, event);\n    };\n\n    wrapper.handler = handler;\n    c.push(wrapper);\n    return wrapper;\n  }\n\n  function findWrapper(id, eventName, handler) {\n    var c = getWrappersForEventName(id, eventName);\n    return c.find(function(wrapper) { return wrapper.handler == handler });\n  }\n\n  function destroyWrapper(id, eventName, handler) {\n    var c = getCacheForID(id);\n    if (!c[eventName]) return false;\n    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));\n  }\n\n  function destroyCache() {\n    for (var id in cache)\n      for (var eventName in cache[id])\n        cache[id][eventName] = null;\n  }\n\n\n  // Internet Explorer needs to remove event handlers on page unload\n  // in order to avoid memory leaks.\n  if (window.attachEvent) {\n    window.attachEvent(\"onunload\", destroyCache);\n  }\n\n  // Safari has a dummy event handler on page unload so that it won't\n  // use its bfcache. Safari <= 3.1 has an issue with restoring the \"document\"\n  // object when page is returned to via the back button using its bfcache.\n  if (Prototype.Browser.WebKit) {\n    window.addEventListener('unload', Prototype.emptyFunction, false);\n  }\n\n  return {\n    observe: function(element, eventName, handler) {\n      element = $(element);\n      var name = getDOMEventName(eventName);\n\n      var wrapper = createWrapper(element, eventName, handler);\n      if (!wrapper) return element;\n\n      if (element.addEventListener) {\n        element.addEventListener(name, wrapper, false);\n      } else {\n        element.attachEvent(\"on\" + name, wrapper);\n      }\n\n      return element;\n    },\n\n    stopObserving: function(element, eventName, handler) {\n      element = $(element);\n      var id = getEventID(element), name = getDOMEventName(eventName);\n\n      if (!handler && eventName) {\n        getWrappersForEventName(id, eventName).each(function(wrapper) {\n          element.stopObserving(eventName, wrapper.handler);\n        });\n        return element;\n\n      } else if (!eventName) {\n        Object.keys(getCacheForID(id)).each(function(eventName) {\n          element.stopObserving(eventName);\n        });\n        return element;\n      }\n\n      var wrapper = findWrapper(id, eventName, handler);\n      if (!wrapper) return element;\n\n      if (element.removeEventListener) {\n        element.removeEventListener(name, wrapper, false);\n      } else {\n        element.detachEvent(\"on\" + name, wrapper);\n      }\n\n      destroyWrapper(id, eventName, handler);\n\n      return element;\n    },\n\n    fire: function(element, eventName, memo) {\n      element = $(element);\n      if (element == document && document.createEvent && !element.dispatchEvent)\n        element = document.documentElement;\n\n      var event;\n      if (document.createEvent) {\n        event = document.createEvent(\"HTMLEvents\");\n        event.initEvent(\"dataavailable\", true, true);\n      } else {\n        event = document.createEventObject();\n        event.eventType = \"ondataavailable\";\n      }\n\n      event.eventName = eventName;\n      event.memo = memo || { };\n\n      if (document.createEvent) {\n        element.dispatchEvent(event);\n      } else {\n        element.fireEvent(event.eventType, event);\n      }\n\n      return Event.extend(event);\n    }\n  };\n})());\n\nObject.extend(Event, Event.Methods);\n\nElement.addMethods({\n  fire:          Event.fire,\n  observe:       Event.observe,\n  stopObserving: Event.stopObserving\n});\n\nObject.extend(document, {\n  fire:          Element.Methods.fire.methodize(),\n  observe:       Element.Methods.observe.methodize(),\n  stopObserving: Element.Methods.stopObserving.methodize(),\n  loaded:        false\n});\n\n(function() {\n  /* Support for the DOMContentLoaded event is based on work by Dan Webb,\n     Matthias Miller, Dean Edwards and John Resig. */\n\n  var timer;\n\n  function fireContentLoadedEvent() {\n    if (document.loaded) return;\n    if (timer) window.clearInterval(timer);\n    document.fire(\"dom:loaded\");\n    document.loaded = true;\n  }\n\n  if (document.addEventListener) {\n    if (Prototype.Browser.WebKit) {\n      timer = window.setInterval(function() {\n        if (/loaded|complete/.test(document.readyState))\n          fireContentLoadedEvent();\n      }, 0);\n\n      Event.observe(window, \"load\", fireContentLoadedEvent);\n\n    } else {\n      document.addEventListener(\"DOMContentLoaded\",\n        fireContentLoadedEvent, false);\n    }\n\n  } else {\n    document.write(\"<script id=__onDOMContentLoaded defer src=//:><\\/script>\");\n    $(\"__onDOMContentLoaded\").onreadystatechange = function() {\n      if (this.readyState == \"complete\") {\n        this.onreadystatechange = null;\n        fireContentLoadedEvent();\n      }\n    };\n  }\n})();\n/*------------------------------- DEPRECATED -------------------------------*/\n\nHash.toQueryString = Object.toQueryString;\n\nvar Toggle = { display: Element.toggle };\n\nElement.Methods.childOf = Element.Methods.descendantOf;\n\nvar Insertion = {\n  Before: function(element, content) {\n    return Element.insert(element, {before:content});\n  },\n\n  Top: function(element, content) {\n    return Element.insert(element, {top:content});\n  },\n\n  Bottom: function(element, content) {\n    return Element.insert(element, {bottom:content});\n  },\n\n  After: function(element, content) {\n    return Element.insert(element, {after:content});\n  }\n};\n\nvar $continue = new Error('\"throw $continue\" is deprecated, use \"return\" instead');\n\n// This should be moved to script.aculo.us; notice the deprecated methods\n// further below, that map to the newer Element methods.\nvar Position = {\n  // set to true if needed, warning: firefox performance problems\n  // NOT neeeded for page scrolling, only if draggable contained in\n  // scrollable elements\n  includeScrollOffsets: false,\n\n  // must be called before calling withinIncludingScrolloffset, every time the\n  // page is scrolled\n  prepare: function() {\n    this.deltaX =  window.pageXOffset\n                || document.documentElement.scrollLeft\n                || document.body.scrollLeft\n                || 0;\n    this.deltaY =  window.pageYOffset\n                || document.documentElement.scrollTop\n                || document.body.scrollTop\n                || 0;\n  },\n\n  // caches x/y coordinate pair to use with overlap\n  within: function(element, x, y) {\n    if (this.includeScrollOffsets)\n      return this.withinIncludingScrolloffsets(element, x, y);\n    this.xcomp = x;\n    this.ycomp = y;\n    this.offset = Element.cumulativeOffset(element);\n\n    return (y >= this.offset[1] &&\n            y <  this.offset[1] + element.offsetHeight &&\n            x >= this.offset[0] &&\n            x <  this.offset[0] + element.offsetWidth);\n  },\n\n  withinIncludingScrolloffsets: function(element, x, y) {\n    var offsetcache = Element.cumulativeScrollOffset(element);\n\n    this.xcomp = x + offsetcache[0] - this.deltaX;\n    this.ycomp = y + offsetcache[1] - this.deltaY;\n    this.offset = Element.cumulativeOffset(element);\n\n    return (this.ycomp >= this.offset[1] &&\n            this.ycomp <  this.offset[1] + element.offsetHeight &&\n            this.xcomp >= this.offset[0] &&\n            this.xcomp <  this.offset[0] + element.offsetWidth);\n  },\n\n  // within must be called directly before\n  overlap: function(mode, element) {\n    if (!mode) return 0;\n    if (mode == 'vertical')\n      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /\n        element.offsetHeight;\n    if (mode == 'horizontal')\n      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /\n        element.offsetWidth;\n  },\n\n  // Deprecation layer -- use newer Element methods now (1.5.2).\n\n  cumulativeOffset: Element.Methods.cumulativeOffset,\n\n  positionedOffset: Element.Methods.positionedOffset,\n\n  absolutize: function(element) {\n    Position.prepare();\n    return Element.absolutize(element);\n  },\n\n  relativize: function(element) {\n    Position.prepare();\n    return Element.relativize(element);\n  },\n\n  realOffset: Element.Methods.cumulativeScrollOffset,\n\n  offsetParent: Element.Methods.getOffsetParent,\n\n  page: Element.Methods.viewportOffset,\n\n  clone: function(source, target, options) {\n    options = options || { };\n    return Element.clonePosition(target, source, options);\n  }\n};\n\n/*--------------------------------------------------------------------------*/\n\nif (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){\n  function iter(name) {\n    return name.blank() ? null : \"[contains(concat(' ', @class, ' '), ' \" + name + \" ')]\";\n  }\n\n  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?\n  function(element, className) {\n    className = className.toString().strip();\n    var cond = /\\s/.test(className) ? $w(className).map(iter).join('') : iter(className);\n    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];\n  } : function(element, className) {\n    className = className.toString().strip();\n    var elements = [], classNames = (/\\s/.test(className) ? $w(className) : null);\n    if (!classNames && !className) return elements;\n\n    var nodes = $(element).getElementsByTagName('*');\n    className = ' ' + className + ' ';\n\n    for (var i = 0, child, cn; child = nodes[i]; i++) {\n      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||\n          (classNames && classNames.all(function(name) {\n            return !name.toString().blank() && cn.include(' ' + name + ' ');\n          }))))\n        elements.push(Element.extend(child));\n    }\n    return elements;\n  };\n\n  return function(className, parentElement) {\n    return $(parentElement || document.body).getElementsByClassName(className);\n  };\n}(Element.Methods);\n\n/*--------------------------------------------------------------------------*/\n\nElement.ClassNames = Class.create();\nElement.ClassNames.prototype = {\n  initialize: function(element) {\n    this.element = $(element);\n  },\n\n  _each: function(iterator) {\n    this.element.className.split(/\\s+/).select(function(name) {\n      return name.length > 0;\n    })._each(iterator);\n  },\n\n  set: function(className) {\n    this.element.className = className;\n  },\n\n  add: function(classNameToAdd) {\n    if (this.include(classNameToAdd)) return;\n    this.set($A(this).concat(classNameToAdd).join(' '));\n  },\n\n  remove: function(classNameToRemove) {\n    if (!this.include(classNameToRemove)) return;\n    this.set($A(this).without(classNameToRemove).join(' '));\n  },\n\n  toString: function() {\n    return $A(this).join(' ');\n  }\n};\n\nObject.extend(Element.ClassNames.prototype, Enumerable);\n\n/*--------------------------------------------------------------------------*/\n\nElement.addMethods();"
  },
  {
    "path": "examples/rails_openid/public/robots.txt",
    "content": "# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-Agent: *\n# Disallow: /\n"
  },
  {
    "path": "examples/rails_openid/script/rails",
    "content": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails 3 gems installed from the root of your application.\n\nAPP_PATH = File.expand_path('../../config/application',  __FILE__)\nrequire File.expand_path('../../config/boot',  __FILE__)\nrequire 'rails/commands'\n"
  },
  {
    "path": "examples/rails_openid/test/fixtures/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/test/functional/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/test/functional/login_controller_test.rb",
    "content": "require File.dirname(__FILE__) + '/../test_helper'\nrequire 'login_controller'\n\n# Re-raise errors caught by the controller.\nclass LoginController; def rescue_action(e) raise e end; end\n\nclass LoginControllerTest < Test::Unit::TestCase\n  def setup\n    @controller = LoginController.new\n    @request    = ActionController::TestRequest.new\n    @response   = ActionController::TestResponse.new\n  end\n\n  # Replace this with your real tests.\n  def test_truth\n    assert true\n  end\nend\n"
  },
  {
    "path": "examples/rails_openid/test/functional/server_controller_test.rb",
    "content": "require File.dirname(__FILE__) + '/../test_helper'\nrequire 'server_controller'\n\n# Re-raise errors caught by the controller.\nclass ServerController; def rescue_action(e) raise e end; end\n\nclass ServerControllerTest < Test::Unit::TestCase\n  def setup\n    @controller = ServerController.new\n    @request    = ActionController::TestRequest.new\n    @response   = ActionController::TestResponse.new\n  end\n\n  # Replace this with your real tests.\n  def test_truth\n    assert true\n  end\nend\n"
  },
  {
    "path": "examples/rails_openid/test/integration/.gitkeep",
    "content": ""
  },
  {
    "path": "examples/rails_openid/test/performance/browsing_test.rb",
    "content": "require 'test_helper'\nrequire 'rails/performance_test_help'\n\nclass BrowsingTest < ActionDispatch::PerformanceTest\n  # Refer to the documentation for all available options\n  # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]\n  #                          :output => 'tmp/performance', :formats => [:flat] }\n\n  def test_homepage\n    get '/'\n  end\nend\n"
  },
  {
    "path": "examples/rails_openid/test/test_helper.rb",
    "content": "ENV[\"RAILS_ENV\"] = \"test\"\nrequire File.expand_path('../../config/environment', __FILE__)\nrequire 'rails/test_help'\n\nclass ActiveSupport::TestCase\n  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.\n  #\n  # Note: You'll currently still have to declare fixtures explicitly in integration tests\n  # -- they do not yet inherit this setting\n  fixtures :all\n\n  # Add more helper methods to be used by all tests here...\nend\n"
  },
  {
    "path": "examples/rails_openid/test/unit/.gitkeep",
    "content": ""
  },
  {
    "path": "lib/hmac/hmac.rb",
    "content": "# Copyright (C) 2001  Daiki Ueno <ueno@unixuser.org>\n# This library is distributed under the terms of the Ruby license.\n\n# This module provides common interface to HMAC engines.\n# HMAC standard is documented in RFC 2104:\n#\n#   H. Krawczyk et al., \"HMAC: Keyed-Hashing for Message Authentication\",\n#   RFC 2104, February 1997\n#\n# These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.\n#\n#   <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>\n\nmodule HMAC\n  class Base\n    def initialize(algorithm, block_size, output_length, key)\n      @algorithm = algorithm\n      @block_size = block_size\n      @output_length = output_length\n      @status = STATUS_UNDEFINED\n      @key_xor_ipad = ''\n      @key_xor_opad = ''\n      set_key(key) unless key.nil?\n    end\n\n    private\n    def check_status\n      unless @status == STATUS_INITIALIZED\n\traise RuntimeError,\n\t  \"The underlying hash algorithm has not yet been initialized.\"\n      end\n    end\n\n    public\n    def set_key(key)\n      # If key is longer than the block size, apply hash function\n      # to key and use the result as a real key.\n      key = @algorithm.digest(key) if key.size > @block_size\n      key_xor_ipad = \"\\x36\" * @block_size\n      key_xor_opad = \"\\x5C\" * @block_size\n      for i in 0 .. key.size - 1\n\tkey_xor_ipad[i] ^= key[i]\n\tkey_xor_opad[i] ^= key[i]\n      end\n      @key_xor_ipad = key_xor_ipad\n      @key_xor_opad = key_xor_opad\n      @md = @algorithm.new\n      @status = STATUS_INITIALIZED\n    end\n\n    def reset_key\n      @key_xor_ipad.gsub!(/./, '?')\n      @key_xor_opad.gsub!(/./, '?')\n      @key_xor_ipad[0..-1] = ''\n      @key_xor_opad[0..-1] = ''\n      @status = STATUS_UNDEFINED\n    end\n\n    def update(text)\n      check_status\n      # perform inner H\n      md = @algorithm.new\n      md.update(@key_xor_ipad)\n      md.update(text)\n      str = md.digest\n      # perform outer H\n      md = @algorithm.new\n      md.update(@key_xor_opad)\n      md.update(str)\n      @md = md\n    end\n    alias << update\n\n    def digest\n      check_status\n      @md.digest\n    end\n\n    def hexdigest\n      check_status\n      @md.hexdigest\n    end\n    alias to_s hexdigest\n\n    # These two class methods below are safer than using above\n    # instance methods combinatorially because an instance will have\n    # held a key even if it's no longer in use.\n    def Base.digest(key, text)\n      begin\n\thmac = self.new(key)\n\thmac.update(text)\n\thmac.digest\n      ensure\n\thmac.reset_key\n      end\n    end\n\n    def Base.hexdigest(key, text)\n      begin\n\thmac = self.new(key)\n\thmac.update(text)\n\thmac.hexdigest\n      ensure\n\thmac.reset_key\n      end\n    end\n\n    private_class_method :new, :digest, :hexdigest\n  end\n\n  STATUS_UNDEFINED, STATUS_INITIALIZED = 0, 1\nend\n"
  },
  {
    "path": "lib/hmac/sha1.rb",
    "content": "require 'hmac/hmac'\nrequire 'digest/sha1'\n\nmodule HMAC\n  class SHA1 < Base\n    def initialize(key = nil)\n      super(Digest::SHA1, 64, 20, key)\n    end\n    public_class_method :new, :digest, :hexdigest\n  end\nend\n"
  },
  {
    "path": "lib/hmac/sha2.rb",
    "content": "require 'hmac/hmac'\nrequire 'digest/sha2'\n\nmodule HMAC\n  class SHA256 < Base\n    def initialize(key = nil)\n      super(Digest::SHA256, 64, 32, key)\n    end\n    public_class_method :new, :digest, :hexdigest\n  end\n\n  class SHA384 < Base\n    def initialize(key = nil)\n      super(Digest::SHA384, 128, 48, key)\n    end\n    public_class_method :new, :digest, :hexdigest\n  end\n\n  class SHA512 < Base\n    def initialize(key = nil)\n      super(Digest::SHA512, 128, 64, key)\n    end\n    public_class_method :new, :digest, :hexdigest\n  end\nend\n"
  },
  {
    "path": "lib/openid/association.rb",
    "content": "require \"openid/kvform\"\nrequire \"openid/util\"\nrequire \"openid/cryptutil\"\nrequire \"openid/message\"\n\nmodule OpenID\n\n  def self.get_secret_size(assoc_type)\n    if assoc_type == 'HMAC-SHA1'\n      return 20\n    elsif assoc_type == 'HMAC-SHA256'\n      return 32\n    else\n      raise ArgumentError(\"Unsupported association type: #{assoc_type}\")\n    end\n  end\n\n  # An Association holds the shared secret between a relying party and\n  # an OpenID provider.\n  class Association\n    attr_reader :handle, :secret, :issued, :lifetime, :assoc_type\n\n    FIELD_ORDER =\n      [:version, :handle, :secret, :issued, :lifetime, :assoc_type,]\n\n    # Load a serialized Association\n    def self.deserialize(serialized)\n      parsed = Util.kv_to_seq(serialized)\n      parsed_fields = parsed.map{|k, v| k.to_sym}\n      if parsed_fields != FIELD_ORDER\n          raise ProtocolError, 'Unexpected fields in serialized association'\\\n          \" (Expected #{FIELD_ORDER.inspect}, got #{parsed_fields.inspect})\"\n      end\n      version, handle, secret64, issued_s, lifetime_s, assoc_type =\n        parsed.map {|field, value| value}\n      if version != '2'\n        raise ProtocolError, \"Attempted to deserialize unsupported version \"\\\n                             \"(#{parsed[0][1].inspect})\"\n      end\n\n      self.new(handle,\n               Util.from_base64(secret64),\n               Time.at(issued_s.to_i),\n               lifetime_s.to_i,\n               assoc_type)\n    end\n\n    # Create an Association with an issued time of now\n    def self.from_expires_in(expires_in, handle, secret, assoc_type)\n      issued = Time.now\n      self.new(handle, secret, issued, expires_in, assoc_type)\n    end\n\n    def initialize(handle, secret, issued, lifetime, assoc_type)\n      @handle = handle\n      @secret = secret\n      @issued = issued\n      @lifetime = lifetime\n      @assoc_type = assoc_type\n    end\n\n    # Serialize the association to a form that's consistent across\n    # JanRain OpenID libraries.\n    def serialize\n      data = {\n        :version => '2',\n        :handle => handle,\n        :secret => Util.to_base64(secret),\n        :issued => issued.to_i.to_s,\n        :lifetime => lifetime.to_i.to_s,\n        :assoc_type => assoc_type,\n      }\n\n      Util.assert(data.length == FIELD_ORDER.length)\n\n      pairs = FIELD_ORDER.map{|field| [field.to_s, data[field]]}\n      return Util.seq_to_kv(pairs, true)\n    end\n\n    # The number of seconds until this association expires\n    def expires_in(now=nil)\n      if now.nil?\n        now = Time.now.to_i\n      else\n        now = now.to_i\n      end\n      time_diff = (issued.to_i + lifetime) - now\n      if time_diff < 0\n        return 0\n      else\n        return time_diff\n      end\n    end\n\n    # Generate a signature for a sequence of [key, value] pairs\n    def sign(pairs)\n      kv = Util.seq_to_kv(pairs)\n      case assoc_type\n      when 'HMAC-SHA1'\n        CryptUtil.hmac_sha1(@secret, kv)\n      when 'HMAC-SHA256'\n        CryptUtil.hmac_sha256(@secret, kv)\n      else\n        raise ProtocolError, \"Association has unknown type: \"\\\n          \"#{assoc_type.inspect}\"\n      end\n    end\n\n    # Generate the list of pairs that form the signed elements of the\n    # given message\n    def make_pairs(message)\n      signed = message.get_arg(OPENID_NS, 'signed')\n      if signed.nil?\n        raise ProtocolError, 'Missing signed list'\n      end\n      signed_fields = signed.split(',', -1)\n      data = message.to_post_args\n      signed_fields.map {|field| [field, data.fetch('openid.'+field,'')] }\n    end\n\n    # Return whether the message's signature passes\n    def check_message_signature(message)\n      message_sig = message.get_arg(OPENID_NS, 'sig')\n      if message_sig.nil?\n        raise ProtocolError, \"#{message} has no sig.\"\n      end\n      calculated_sig = get_message_signature(message)\n      return CryptUtil.const_eq(calculated_sig, message_sig)\n    end\n\n    # Get the signature for this message\n    def get_message_signature(message)\n      Util.to_base64(sign(make_pairs(message)))\n    end\n\n    def ==(other)\n      (other.class == self.class and\n       other.handle == self.handle and\n       other.secret == self.secret and\n\n       # The internals of the time objects seemed to differ\n       # in an opaque way when serializing/unserializing.\n       # I don't think this will be a problem.\n       other.issued.to_i == self.issued.to_i and\n\n       other.lifetime == self.lifetime and\n       other.assoc_type == self.assoc_type)\n    end\n\n    # Add a signature (and a signed list) to a message.\n    def sign_message(message)\n      if (message.has_key?(OPENID_NS, 'sig') or\n          message.has_key?(OPENID_NS, 'signed'))\n        raise ArgumentError, 'Message already has signed list or signature'\n      end\n\n      extant_handle = message.get_arg(OPENID_NS, 'assoc_handle')\n      if extant_handle and extant_handle != self.handle\n        raise ArgumentError, \"Message has a different association handle\"\n      end\n\n      signed_message = message.copy()\n      signed_message.set_arg(OPENID_NS, 'assoc_handle', self.handle)\n      message_keys = signed_message.to_post_args.keys()\n\n      signed_list = []\n      message_keys.each { |k|\n        if k.start_with?('openid.')\n          signed_list << k[7..-1]\n        end\n      }\n\n      signed_list << 'signed'\n      signed_list.sort!\n\n      signed_message.set_arg(OPENID_NS, 'signed', signed_list.join(','))\n      sig = get_message_signature(signed_message)\n      signed_message.set_arg(OPENID_NS, 'sig', sig)\n      return signed_message\n    end\n  end\n\n  class AssociationNegotiator\n    attr_reader :allowed_types\n\n    def self.get_session_types(assoc_type)\n      case assoc_type\n      when 'HMAC-SHA1'\n        ['DH-SHA1', 'no-encryption']\n      when 'HMAC-SHA256'\n        ['DH-SHA256', 'no-encryption']\n      else\n        raise ProtocolError, \"Unknown association type #{assoc_type.inspect}\"\n      end\n    end\n\n    def self.check_session_type(assoc_type, session_type)\n      if !get_session_types(assoc_type).include?(session_type)\n        raise ProtocolError, \"Session type #{session_type.inspect} not \"\\\n                             \"valid for association type #{assoc_type.inspect}\"\n      end\n    end\n\n    def initialize(allowed_types)\n      self.allowed_types=(allowed_types)\n    end\n\n    def copy\n      Marshal.load(Marshal.dump(self))\n    end\n\n    def allowed_types=(allowed_types)\n      allowed_types.each do |assoc_type, session_type|\n        self.class.check_session_type(assoc_type, session_type)\n      end\n      @allowed_types = allowed_types\n    end\n\n    def add_allowed_type(assoc_type, session_type=nil)\n      if session_type.nil?\n        session_types = self.class.get_session_types(assoc_type)\n      else\n        self.class.check_session_type(assoc_type, session_type)\n        session_types = [session_type]\n      end\n      for session_type in session_types do\n        @allowed_types << [assoc_type, session_type]\n      end\n    end\n\n    def allowed?(assoc_type, session_type)\n      @allowed_types.include?([assoc_type, session_type])\n    end\n\n    def get_allowed_type\n      @allowed_types.empty? ? nil : @allowed_types[0]\n    end\n  end\n\n  DefaultNegotiator =\n    AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],\n                               ['HMAC-SHA1', 'no-encryption'],\n                               ['HMAC-SHA256', 'DH-SHA256'],\n                               ['HMAC-SHA256', 'no-encryption']])\n\n  EncryptedNegotiator =\n    AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],\n                               ['HMAC-SHA256', 'DH-SHA256']])\nend\n"
  },
  {
    "path": "lib/openid/consumer/associationmanager.rb",
    "content": "require \"openid/dh\"\nrequire \"openid/util\"\nrequire \"openid/kvpost\"\nrequire \"openid/cryptutil\"\nrequire \"openid/protocolerror\"\nrequire \"openid/association\"\n\nmodule OpenID\n  class Consumer\n\n    # A superclass for implementing Diffie-Hellman association sessions.\n    class DiffieHellmanSession\n      class << self\n        attr_reader :session_type, :secret_size, :allowed_assoc_types,\n          :hashfunc\n      end\n\n      def initialize(dh=nil)\n        if dh.nil?\n          dh = DiffieHellman.from_defaults\n        end\n        @dh = dh\n      end\n\n      # Return the query parameters for requesting an association\n      # using this Diffie-Hellman association session\n      def get_request\n        args = {'dh_consumer_public' => CryptUtil.num_to_base64(@dh.public)}\n        if (!@dh.using_default_values?)\n          args['dh_modulus'] = CryptUtil.num_to_base64(@dh.modulus)\n          args['dh_gen'] = CryptUtil.num_to_base64(@dh.generator)\n        end\n\n        return args\n      end\n\n      # Process the response from a successful association request and\n      # return the shared secret for this association\n      def extract_secret(response)\n        dh_server_public64 = response.get_arg(OPENID_NS, 'dh_server_public',\n                                              NO_DEFAULT)\n        enc_mac_key64 = response.get_arg(OPENID_NS, 'enc_mac_key', NO_DEFAULT)\n        dh_server_public = CryptUtil.base64_to_num(dh_server_public64)\n        enc_mac_key = Util.from_base64(enc_mac_key64)\n        return @dh.xor_secret(self.class.hashfunc,\n                              dh_server_public, enc_mac_key)\n      end\n    end\n\n    # A Diffie-Hellman association session that uses SHA1 as its hash\n    # function\n    class DiffieHellmanSHA1Session < DiffieHellmanSession\n      @session_type = 'DH-SHA1'\n      @secret_size = 20\n      @allowed_assoc_types = ['HMAC-SHA1']\n      @hashfunc = CryptUtil.method(:sha1)\n    end\n\n    # A Diffie-Hellman association session that uses SHA256 as its hash\n    # function\n    class DiffieHellmanSHA256Session < DiffieHellmanSession\n      @session_type = 'DH-SHA256'\n      @secret_size = 32\n      @allowed_assoc_types = ['HMAC-SHA256']\n      @hashfunc = CryptUtil.method(:sha256)\n    end\n\n    # An association session that does not use encryption\n    class NoEncryptionSession\n      class << self\n        attr_reader :session_type, :allowed_assoc_types\n      end\n      @session_type = 'no-encryption'\n      @allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256']\n\n      def get_request\n        return {}\n      end\n\n      def extract_secret(response)\n        mac_key64 = response.get_arg(OPENID_NS, 'mac_key', NO_DEFAULT)\n        return Util.from_base64(mac_key64)\n      end\n    end\n\n    # An object that manages creating and storing associations for an\n    # OpenID provider endpoint\n    class AssociationManager\n      def self.create_session(session_type)\n        case session_type\n        when 'no-encryption'\n          NoEncryptionSession.new\n        when 'DH-SHA1'\n          DiffieHellmanSHA1Session.new\n        when 'DH-SHA256'\n          DiffieHellmanSHA256Session.new\n        else\n          raise ArgumentError, \"Unknown association session type: \"\\\n                               \"#{session_type.inspect}\"\n        end\n      end\n\n      def initialize(store, server_url, compatibility_mode=false,\n                     negotiator=nil)\n        @store = store\n        @server_url = server_url\n        @compatibility_mode = compatibility_mode\n        @negotiator = negotiator || DefaultNegotiator\n      end\n\n      def get_association\n        if @store.nil?\n          return nil\n        end\n\n        assoc = @store.get_association(@server_url)\n        if assoc.nil? || assoc.expires_in <= 0\n          assoc = negotiate_association\n          if !assoc.nil?\n            @store.store_association(@server_url, assoc)\n          end\n        end\n\n        return assoc\n      end\n\n      def negotiate_association\n        assoc_type, session_type = @negotiator.get_allowed_type\n        begin\n          return request_association(assoc_type, session_type)\n        rescue ServerError => why\n          supported_types = extract_supported_association_type(why, assoc_type)\n          if !supported_types.nil?\n            # Attempt to create an association from the assoc_type and\n            # session_type that the server told us it supported.\n            assoc_type, session_type = supported_types\n            begin\n              return request_association(assoc_type, session_type)\n            rescue ServerError => why\n              Util.log(\"Server #{@server_url} refused its suggested \" \\\n                       \"association type: session_type=#{session_type}, \" \\\n                       \"assoc_type=#{assoc_type}\")\n              return nil\n            end\n          end\n        rescue InvalidOpenIDNamespace\n          Util.log(\"Server #{@server_url} returned a malformed association \" \\\n                   \"response.  Falling back to check_id mode for this request.\")\n          return nil\n        end\n      end\n\n      protected\n      def extract_supported_association_type(server_error, assoc_type)\n        # Any error message whose code is not 'unsupported-type' should\n        # be considered a total failure.\n        if (server_error.error_code != 'unsupported-type' or\n            server_error.message.is_openid1)\n          Util.log(\"Server error when requesting an association from \"\\\n                   \"#{@server_url}: #{server_error.error_text}\")\n          return nil\n        end\n\n        # The server didn't like the association/session type that we\n        # sent, and it sent us back a message that might tell us how to\n        # handle it.\n        Util.log(\"Unsupported association type #{assoc_type}: \"\\\n                 \"#{server_error.error_text}\")\n\n        # Extract the session_type and assoc_type from the error message\n        assoc_type = server_error.message.get_arg(OPENID_NS, 'assoc_type')\n        session_type = server_error.message.get_arg(OPENID_NS, 'session_type')\n\n        if assoc_type.nil? or session_type.nil?\n          Util.log(\"Server #{@server_url} responded with unsupported \"\\\n                   \"association session but did not supply a fallback.\")\n          return nil\n        elsif !@negotiator.allowed?(assoc_type, session_type)\n          Util.log(\"Server sent unsupported session/association type: \"\\\n                   \"session_type=#{session_type}, assoc_type=#{assoc_type}\")\n          return nil\n        else\n          return [assoc_type, session_type]\n        end\n      end\n\n      # Make and process one association request to this endpoint's OP\n      # endpoint URL. Returns an association object or nil if the\n      # association processing failed. Raises ServerError when the\n      # remote OpenID server returns an error.\n      def request_association(assoc_type, session_type)\n        assoc_session, args = create_associate_request(assoc_type, session_type)\n\n        begin\n          response = OpenID.make_kv_post(args, @server_url)\n          return extract_association(response, assoc_session)\n        rescue HTTPStatusError => why\n          Util.log(\"Got HTTP status error when requesting association: #{why}\")\n          return nil\n        rescue Message::KeyNotFound => why\n          Util.log(\"Missing required parameter in response from \"\\\n                   \"#{@server_url}: #{why}\")\n          return nil\n\n        rescue ProtocolError => why\n          Util.log(\"Protocol error processing response from #{@server_url}: \"\\\n                   \"#{why}\")\n          return nil\n        end\n      end\n\n      # Create an association request for the given assoc_type and\n      # session_type. Returns a pair of the association session object\n      # and the request message that will be sent to the server.\n      def create_associate_request(assoc_type, session_type)\n        assoc_session = self.class.create_session(session_type)\n        args = {\n          'mode' => 'associate',\n          'assoc_type' => assoc_type,\n        }\n\n        if !@compatibility_mode\n          args['ns'] = OPENID2_NS\n        end\n\n        # Leave out the session type if we're in compatibility mode\n        # *and* it's no-encryption.\n        if !@compatibility_mode ||\n            assoc_session.class.session_type != 'no-encryption'\n          args['session_type'] = assoc_session.class.session_type\n        end\n\n        args.merge!(assoc_session.get_request)\n        message = Message.from_openid_args(args)\n        return assoc_session, message\n      end\n\n      # Given an association response message, extract the OpenID 1.X\n      # session type. Returns the association type for this message\n      #\n      # This function mostly takes care of the 'no-encryption' default\n      # behavior in OpenID 1.\n      #\n      # If the association type is plain-text, this function will\n      # return 'no-encryption'\n      def get_openid1_session_type(assoc_response)\n        # If it's an OpenID 1 message, allow session_type to default\n        # to nil (which signifies \"no-encryption\")\n        session_type = assoc_response.get_arg(OPENID_NS, 'session_type')\n\n        # Handle the differences between no-encryption association\n        # respones in OpenID 1 and 2:\n\n        # no-encryption is not really a valid session type for\n        # OpenID 1, but we'll accept it anyway, while issuing a\n        # warning.\n        if session_type == 'no-encryption'\n          Util.log(\"WARNING: #{@server_url} sent 'no-encryption'\"\\\n                   \"for OpenID 1.X\")\n\n        # Missing or empty session type is the way to flag a\n        # 'no-encryption' response. Change the session type to\n        # 'no-encryption' so that it can be handled in the same\n        # way as OpenID 2 'no-encryption' respones.\n        elsif session_type == '' || session_type.nil?\n          session_type = 'no-encryption'\n        end\n\n        return session_type\n      end\n\n      def self.extract_expires_in(message)\n        # expires_in should be a base-10 string.\n        expires_in_str = message.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT)\n        if !(/\\A\\d+\\Z/ =~ expires_in_str)\n          raise ProtocolError, \"Invalid expires_in field: #{expires_in_str}\"\n        end\n        expires_in_str.to_i\n      end\n\n      # Attempt to extract an association from the response, given the\n      # association response message and the established association\n      # session.\n      def extract_association(assoc_response, assoc_session)\n        # Extract the common fields from the response, raising an\n        # exception if they are not found\n        assoc_type = assoc_response.get_arg(OPENID_NS, 'assoc_type',\n                                            NO_DEFAULT)\n        assoc_handle = assoc_response.get_arg(OPENID_NS, 'assoc_handle',\n                                              NO_DEFAULT)\n        expires_in = self.class.extract_expires_in(assoc_response)\n\n        # OpenID 1 has funny association session behaviour.\n        if assoc_response.is_openid1\n            session_type = get_openid1_session_type(assoc_response)\n        else\n          session_type = assoc_response.get_arg(OPENID2_NS, 'session_type',\n                                                NO_DEFAULT)\n        end\n\n        # Session type mismatch\n        if assoc_session.class.session_type != session_type\n          if (assoc_response.is_openid1 and session_type == 'no-encryption')\n            # In OpenID 1, any association request can result in a\n            # 'no-encryption' association response. Setting\n            # assoc_session to a new no-encryption session should\n            # make the rest of this function work properly for\n            # that case.\n            assoc_session = NoEncryptionSession.new\n          else\n            # Any other mismatch, regardless of protocol version\n            # results in the failure of the association session\n            # altogether.\n            raise ProtocolError, \"Session type mismatch. Expected \"\\\n                                 \"#{assoc_session.class.session_type}, got \"\\\n                                 \"#{session_type}\"\n          end\n        end\n\n        # Make sure assoc_type is valid for session_type\n        if !assoc_session.class.allowed_assoc_types.member?(assoc_type)\n          raise ProtocolError, \"Unsupported assoc_type for session \"\\\n                               \"#{assoc_session.class.session_type} \"\\\n                               \"returned: #{assoc_type}\"\n        end\n\n        # Delegate to the association session to extract the secret\n        # from the response, however is appropriate for that session\n        # type.\n        begin\n          secret = assoc_session.extract_secret(assoc_response)\n        rescue Message::KeyNotFound, ArgumentError => why\n          raise ProtocolError, \"Malformed response for \"\\\n                               \"#{assoc_session.class.session_type} \"\\\n                               \"session: #{why.message}\"\n        end\n\n\n        return Association.from_expires_in(expires_in, assoc_handle, secret,\n                                           assoc_type)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer/checkid_request.rb",
    "content": "require \"openid/message\"\nrequire \"openid/util\"\n\nmodule OpenID\n  class Consumer\n    # An object that holds the state necessary for generating an\n    # OpenID authentication request. This object holds the association\n    # with the server and the discovered information with which the\n    # request will be made.\n    #\n    # It is separate from the consumer because you may wish to add\n    # things to the request before sending it on its way to the\n    # server. It also has serialization options that let you encode\n    # the authentication request as a URL or as a form POST.\n    class CheckIDRequest\n      attr_accessor :return_to_args, :message\n      attr_reader :endpoint\n\n      # Users of this library should not create instances of this\n      # class.  Instances of this class are created by the library\n      # when needed.\n      def initialize(assoc, endpoint)\n        @assoc = assoc\n        @endpoint = endpoint\n        @return_to_args = {}\n        @message = Message.new(endpoint.preferred_namespace)\n        @anonymous = false\n      end\n\n      attr_reader :anonymous\n\n      # Set whether this request should be made anonymously. If a\n      # request is anonymous, the identifier will not be sent in the\n      # request. This is only useful if you are making another kind of\n      # request with an extension in this request.\n      #\n      # Anonymous requests are not allowed when the request is made\n      # with OpenID 1.\n      def anonymous=(is_anonymous)\n        if is_anonymous && @message.is_openid1\n          raise ArgumentError, (\"OpenID1 requests MUST include the \"\\\n                                \"identifier in the request\")\n        end\n        @anonymous = is_anonymous\n      end\n\n      # Add an object that implements the extension interface for\n      # adding arguments to an OpenID message to this checkid request.\n      #\n      # extension_request: an OpenID::Extension object.\n      def add_extension(extension_request)\n        extension_request.to_message(@message)\n      end\n\n      # Add an extension argument to this OpenID authentication\n      # request. You probably want to use add_extension and the\n      # OpenID::Extension interface.\n      #\n      # Use caution when adding arguments, because they will be\n      # URL-escaped and appended to the redirect URL, which can easily\n      # get quite long.\n      def add_extension_arg(namespace, key, value)\n        @message.set_arg(namespace, key, value)\n      end\n\n      # Produce a OpenID::Message representing this request.\n      #\n      # Not specifying a return_to URL means that the user will not be\n      # returned to the site issuing the request upon its completion.\n      #\n      # If immediate mode is requested, the OpenID provider is to send\n      # back a response immediately, useful for behind-the-scenes\n      # authentication attempts.  Otherwise the OpenID provider may\n      # engage the user before providing a response.  This is the\n      # default case, as the user may need to provide credentials or\n      # approve the request before a positive response can be sent.\n      def get_message(realm, return_to=nil, immediate=false)\n        if !return_to.nil?\n          return_to = Util.append_args(return_to, @return_to_args)\n        elsif immediate\n          raise ArgumentError, ('\"return_to\" is mandatory when using '\\\n                                '\"checkid_immediate\"')\n        elsif @message.is_openid1\n          raise ArgumentError, ('\"return_to\" is mandatory for OpenID 1 '\\\n                                'requests')\n        elsif @return_to_args.empty?\n          raise ArgumentError, ('extra \"return_to\" arguments were specified, '\\\n                                'but no return_to was specified')\n        end\n\n\n        message = @message.copy\n\n        mode = immediate ? 'checkid_immediate' : 'checkid_setup'\n        message.set_arg(OPENID_NS, 'mode', mode)\n\n        realm_key = message.is_openid1 ? 'trust_root' : 'realm'\n        message.set_arg(OPENID_NS, realm_key, realm)\n\n        if !return_to.nil?\n          message.set_arg(OPENID_NS, 'return_to', return_to)\n        end\n\n        if not @anonymous\n          if @endpoint.is_op_identifier\n            # This will never happen when we're in OpenID 1\n            # compatibility mode, as long as is_op_identifier()\n            # returns false whenever preferred_namespace returns\n            # OPENID1_NS.\n            claimed_id = request_identity = IDENTIFIER_SELECT\n          else\n            request_identity = @endpoint.get_local_id\n            claimed_id = @endpoint.claimed_id\n          end\n\n          # This is true for both OpenID 1 and 2\n          message.set_arg(OPENID_NS, 'identity', request_identity)\n\n          if message.is_openid2\n            message.set_arg(OPENID2_NS, 'claimed_id', claimed_id)\n          end\n        end\n\n        if @assoc && (message.is_openid1 || !['checkid_setup', 'checkid_immediate'].include?(mode))\n          message.set_arg(OPENID_NS, 'assoc_handle', @assoc.handle)\n          assoc_log_msg = \"with assocication #{@assoc.handle}\"\n        else\n          assoc_log_msg = 'using stateless mode.'\n        end\n\n        Util.log(\"Generated #{mode} request to #{@endpoint.server_url} \"\\\n                 \"#{assoc_log_msg}\")\n        return message\n      end\n\n      # Returns a URL with an encoded OpenID request.\n      #\n      # The resulting URL is the OpenID provider's endpoint URL with\n      # parameters appended as query arguments.  You should redirect\n      # the user agent to this URL.\n      #\n      # OpenID 2.0 endpoints also accept POST requests, see\n      # 'send_redirect?' and 'form_markup'.\n      def redirect_url(realm, return_to=nil, immediate=false)\n        message = get_message(realm, return_to, immediate)\n        return message.to_url(@endpoint.server_url)\n      end\n\n      # Get html for a form to submit this request to the IDP.\n      #\n      # form_tag_attrs is a hash of attributes to be added to the form\n      # tag. 'accept-charset' and 'enctype' have defaults that can be\n      # overridden. If a value is supplied for 'action' or 'method',\n      # it will be replaced.\n      def form_markup(realm, return_to=nil, immediate=false,\n                      form_tag_attrs=nil)\n        message = get_message(realm, return_to, immediate)\n        return message.to_form_markup(@endpoint.server_url, form_tag_attrs)\n      end\n\n      # Get a complete HTML document that autosubmits the request to the IDP\n      # with javascript.  This method wraps form_markup - see that method's\n      # documentation for help with the parameters.\n      def html_markup(realm, return_to=nil, immediate=false,\n                      form_tag_attrs=nil)\n        Util.auto_submit_html(form_markup(realm, \n                                          return_to, \n                                          immediate, \n                                          form_tag_attrs))\n      end\n\n      # Should this OpenID authentication request be sent as a HTTP\n      # redirect or as a POST (form submission)?\n      #\n      # This takes the same parameters as redirect_url or form_markup\n      def send_redirect?(realm, return_to=nil, immediate=false)\n        if @endpoint.compatibility_mode\n          return true\n        else\n          url = redirect_url(realm, return_to, immediate)\n          return url.length <= OPENID1_URL_LIMIT\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer/discovery.rb",
    "content": "# Functions to discover OpenID endpoints from identifiers.\n\nrequire 'uri'\nrequire 'openid/util'\nrequire 'openid/fetchers'\nrequire 'openid/urinorm'\nrequire 'openid/message'\nrequire 'openid/yadis/discovery'\nrequire 'openid/yadis/xrds'\nrequire 'openid/yadis/xri'\nrequire 'openid/yadis/services'\nrequire 'openid/yadis/filters'\nrequire 'openid/consumer/html_parse'\nrequire 'openid/yadis/xrires'\n\nmodule OpenID\n\n  OPENID_1_0_NS = 'http://openid.net/xmlns/1.0'\n  OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server'\n  OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon'\n  OPENID_1_1_TYPE = 'http://openid.net/signon/1.1'\n  OPENID_1_0_TYPE = 'http://openid.net/signon/1.0'\n\n  OPENID_1_0_MESSAGE_NS = OPENID1_NS\n  OPENID_2_0_MESSAGE_NS = OPENID2_NS\n\n  # Object representing an OpenID service endpoint.\n  class OpenIDServiceEndpoint\n\n    # OpenID service type URIs, listed in order of preference.  The\n    # ordering of this list affects yadis and XRI service discovery.\n    OPENID_TYPE_URIS = [\n        OPENID_IDP_2_0_TYPE,\n\n        OPENID_2_0_TYPE,\n        OPENID_1_1_TYPE,\n        OPENID_1_0_TYPE,\n        ]\n\n    # the verified identifier.\n    attr_accessor :claimed_id\n\n    # For XRI, the persistent identifier.\n    attr_accessor :canonical_id\n\n    attr_accessor :server_url, :type_uris, :local_id, :used_yadis\n\n    def initialize\n      @claimed_id = nil\n      @server_url = nil\n      @type_uris = []\n      @local_id = nil\n      @canonical_id = nil\n      @used_yadis = false # whether this came from an XRDS\n      @display_identifier = nil\n    end\n\n    def display_identifier\n      return @display_identifier if @display_identifier\n\n      return @claimed_id if @claimed_id.nil? \n\n      begin\n        parsed_identifier = URI.parse(@claimed_id)\n      rescue URI::InvalidURIError\n        raise ProtocolError, \"Claimed identifier #{claimed_id} is not a valid URI\"\n      end\n\n      return @claimed_id if not parsed_identifier.fragment\n\n      disp = parsed_identifier\n      disp.fragment = nil\n\n      return disp.to_s\n    end\n\n    def display_identifier=(display_identifier)\n      @display_identifier = display_identifier\n    end\n\n    def uses_extension(extension_uri)\n      return @type_uris.member?(extension_uri)\n    end\n\n    def preferred_namespace\n      if (@type_uris.member?(OPENID_IDP_2_0_TYPE) or\n          @type_uris.member?(OPENID_2_0_TYPE))\n        return OPENID_2_0_MESSAGE_NS\n      else\n        return OPENID_1_0_MESSAGE_NS\n      end\n    end\n\n    def supports_type(type_uri)\n      # Does this endpoint support this type?\n      #\n      # I consider C{/server} endpoints to implicitly support C{/signon}.\n      (\n       @type_uris.member?(type_uri) or\n       (type_uri == OPENID_2_0_TYPE and is_op_identifier())\n       )\n    end\n\n    def compatibility_mode\n      return preferred_namespace() != OPENID_2_0_MESSAGE_NS\n    end\n\n    def is_op_identifier\n      return @type_uris.member?(OPENID_IDP_2_0_TYPE)\n    end\n\n    def parse_service(yadis_url, uri, type_uris, service_element)\n      # Set the state of this object based on the contents of the\n      # service element.\n      @type_uris = type_uris\n      @server_url = uri\n      @used_yadis = true\n\n      if !is_op_identifier()\n        # XXX: This has crappy implications for Service elements that\n        # contain both 'server' and 'signon' Types.  But that's a\n        # pathological configuration anyway, so I don't think I care.\n        @local_id = OpenID.find_op_local_identifier(service_element,\n                                                    @type_uris)\n        @claimed_id = yadis_url\n      end\n    end\n\n    def get_local_id\n      # Return the identifier that should be sent as the\n      # openid.identity parameter to the server.\n      if @local_id.nil? and @canonical_id.nil?\n        return @claimed_id\n      else\n        return (@local_id or @canonical_id)\n      end\n    end\n\n    def to_session_value\n      Hash[*(instance_variables.map{|name| [name, instance_variable_get(name)] }.flatten(1))]\n    end\n\n    def ==(other)\n      to_session_value == other.to_session_value\n    end\n\n    def self.from_session_value(value)\n      return value unless value.is_a?(Hash)\n\n      self.new.tap do |endpoint|\n        value.each do |name, val|\n          endpoint.instance_variable_set(name, val)\n        end\n      end\n    end\n\n    def self.from_basic_service_endpoint(endpoint)\n      # Create a new instance of this class from the endpoint object\n      # passed in.\n      #\n      # @return: nil or OpenIDServiceEndpoint for this endpoint object\"\"\"\n\n      type_uris = endpoint.match_types(OPENID_TYPE_URIS)\n\n      # If any Type URIs match and there is an endpoint URI specified,\n      # then this is an OpenID endpoint\n      if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?\n        openid_endpoint = self.new\n        openid_endpoint.parse_service(\n                                      endpoint.yadis_url,\n                                      endpoint.uri,\n                                      endpoint.type_uris,\n                                      endpoint.service_element)\n      else\n        openid_endpoint = nil\n      end\n\n      return openid_endpoint\n    end\n\n    def self.from_html(uri, html)\n      # Parse the given document as HTML looking for an OpenID <link\n      # rel=...>\n      #\n      # @rtype: [OpenIDServiceEndpoint]\n\n      discovery_types = [\n                         [OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'],\n                         [OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'],\n                        ]\n\n      link_attrs = OpenID.parse_link_attrs(html)\n      services = []\n      discovery_types.each { |type_uri, op_endpoint_rel, local_id_rel|\n\n        op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)\n\n        if !op_endpoint_url\n          next\n        end\n\n        service = self.new\n        service.claimed_id = uri\n        service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)\n        service.server_url = op_endpoint_url\n        service.type_uris = [type_uri]\n\n        services << service\n      }\n\n      return services\n    end\n\n    def self.from_xrds(uri, xrds)\n      # Parse the given document as XRDS looking for OpenID services.\n      #\n      # @rtype: [OpenIDServiceEndpoint]\n      #\n      # @raises L{XRDSError}: When the XRDS does not parse.\n      return Yadis::apply_filter(uri, xrds, self)\n    end\n\n    def self.from_discovery_result(discoveryResult)\n      # Create endpoints from a DiscoveryResult.\n      #\n      # @type discoveryResult: L{DiscoveryResult}\n      #\n      # @rtype: list of L{OpenIDServiceEndpoint}\n      #\n      # @raises L{XRDSError}: When the XRDS does not parse.\n      if discoveryResult.is_xrds()\n        meth = self.method('from_xrds')\n      else\n        meth = self.method('from_html')\n      end\n\n      return meth.call(discoveryResult.normalized_uri,\n                       discoveryResult.response_text)\n    end\n\n    def self.from_op_endpoint_url(op_endpoint_url)\n      # Construct an OP-Identifier OpenIDServiceEndpoint object for\n      # a given OP Endpoint URL\n      #\n      # @param op_endpoint_url: The URL of the endpoint\n      # @rtype: OpenIDServiceEndpoint\n      service = self.new\n      service.server_url = op_endpoint_url\n      service.type_uris = [OPENID_IDP_2_0_TYPE]\n      return service\n    end\n\n    def to_s\n      return sprintf(\"<%s server_url=%s claimed_id=%s \" +\n                     \"local_id=%s canonical_id=%s used_yadis=%s>\",\n                     self.class, @server_url, @claimed_id,\n                     @local_id, @canonical_id, @used_yadis)\n    end\n  end\n\n  def self.find_op_local_identifier(service_element, type_uris)\n    # Find the OP-Local Identifier for this xrd:Service element.\n    #\n    # This considers openid:Delegate to be a synonym for xrd:LocalID\n    # if both OpenID 1.X and OpenID 2.0 types are present. If only\n    # OpenID 1.X is present, it returns the value of\n    # openid:Delegate. If only OpenID 2.0 is present, it returns the\n    # value of xrd:LocalID. If there is more than one LocalID tag and\n    # the values are different, it raises a DiscoveryFailure. This is\n    # also triggered when the xrd:LocalID and openid:Delegate tags are\n    # different.\n\n    # XXX: Test this function on its own!\n\n    # Build the list of tags that could contain the OP-Local\n    # Identifier\n    local_id_tags = []\n    if type_uris.member?(OPENID_1_1_TYPE) or\n        type_uris.member?(OPENID_1_0_TYPE)\n      # local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')\n      service_element.add_namespace('openid', OPENID_1_0_NS)\n      local_id_tags << \"openid:Delegate\"\n    end\n\n    if type_uris.member?(OPENID_2_0_TYPE)\n      # local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))\n      service_element.add_namespace('xrd', Yadis::XRD_NS_2_0)\n      local_id_tags << \"xrd:LocalID\"\n    end\n\n    # Walk through all the matching tags and make sure that they all\n    # have the same value\n    local_id = nil\n    local_id_tags.each { |local_id_tag|\n      service_element.each_element(local_id_tag) { |local_id_element|\n        if local_id.nil?\n          local_id = local_id_element.text\n        elsif local_id != local_id_element.text\n          format = 'More than one %s tag found in one service element'\n          message = sprintf(format, local_id_tag)\n          raise DiscoveryFailure.new(message, nil)\n        end\n      }\n    }\n\n    return local_id\n  end\n\n  def self.normalize_xri(xri)\n    # Normalize an XRI, stripping its scheme if present\n    m = /^xri:\\/\\/(.*)/.match(xri)\n    xri = m[1] if m\n    return xri\n  end\n\n  def self.normalize_url(url)\n    # Normalize a URL, converting normalization failures to\n    # DiscoveryFailure\n    begin\n      normalized = URINorm.urinorm(url)\n    rescue URI::Error => why\n      raise DiscoveryFailure.new(\"Error normalizing #{url}: #{why.message}\", nil)\n    else\n      defragged = URI::parse(normalized)\n      defragged.fragment = nil\n      return defragged.normalize.to_s\n    end\n  end\n\n  def self.best_matching_service(service, preferred_types)\n    # Return the index of the first matching type, or something higher\n    # if no type matches.\n    #\n    # This provides an ordering in which service elements that contain\n    # a type that comes earlier in the preferred types list come\n    # before service elements that come later. If a service element\n    # has more than one type, the most preferred one wins.\n    preferred_types.each_with_index { |value, index|\n      if service.type_uris.member?(value)\n        return index\n      end\n    }\n\n    return preferred_types.length\n  end\n\n  def self.arrange_by_type(service_list, preferred_types)\n    # Rearrange service_list in a new list so services are ordered by\n    # types listed in preferred_types.  Return the new list.\n\n    # Build a list with the service elements in tuples whose\n    # comparison will prefer the one with the best matching service\n    prio_services = []\n\n    service_list.each_with_index { |s, index|\n      prio_services << [best_matching_service(s, preferred_types), index, s]\n    }\n\n    prio_services.sort!\n\n    # Now that the services are sorted by priority, remove the sort\n    # keys from the list.\n    (0...prio_services.length).each { |i|\n      prio_services[i] = prio_services[i][2]\n    }\n\n    return prio_services\n  end\n\n  def self.get_op_or_user_services(openid_services)\n    # Extract OP Identifier services.  If none found, return the rest,\n    # sorted with most preferred first according to\n    # OpenIDServiceEndpoint.openid_type_uris.\n    #\n    # openid_services is a list of OpenIDServiceEndpoint objects.\n    #\n    # Returns a list of OpenIDServiceEndpoint objects.\n\n    op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])\n\n    openid_services = arrange_by_type(openid_services,\n                                      OpenIDServiceEndpoint::OPENID_TYPE_URIS)\n\n    if !op_services.empty?\n      return op_services\n    else\n      return openid_services\n    end\n  end\n\n  def self.discover_yadis(uri)\n    # Discover OpenID services for a URI. Tries Yadis and falls back\n    # on old-style <link rel='...'> discovery if Yadis fails.\n    #\n    # @param uri: normalized identity URL\n    # @type uri: str\n    # \n    # @return: (claimed_id, services)\n    # @rtype: (str, list(OpenIDServiceEndpoint))\n    #\n    # @raises DiscoveryFailure: when discovery fails.\n\n    # Might raise a yadis.discover.DiscoveryFailure if no document\n    # came back for that URI at all.  I don't think falling back to\n    # OpenID 1.0 discovery on the same URL will help, so don't bother\n    # to catch it.\n    response = Yadis.discover(uri)\n\n    yadis_url = response.normalized_uri\n    body = response.response_text\n\n    begin\n      openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)\n    rescue Yadis::XRDSError\n      # Does not parse as a Yadis XRDS file\n      openid_services = []\n    end\n\n    if openid_services.empty?\n      # Either not an XRDS or there are no OpenID services.\n\n      if response.is_xrds\n        # if we got the Yadis content-type or followed the Yadis\n        # header, re-fetch the document without following the Yadis\n        # header, with no Accept header.\n        return self.discover_no_yadis(uri)\n      end\n\n      # Try to parse the response as HTML.\n      # <link rel=\"...\">\n      openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)\n    end\n\n    return [yadis_url, self.get_op_or_user_services(openid_services)]\n  end\n\n  def self.discover_xri(iname)\n    endpoints = []\n    iname = self.normalize_xri(iname)\n\n    begin\n      canonical_id, services = Yadis::XRI::ProxyResolver.new().query( iname )\n\n      if canonical_id.nil?\n        raise Yadis::XRDSError.new(sprintf('No CanonicalID found for XRI %s', iname))\n      end\n\n      flt = Yadis.make_filter(OpenIDServiceEndpoint)\n\n      services.each { |service_element|\n        endpoints += flt.get_service_endpoints(iname, service_element)\n      }\n    rescue Yadis::XRDSError, Yadis::XRI::XRIHTTPError => why\n      Util.log('xrds error on ' + iname + ': ' + why.to_s)\n    end\n\n    endpoints.each { |endpoint|\n      # Is there a way to pass this through the filter to the endpoint\n      # constructor instead of tacking it on after?\n      endpoint.canonical_id = canonical_id\n      endpoint.claimed_id = canonical_id\n      endpoint.display_identifier = iname\n    }\n\n    # FIXME: returned xri should probably be in some normal form\n    return [iname, self.get_op_or_user_services(endpoints)]\n  end\n\n  def self.discover_no_yadis(uri)\n    http_resp = OpenID.fetch(uri)\n    if http_resp.code != \"200\" and http_resp.code != \"206\"\n      raise DiscoveryFailure.new(\n        \"HTTP Response status from identity URL host is not \\\"200\\\". \"\\\n        \"Got status #{http_resp.code.inspect}\", http_resp)\n    end\n\n    claimed_id = http_resp.final_url\n    openid_services = OpenIDServiceEndpoint.from_html(\n        claimed_id, http_resp.body)\n    return [claimed_id, openid_services]\n  end\n\n  def self.discover_uri(uri)\n    # Hack to work around URI parsing for URls with *no* scheme.\n    if uri.index(\"://\").nil?\n      uri = 'http://' + uri\n    end\n\n    begin\n      parsed = URI::parse(uri)\n    rescue URI::InvalidURIError => why\n      raise DiscoveryFailure.new(\"URI is not valid: #{why.message}\", nil)\n    end\n\n    if !parsed.scheme.nil? and !parsed.scheme.empty?\n      if !['http', 'https'].member?(parsed.scheme)\n        raise DiscoveryFailure.new(\n                \"URI scheme #{parsed.scheme} is not HTTP or HTTPS\", nil)\n      end\n    end\n\n    uri = self.normalize_url(uri)\n    claimed_id, openid_services = self.discover_yadis(uri)\n    claimed_id = self.normalize_url(claimed_id)\n    return [claimed_id, openid_services]\n  end\n\n  def self.discover(identifier)\n    if Yadis::XRI::identifier_scheme(identifier) == :xri\n      discover_xri(identifier)\n    else\n      return discover_uri(identifier)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer/discovery_manager.rb",
    "content": "module OpenID\n  class Consumer\n\n    # A set of discovered services, for tracking which providers have\n    # been attempted for an OpenID identifier\n    class DiscoveredServices\n      attr_reader :current\n\n      def initialize(starting_url, yadis_url, services)\n        @starting_url = starting_url\n        @yadis_url = yadis_url\n        @services = services.dup\n        @current = nil\n      end\n\n      def next\n        @current = @services.shift\n      end\n\n      def for_url?(url)\n        [@starting_url, @yadis_url].member?(url)\n      end\n\n      def started?\n        !@current.nil?\n      end\n\n      def empty?\n        @services.empty?\n      end\n\n      def to_session_value\n        services = @services.map{|s| s.respond_to?(:to_session_value) ? s.to_session_value : s }\n        current_val = @current.respond_to?(:to_session_value) ? @current.to_session_value : @current\n\n        {\n          'starting_url' => @starting_url,\n          'yadis_url' => @yadis_url,\n          'services' => services,\n          'current' => current_val\n        }\n      end\n\n      def ==(other)\n        to_session_value == other.to_session_value\n      end\n\n      def self.from_session_value(value)\n        return value unless value.is_a?(Hash)\n\n        services = value['services'].map{|s| OpenID::OpenIDServiceEndpoint.from_session_value(s) }\n        current = OpenID::OpenIDServiceEndpoint.from_session_value(value['current'])\n\n        obj = self.new(value['starting_url'], value['yadis_url'], services)\n        obj.instance_variable_set(\"@current\", current)\n        obj\n      end\n    end\n\n    # Manages calling discovery and tracking which endpoints have\n    # already been attempted.\n    class DiscoveryManager\n      def initialize(session, url, session_key_suffix=nil)\n        @url = url\n\n        @session = OpenID::Consumer::Session.new(session, DiscoveredServices)\n        @session_key_suffix = session_key_suffix || 'auth'\n      end\n\n      def get_next_service\n        manager = get_manager\n        if !manager.nil? && manager.empty?\n          destroy_manager\n          manager = nil\n        end\n\n        if manager.nil?\n          yadis_url, services = yield @url\n          manager = create_manager(yadis_url, services)\n        end\n\n        if !manager.nil?\n          service = manager.next\n          store(manager)\n        else\n          service = nil\n        end\n\n        return service\n      end\n\n      def cleanup(force=false)\n        manager = get_manager(force)\n        if !manager.nil?\n          service = manager.current\n          destroy_manager(force)\n        else\n          service = nil\n        end\n        return service\n      end\n\n      protected\n\n      def get_manager(force=false)\n        manager = load\n        if force || manager.nil? || manager.for_url?(@url)\n          return manager\n        else\n          return nil\n        end\n      end\n\n      def create_manager(yadis_url, services)\n        manager = get_manager\n        if !manager.nil?\n          raise StandardError, \"There is already a manager for #{yadis_url}\"\n        end\n        if services.empty?\n          return nil\n        end\n        manager = DiscoveredServices.new(@url, yadis_url, services)\n        store(manager)\n        return manager\n      end\n\n      def destroy_manager(force=false)\n        if !get_manager(force).nil?\n          destroy!\n        end\n      end\n\n      def session_key\n        'OpenID::Consumer::DiscoveredServices::' + @session_key_suffix\n      end\n\n      def store(manager)\n        @session[session_key] = manager\n      end\n\n      def load\n        @session[session_key]\n      end\n\n      def destroy!\n        @session[session_key] = nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer/html_parse.rb",
    "content": "require \"openid/yadis/htmltokenizer\"\n\nmodule OpenID\n\n  # Stuff to remove before we start looking for tags\n  REMOVED_RE = /\n    # Comments\n    <!--.*?-->\n\n    # CDATA blocks\n  | <!\\[CDATA\\[.*?\\]\\]>\n\n    # script blocks\n  | <script\\b\n\n    # make sure script is not an XML namespace\n    (?!:)\n\n    [^>]*>.*?<\\/script>\n\n  /mix\n\n  def OpenID.openid_unescape(s)\n    s.gsub('&amp;','&').gsub('&lt;','<').gsub('&gt;','>').gsub('&quot;','\"')\n  end\n\n  def OpenID.unescape_hash(h)\n    newh = {}\n    h.map{|k,v|\n      newh[k]=openid_unescape(v)\n    }\n    newh\n  end\n\n\n  def OpenID.parse_link_attrs(html)\n    begin\n      stripped = html.gsub(REMOVED_RE,'')\n    rescue ArgumentError\n      begin\n        stripped = html.encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace, :replace => '').gsub(REMOVED_RE,'')\n      rescue Encoding::UndefinedConversionError, Encoding::ConverterNotFoundError\n        # needed for a problem in JRuby where it can't handle the conversion.\n        # see details here: https://github.com/jruby/jruby/issues/829\n        stripped = html.encode('UTF-8', 'ASCII', :invalid => :replace, :undef => :replace, :replace => '').gsub(REMOVED_RE,'')\n      end\n    end\n    parser = HTMLTokenizer.new(stripped)\n\n    links = []\n    # to keep track of whether or not we are in the head element\n    in_head = false\n    in_html = false\n    saw_head = false\n\n    begin\n      while el = parser.getTag('head', '/head', 'link', 'body', '/body', \n                               'html', '/html')\n        \n        # we are leaving head or have reached body, so we bail\n        return links if ['/head', 'body', '/body', '/html'].member?(el.tag_name)\n\n        # enforce html > head > link\n        if el.tag_name == 'html'\n          in_html = true\n        end\n        next unless in_html\n        if el.tag_name == 'head'\n          if saw_head\n            return links #only allow one head\n          end\n          saw_head = true\n          unless el.to_s[-2] == 47 # tag ends with a /: a short tag\n            in_head = true\n          end\n        end\n        next unless in_head\n\n        return links if el.tag_name == 'html'\n\n        if el.tag_name == 'link'\n          links << unescape_hash(el.attr_hash)\n        end\n        \n      end\n    rescue Exception # just stop parsing if there's an error\n    end\n    return links\n  end\n\n  def OpenID.rel_matches(rel_attr, target_rel)\n    # Does this target_rel appear in the rel_str?\n    # XXX: TESTME\n    rels = rel_attr.strip().split()\n    rels.each { |rel|\n      rel = rel.downcase\n      if rel == target_rel\n        return true\n      end\n    }\n\n    return false\n  end\n\n  def OpenID.link_has_rel(link_attrs, target_rel)\n    # Does this link have target_rel as a relationship?\n\n    # XXX: TESTME\n    rel_attr = link_attrs['rel']\n    return (rel_attr and rel_matches(rel_attr, target_rel))\n  end\n\n  def OpenID.find_links_rel(link_attrs_list, target_rel)\n    # Filter the list of link attributes on whether it has target_rel\n    # as a relationship.\n\n    # XXX: TESTME\n    matchesTarget = lambda { |attrs| link_has_rel(attrs, target_rel) }\n    result = []\n\n    link_attrs_list.each { |item|\n      if matchesTarget.call(item)\n        result << item\n      end\n    }\n\n    return result\n  end\n\n  def OpenID.find_first_href(link_attrs_list, target_rel)\n    # Return the value of the href attribute for the first link tag in\n    # the list that has target_rel as a relationship.\n\n    # XXX: TESTME\n    matches = find_links_rel(link_attrs_list, target_rel)\n    if !matches or matches.empty?\n      return nil\n    end\n\n    first = matches[0]\n    return first['href']\n  end\nend\n\n"
  },
  {
    "path": "lib/openid/consumer/idres.rb",
    "content": "require \"openid/message\"\nrequire \"openid/protocolerror\"\nrequire \"openid/kvpost\"\nrequire \"openid/consumer/discovery\"\nrequire \"openid/urinorm\"\n\nmodule OpenID\n  class TypeURIMismatch < ProtocolError\n    attr_reader :type_uri, :endpoint\n\n    def initialize(type_uri, endpoint)\n      @type_uri = type_uri\n      @endpoint = endpoint\n    end\n  end\n\n  class Consumer\n    @openid1_return_to_nonce_name = 'rp_nonce'\n    @openid1_return_to_claimed_id_name = 'openid1_claimed_id'\n\n    # Set the name of the query parameter that this library will use\n    # to thread a nonce through an OpenID 1 transaction. It will be\n    # appended to the return_to URL.\n    def self.openid1_return_to_nonce_name=(query_arg_name)\n      @openid1_return_to_nonce_name = query_arg_name\n    end\n\n    # See openid1_return_to_nonce_name= documentation\n    def self.openid1_return_to_nonce_name\n      @openid1_return_to_nonce_name\n    end\n\n    # Set the name of the query parameter that this library will use\n    # to thread the requested URL through an OpenID 1 transaction (for\n    # use when verifying discovered information). It will be appended\n    # to the return_to URL.\n    def self.openid1_return_to_claimed_id_name=(query_arg_name)\n      @openid1_return_to_claimed_id_name = query_arg_name\n    end\n\n    # See openid1_return_to_claimed_id_name=\n    def self.openid1_return_to_claimed_id_name\n      @openid1_return_to_claimed_id_name\n    end\n\n    # Handles an openid.mode=id_res response. This object is\n    # instantiated and used by the Consumer.\n    class IdResHandler\n      attr_reader :endpoint, :message\n\n      def initialize(message, current_url, store=nil, endpoint=nil)\n        @store = store # Fer the nonce and invalidate_handle\n        @message = message\n        @endpoint = endpoint\n        @current_url = current_url\n        @signed_list = nil\n\n        # Start the verification process\n        id_res\n      end\n\n      def signed_fields\n        signed_list.map {|x| 'openid.' + x}\n      end\n\n      protected\n\n      # This method will raise ProtocolError unless the request is a\n      # valid id_res response. Once it has been verified, the methods\n      # 'endpoint', 'message', and 'signed_fields' contain the\n      # verified information.\n      def id_res\n        check_for_fields\n        check_signature\n        check_nonce\n        verify_return_to\n        verify_discovery_results\n      end\n\n      def server_url\n        @endpoint.nil? ? nil : @endpoint.server_url\n      end\n\n      def openid_namespace\n        @message.get_openid_namespace\n      end\n\n      def fetch(field, default=NO_DEFAULT)\n        @message.get_arg(OPENID_NS, field, default)\n      end\n\n      def signed_list\n        if @signed_list.nil?\n          signed_list_str = fetch('signed', nil)\n          if signed_list_str.nil?\n            raise ProtocolError, 'Response missing signed list'\n          end\n\n          @signed_list = signed_list_str.split(',', -1)\n        end\n        @signed_list\n      end\n\n      def check_for_fields\n        # XXX: if a field is missing, we should not have to explicitly\n        # check that it's present, just make sure that the fields are\n        # actually being used by the rest of the code in\n        # tests. Although, which fields are signed does need to be\n        # checked somewhere.\n        basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']\n        basic_sig_fields = ['return_to', 'identity']\n\n        case openid_namespace\n        when OPENID2_NS\n          require_fields = basic_fields + ['op_endpoint']\n          require_sigs = basic_sig_fields +\n            ['response_nonce', 'claimed_id', 'assoc_handle', 'op_endpoint']\n        when OPENID1_NS, OPENID11_NS\n          require_fields = basic_fields + ['identity']\n          require_sigs = basic_sig_fields\n        else\n          raise RuntimeError, \"check_for_fields doesn't know about \"\\\n                              \"namespace #{openid_namespace.inspect}\"\n        end\n\n        require_fields.each do |field|\n          if !@message.has_key?(OPENID_NS, field)\n            raise ProtocolError, \"Missing required field #{field}\"\n          end\n        end\n\n        require_sigs.each do |field|\n          # Field is present and not in signed list\n          if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)\n            raise ProtocolError, \"#{field.inspect} not signed\"\n          end\n        end\n      end\n\n      def verify_return_to\n        begin\n          msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))\n        rescue URI::InvalidURIError\n          raise ProtocolError, (\"return_to is not a valid URI\")\n        end\n\n        verify_return_to_args(msg_return_to)\n        if !@current_url.nil?\n          verify_return_to_base(msg_return_to)\n        end\n      end\n\n      def verify_return_to_args(msg_return_to)\n        return_to_parsed_query = {}\n        if !msg_return_to.query.nil?\n          CGI.parse(msg_return_to.query).each_pair do |k, vs|\n            return_to_parsed_query[k] = vs[0]\n          end\n        end\n        query = @message.to_post_args\n        return_to_parsed_query.each_pair do |rt_key, rt_val|\n          msg_val = query[rt_key]\n          if msg_val.nil? && !rt_val.nil?\n            raise ProtocolError, \"Message missing return_to argument '#{rt_key}'\"\n          elsif msg_val != rt_val\n            raise ProtocolError, (\"Parameter '#{rt_key}' value \"\\\n                                  \"#{msg_val.inspect} does not match \"\\\n                                  \"return_to's value #{rt_val.inspect}\")\n          end\n        end\n        @message.get_args(BARE_NS).each_pair do |bare_key, bare_val|\n          rt_val = return_to_parsed_query[bare_key]\n          if not return_to_parsed_query.has_key? bare_key\n            # This may be caused by your web framework throwing extra\n            # entries in to your parameters hash that were not GET or\n            # POST parameters.  For example, Rails has been known to\n            # add \"controller\" and \"action\" keys; another server adds\n            # at least a \"format\" key.\n            raise ProtocolError, (\"Unexpected parameter (not on return_to): \"\\\n                                  \"'#{bare_key}'=#{rt_val.inspect})\")\n          end\n          if rt_val != bare_val\n            raise ProtocolError, (\"Parameter '#{bare_key}' value \"\\\n                                  \"#{bare_val.inspect} does not match \"\\\n                                  \"return_to's value #{rt_val.inspect}\")\n          end\n        end\n      end\n\n      def verify_return_to_base(msg_return_to)\n        begin\n          app_parsed = URI.parse(URINorm::urinorm(@current_url))\n        rescue URI::InvalidURIError\n          raise ProtocolError, \"current_url is not a valid URI: #{@current_url}\"\n        end\n\n        [:scheme, :host, :port, :path].each do |meth|\n          if msg_return_to.send(meth) != app_parsed.send(meth)\n            raise ProtocolError, \"return_to #{meth.to_s} does not match\"\n          end\n        end\n      end\n\n      # Raises ProtocolError if the signature is bad\n      def check_signature\n        if @store.nil?\n          assoc = nil\n        else\n          assoc = @store.get_association(server_url, fetch('assoc_handle'))\n        end\n\n        if assoc.nil?\n          check_auth\n        else\n          if assoc.expires_in <= 0\n            # XXX: It might be a good idea sometimes to re-start the\n            # authentication with a new association. Doing it\n            # automatically opens the possibility for\n            # denial-of-service by a server that just returns expired\n            # associations (or really short-lived associations)\n            raise ProtocolError, \"Association with #{server_url} expired\"\n          elsif !assoc.check_message_signature(@message)\n            raise ProtocolError, \"Bad signature in response from #{server_url}\"\n          end\n        end\n      end\n\n      def check_auth\n        Util.log(\"Using 'check_authentication' with #{server_url}\")\n        begin\n          request = create_check_auth_request\n        rescue Message::KeyNotFound => why\n          raise ProtocolError, \"Could not generate 'check_authentication' \"\\\n                               \"request: #{why.message}\"\n        end\n\n        response = OpenID.make_kv_post(request, server_url)\n\n        process_check_auth_response(response)\n      end\n\n      def create_check_auth_request\n        signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')\n\n        # check that we got all the signed arguments\n        signed_list.each {|k|\n          @message.get_aliased_arg(k, NO_DEFAULT)\n        }\n\n        ca_message = @message.copy\n        ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')\n\n        return ca_message\n      end\n\n      # Process the response message from a check_authentication\n      # request, invalidating associations if requested.\n      def process_check_auth_response(response)\n        is_valid = response.get_arg(OPENID_NS, 'is_valid', 'false')\n\n        invalidate_handle = response.get_arg(OPENID_NS, 'invalidate_handle')\n        if !invalidate_handle.nil?\n          Util.log(\"Received 'invalidate_handle' from server #{server_url}\")\n          if @store.nil?\n            Util.log('Unexpectedly got \"invalidate_handle\" without a store!')\n          else\n            @store.remove_association(server_url, invalidate_handle)\n          end\n        end\n\n        if is_valid != 'true'\n          raise ProtocolError, (\"Server #{server_url} responds that the \"\\\n                                \"'check_authentication' call is not valid\")\n        end\n      end\n\n      def check_nonce\n        case openid_namespace\n        when OPENID1_NS, OPENID11_NS\n          nonce =\n            @message.get_arg(BARE_NS, Consumer.openid1_return_to_nonce_name)\n\n          # We generated the nonce, so it uses the empty string as the\n          # server URL\n          server_url = ''\n        when OPENID2_NS\n          nonce = @message.get_arg(OPENID2_NS, 'response_nonce')\n          server_url = self.server_url\n        else\n          raise StandardError, 'Not reached'\n        end\n\n        if nonce.nil?\n          raise ProtocolError, 'Nonce missing from response'\n        end\n\n        begin\n          time, extra = Nonce.split_nonce(nonce)\n        rescue ArgumentError\n          raise ProtocolError, \"Malformed nonce: #{nonce.inspect}\"\n        end\n\n        if !@store.nil? && !@store.use_nonce(server_url, time, extra)\n          raise ProtocolError, (\"Nonce already used or out of range: \"\\\n                               \"#{nonce.inspect}\")\n        end\n      end\n\n      def verify_discovery_results\n        begin\n          case openid_namespace\n          when OPENID1_NS, OPENID11_NS\n            verify_discovery_results_openid1\n          when OPENID2_NS\n            verify_discovery_results_openid2\n          else\n            raise StandardError, \"Not reached: #{openid_namespace}\"\n          end\n        rescue Message::KeyNotFound => why\n          raise ProtocolError, \"Missing required field: #{why.message}\"\n        end\n      end\n\n      def verify_discovery_results_openid2\n        to_match = OpenIDServiceEndpoint.new\n        to_match.type_uris = [OPENID_2_0_TYPE]\n        to_match.claimed_id = fetch('claimed_id', nil)\n        to_match.local_id = fetch('identity', nil)\n        to_match.server_url = fetch('op_endpoint')\n\n        if to_match.claimed_id.nil? && !to_match.local_id.nil?\n          raise ProtocolError, ('openid.identity is present without '\\\n                                'openid.claimed_id')\n        elsif !to_match.claimed_id.nil? && to_match.local_id.nil?\n          raise ProtocolError, ('openid.claimed_id is present without '\\\n                                'openid.identity')\n\n        # This is a response without identifiers, so there's really no\n        # checking that we can do, so return an endpoint that's for\n        # the specified `openid.op_endpoint'\n        elsif to_match.claimed_id.nil?\n          @endpoint =\n            OpenIDServiceEndpoint.from_op_endpoint_url(to_match.server_url)\n          return\n        end\n\n        if @endpoint.nil?\n          Util.log('No pre-discovered information supplied')\n          discover_and_verify(to_match.claimed_id, [to_match])\n        else\n          begin\n            verify_discovery_single(@endpoint, to_match)\n          rescue ProtocolError => why\n            Util.log(\"Error attempting to use stored discovery \"\\\n                     \"information: #{why.message}\")\n            Util.log(\"Attempting discovery to verify endpoint\")\n            discover_and_verify(to_match.claimed_id, [to_match])\n          end\n        end\n\n        if @endpoint.claimed_id != to_match.claimed_id\n          @endpoint = @endpoint.dup\n          @endpoint.claimed_id = to_match.claimed_id\n        end\n      end\n\n      def verify_discovery_results_openid1\n        claimed_id =\n          @message.get_arg(BARE_NS, Consumer.openid1_return_to_claimed_id_name)\n\n        if claimed_id.nil?\n          if @endpoint.nil?\n            raise ProtocolError, (\"When using OpenID 1, the claimed ID must \"\\\n                                  \"be supplied, either by passing it through \"\\\n                                  \"as a return_to parameter or by using a \"\\\n                                  \"session, and supplied to the IdResHandler \"\\\n                                  \"when it is constructed.\")\n          else\n            claimed_id = @endpoint.claimed_id\n          end\n        end\n\n        to_match = OpenIDServiceEndpoint.new\n        to_match.type_uris = [OPENID_1_1_TYPE]\n        to_match.local_id = fetch('identity')\n        # Restore delegate information from the initiation phase\n        to_match.claimed_id = claimed_id\n\n        to_match_1_0 = to_match.dup\n        to_match_1_0.type_uris = [OPENID_1_0_TYPE]\n\n        if !@endpoint.nil?\n          begin\n            begin\n              verify_discovery_single(@endpoint, to_match)\n            rescue TypeURIMismatch\n              verify_discovery_single(@endpoint, to_match_1_0)\n            end\n          rescue ProtocolError => why\n            Util.log('Error attempting to use stored discovery information: ' +\n                     why.message)\n            Util.log('Attempting discovery to verify endpoint')\n          else\n            return @endpoint\n          end\n        end\n\n        # Either no endpoint was supplied or OpenID 1.x verification\n        # of the information that's in the message failed on that\n        # endpoint.\n        discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])\n      end\n\n      # Given an endpoint object created from the information in an\n      # OpenID response, perform discovery and verify the discovery\n      # results, returning the matching endpoint that is the result of\n      # doing that discovery.\n      def discover_and_verify(claimed_id, to_match_endpoints)\n        Util.log(\"Performing discovery on #{claimed_id}\")\n        _, services = OpenID.discover(claimed_id)\n        if services.length == 0\n          # XXX: this might want to be something other than\n          # ProtocolError. In Python, it's DiscoveryFailure\n          raise ProtocolError, (\"No OpenID information found at \"\\\n                                \"#{claimed_id}\")\n        end\n        verify_discovered_services(claimed_id, services, to_match_endpoints)\n      end\n\n\n      def verify_discovered_services(claimed_id, services, to_match_endpoints)\n        # Search the services resulting from discovery to find one\n        # that matches the information from the assertion\n        failure_messages = []\n        for endpoint in services\n          for to_match_endpoint in to_match_endpoints\n            begin\n              verify_discovery_single(endpoint, to_match_endpoint)\n            rescue ProtocolError => why\n              failure_messages << why.message\n            else\n              # It matches, so discover verification has\n              # succeeded. Return this endpoint.\n              @endpoint = endpoint\n              return\n            end\n          end\n        end\n\n        Util.log(\"Discovery verification failure for #{claimed_id}\")\n        failure_messages.each do |failure_message|\n          Util.log(\" * Endpoint mismatch: \" + failure_message)\n        end\n\n        # XXX: is DiscoveryFailure in Python OpenID\n        raise ProtocolError, (\"No matching endpoint found after \"\\\n                              \"discovering #{claimed_id}\")\n      end\n\n      def verify_discovery_single(endpoint, to_match)\n        # Every type URI that's in the to_match endpoint has to be\n        # present in the discovered endpoint.\n        for type_uri in to_match.type_uris\n          if !endpoint.uses_extension(type_uri)\n            raise TypeURIMismatch.new(type_uri, endpoint)\n          end\n        end\n\n        # Fragments do not influence discovery, so we can't compare a\n        # claimed identifier with a fragment to discovered information.\n        defragged_claimed_id =\n          case Yadis::XRI.identifier_scheme(to_match.claimed_id)\n          when :xri\n            to_match.claimed_id\n          when :uri\n            begin\n              parsed = URI.parse(to_match.claimed_id)\n            rescue URI::InvalidURIError\n              to_match.claimed_id\n            else\n              parsed.fragment = nil\n              parsed.to_s\n            end\n          else\n            raise StandardError, 'Not reached'\n          end\n\n        if defragged_claimed_id != endpoint.claimed_id\n          raise ProtocolError, (\"Claimed ID does not match (different \"\\\n                                \"subjects!), Expected \"\\\n                                \"#{defragged_claimed_id}, got \"\\\n                                \"#{endpoint.claimed_id}\")\n        end\n\n        if to_match.get_local_id != endpoint.get_local_id\n          raise ProtocolError, (\"local_id mismatch. Expected \"\\\n                                \"#{to_match.get_local_id}, got \"\\\n                                \"#{endpoint.get_local_id}\")\n        end\n\n        # If the server URL is nil, this must be an OpenID 1\n        # response, because op_endpoint is a required parameter in\n        # OpenID 2. In that case, we don't actually care what the\n        # discovered server_url is, because signature checking or\n        # check_auth should take care of that check for us.\n        if to_match.server_url.nil?\n          if to_match.preferred_namespace != OPENID1_NS\n            raise StandardError,\n            \"The code calling this must ensure that OpenID 2 \"\\\n            \"responses have a non-none `openid.op_endpoint' and \"\\\n            \"that it is set as the `server_url' attribute of the \"\\\n            \"`to_match' endpoint.\"\n          end\n        elsif to_match.server_url != endpoint.server_url\n          raise ProtocolError, (\"OP Endpoint mismatch. Expected\"\\\n                                \"#{to_match.server_url}, got \"\\\n                                \"#{endpoint.server_url}\")\n        end\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer/responses.rb",
    "content": "module OpenID\n  class Consumer\n    # Code returned when either the of the\n    # OpenID::OpenIDConsumer.begin_auth or OpenID::OpenIDConsumer.complete_auth\n    # methods return successfully.\n    SUCCESS = :success\n\n    # Code OpenID::OpenIDConsumer.complete_auth\n    # returns when the value it received indicated an invalid login.\n    FAILURE = :failure\n\n    # Code returned by OpenIDConsumer.complete_auth when the user\n    # cancels the operation from the server.\n    CANCEL = :cancel\n\n    # Code returned by OpenID::OpenIDConsumer.complete_auth when the\n    # OpenIDConsumer instance is in immediate mode and ther server sends back a\n    # URL for the user to login with.\n    SETUP_NEEDED = :setup_needed\n\n\n    module Response\n      attr_reader :endpoint\n\n      def status\n        self.class::STATUS\n      end\n\n      # The identity URL that has been authenticated; the Claimed Identifier.\n      # See also display_identifier.\n      def identity_url\n        @endpoint ? @endpoint.claimed_id : nil\n      end\n\n      # The display identifier is related to the Claimed Identifier, but the\n      # two are not always identical.  The display identifier is something the\n      # user should recognize as what they entered, whereas the response's\n      # claimed identifier (in the identity_url attribute) may have extra\n      # information for better persistence.\n      #\n      # URLs will be stripped of their fragments for display.  XRIs will\n      # display the human-readable identifier (i-name) instead of the\n      # persistent identifier (i-number).\n      #\n      # Use the display identifier in your user interface.  Use identity_url\n      # for querying your database or authorization server, or other\n      # identifier equality comparisons.\n      def display_identifier\n        @endpoint ? @endpoint.display_identifier : nil\n      end\n    end\n\n    # A successful acknowledgement from the OpenID server that the\n    # supplied URL is, indeed controlled by the requesting agent.\n    class SuccessResponse\n      include Response\n\n      STATUS = SUCCESS\n\n      attr_reader :message, :signed_fields\n\n      def initialize(endpoint, message, signed_fields)\n        # Don't use :endpoint=, because endpoint should never be nil\n        # for a successfull transaction.\n        @endpoint = endpoint\n        @identity_url = endpoint.claimed_id\n        @message = message\n        @signed_fields = signed_fields\n      end\n\n      # Was this authentication response an OpenID 1 authentication\n      # response?\n      def is_openid1\n        @message.is_openid1\n      end\n\n      # Return whether a particular key is signed, regardless of its\n      # namespace alias\n      def signed?(ns_uri, ns_key)\n        @signed_fields.member?(@message.get_key(ns_uri, ns_key))\n      end\n\n      # Return the specified signed field if available, otherwise\n      # return default\n      def get_signed(ns_uri, ns_key, default=nil)\n        if signed?(ns_uri, ns_key)\n          return @message.get_arg(ns_uri, ns_key, default)\n        else\n          return default\n        end\n      end\n\n      # Get signed arguments from the response message.  Return a dict\n      # of all arguments in the specified namespace.  If any of the\n      # arguments are not signed, return nil.\n      def get_signed_ns(ns_uri)\n        msg_args = @message.get_args(ns_uri)\n        msg_args.each_key do |key|\n          if !signed?(ns_uri, key)\n            return nil\n          end\n        end\n        return msg_args\n      end\n\n      # Return response arguments in the specified namespace.\n      # If require_signed is true and the arguments are not signed,\n      # return nil.\n      def extension_response(namespace_uri, require_signed)\n        if require_signed\n          get_signed_ns(namespace_uri)\n        else\n          @message.get_args(namespace_uri)\n        end\n      end\n    end\n\n    class FailureResponse\n      include Response\n      STATUS = FAILURE\n\n      attr_reader :message, :contact, :reference\n      def initialize(endpoint, message, contact=nil, reference=nil)\n        @endpoint = endpoint\n        @message = message\n        @contact = contact\n        @reference = reference\n      end\n    end\n\n    class CancelResponse\n      include Response\n      STATUS = CANCEL\n      def initialize(endpoint)\n        @endpoint = endpoint\n      end\n    end\n\n    class SetupNeededResponse\n      include Response\n      STATUS = SETUP_NEEDED\n\n      attr_reader :setup_url\n      def initialize(endpoint, setup_url)\n        @endpoint = endpoint\n        @setup_url = setup_url\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer/session.rb",
    "content": "module OpenID\n  class Consumer\n    class Session\n      def initialize(session, decode_klass = nil)\n        @session = session\n        @decode_klass = decode_klass\n      end\n\n      def [](key)\n        val = @session[key]\n        @decode_klass ? @decode_klass.from_session_value(val) : val\n      end\n\n      def []=(key, val)\n        @session[key] = to_session_value(val)\n      end\n\n      def keys\n        @session.keys\n      end\n\n      private\n\n      def to_session_value(val)\n        case val\n        when Array\n          val.map{|ele| to_session_value(ele) }\n        when Hash\n          Hash[*(val.map{|k,v| [k, to_session_value(v)] }.flatten(1))]\n        else\n          val.respond_to?(:to_session_value) ? val.to_session_value : val\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/consumer.rb",
    "content": "require \"openid/consumer/idres.rb\"\nrequire \"openid/consumer/checkid_request.rb\"\nrequire \"openid/consumer/associationmanager.rb\"\nrequire \"openid/consumer/responses.rb\"\nrequire \"openid/consumer/session\"\nrequire \"openid/consumer/discovery_manager\"\nrequire \"openid/consumer/discovery\"\nrequire \"openid/message\"\nrequire \"openid/yadis/discovery\"\nrequire \"openid/store/nonce\"\n\nmodule OpenID\n  # OpenID support for Relying Parties (aka Consumers).\n  #\n  # This module documents the main interface with the OpenID consumer\n  # library.  The only part of the library which has to be used and\n  # isn't documented in full here is the store required to create an\n  # Consumer instance.\n  #\n  # = OVERVIEW\n  #\n  # The OpenID identity verification process most commonly uses the\n  # following steps, as visible to the user of this library:\n  #\n  # 1. The user enters their OpenID into a field on the consumer's\n  #    site, and hits a login button.\n  #\n  # 2. The consumer site discovers the user's OpenID provider using\n  #    the Yadis protocol.\n  #\n  # 3. The consumer site sends the browser a redirect to the OpenID\n  #    provider.  This is the authentication request as described in\n  #    the OpenID specification.\n  #\n  # 4. The OpenID provider's site sends the browser a redirect back to\n  #    the consumer site.  This redirect contains the provider's\n  #    response to the authentication request.\n  #\n  # The most important part of the flow to note is the consumer's site\n  # must handle two separate HTTP requests in order to perform the\n  # full identity check.\n  #\n  # = LIBRARY DESIGN\n  #\n  # This consumer library is designed with that flow in mind.  The\n  # goal is to make it as easy as possible to perform the above steps\n  # securely.\n  #\n  # At a high level, there are two important parts in the consumer\n  # library.  The first important part is this module, which contains\n  # the interface to actually use this library.  The second is\n  # openid/store/interface.rb, which describes the interface to use if\n  # you need to create a custom method for storing the state this\n  # library needs to maintain between requests.\n  #\n  # In general, the second part is less important for users of the\n  # library to know about, as several implementations are provided\n  # which cover a wide variety of situations in which consumers may\n  # use the library.\n  #\n  # The Consumer class has methods corresponding to the actions\n  # necessary in each of steps 2, 3, and 4 described in the overview.\n  # Use of this library should be as easy as creating an Consumer\n  # instance and calling the methods appropriate for the action the\n  # site wants to take.\n  #\n  # This library automatically detects which version of the OpenID\n  # protocol should be used for a transaction and constructs the\n  # proper requests and responses.  Users of this library do not need\n  # to worry about supporting multiple protocol versions; the library\n  # supports them implicitly.  Depending on the version of the\n  # protocol in use, the OpenID transaction may be more secure.  See\n  # the OpenID specifications for more information.\n  #\n  # = SESSIONS, STORES, AND STATELESS MODE\n  #\n  # The Consumer object keeps track of two types of state:\n  #\n  # 1. State of the user's current authentication attempt.  Things\n  #    like the identity URL, the list of endpoints discovered for\n  #    that URL, and in case where some endpoints are unreachable, the\n  #    list of endpoints already tried.  This state needs to be held\n  #    from Consumer.begin() to Consumer.complete(), but it is only\n  #    applicable to a single session with a single user agent, and at\n  #    the end of the authentication process (i.e. when an OP replies\n  #    with either <tt>id_res</tt>. or <tt>cancel</tt> it may be\n  #    discarded.\n  #\n  # 2. State of relationships with servers, i.e. shared secrets\n  #    (associations) with servers and nonces seen on signed messages.\n  #    This information should persist from one session to the next\n  #    and should not be bound to a particular user-agent.\n  #\n  # These two types of storage are reflected in the first two\n  # arguments of Consumer's constructor, <tt>session</tt> and\n  # <tt>store</tt>.  <tt>session</tt> is a dict-like object and we\n  # hope your web framework provides you with one of these bound to\n  # the user agent.  <tt>store</tt> is an instance of Store.\n  #\n  # Since the store does hold secrets shared between your application\n  # and the OpenID provider, you should be careful about how you use\n  # it in a shared hosting environment.  If the filesystem or database\n  # permissions of your web host allow strangers to read from them, do\n  # not store your data there!  If you have no safe place to store\n  # your data, construct your consumer with nil for the store, and it\n  # will operate only in stateless mode.  Stateless mode may be\n  # slower, put more load on the OpenID provider, and trusts the\n  # provider to keep you safe from replay attacks.\n  #\n  # Several store implementation are provided, and the interface is\n  # fully documented so that custom stores can be used as well.  See\n  # the documentation for the Consumer class for more information on\n  # the interface for stores.  The implementations that are provided\n  # allow the consumer site to store the necessary data in several\n  # different ways, including several SQL databases and normal files\n  # on disk.\n  #\n  # = IMMEDIATE MODE\n  #\n  # In the flow described above, the user may need to confirm to the\n  # OpenID provider that it's ok to disclose his or her identity.  The\n  # provider may draw pages asking for information from the user\n  # before it redirects the browser back to the consumer's site.  This\n  # is generally transparent to the consumer site, so it is typically\n  # ignored as an implementation detail.\n  #\n  # There can be times, however, where the consumer site wants to get\n  # a response immediately.  When this is the case, the consumer can\n  # put the library in immediate mode.  In immediate mode, there is an\n  # extra response possible from the server, which is essentially the\n  # server reporting that it doesn't have enough information to answer\n  # the question yet.\n  #\n  # = USING THIS LIBRARY\n  #\n  # Integrating this library into an application is usually a\n  # relatively straightforward process.  The process should basically\n  # follow this plan:\n  #\n  # Add an OpenID login field somewhere on your site.  When an OpenID\n  # is entered in that field and the form is submitted, it should make\n  # a request to the site that includes that OpenID URL.\n  #\n  # First, the application should instantiate a Consumer with a\n  # session for per-user state and store for shared state using the\n  # store of choice.\n  #\n  # Next, the application should call the <tt>begin</tt> method of\n  # Consumer instance.  This method takes the OpenID URL as entered by\n  # the user.  The <tt>begin</tt> method returns a CheckIDRequest\n  # object.\n  #\n  # Next, the application should call the redirect_url method on the\n  # CheckIDRequest object.  The parameter <tt>return_to</tt> is the\n  # URL that the OpenID server will send the user back to after\n  # attempting to verify his or her identity.  The <tt>realm</tt>\n  # parameter is the URL (or URL pattern) that identifies your web\n  # site to the user when he or she is authorizing it.  Send a\n  # redirect to the resulting URL to the user's browser.\n  #\n  # That's the first half of the authentication process.  The second\n  # half of the process is done after the user's OpenID Provider sends\n  # the user's browser a redirect back to your site to complete their\n  # login.\n  #\n  # When that happens, the user will contact your site at the URL\n  # given as the <tt>return_to</tt> URL to the redirect_url call made\n  # above.  The request will have several query parameters added to\n  # the URL by the OpenID provider as the information necessary to\n  # finish the request.\n  #\n  # Get a Consumer instance with the same session and store as before\n  # and call its complete() method, passing in all the received query\n  # arguments and URL currently being handled.\n  #\n  # There are multiple possible return types possible from that\n  # method. These indicate the whether or not the login was\n  # successful, and include any additional information appropriate for\n  # their type.\n  class Consumer\n    attr_accessor :session_key_prefix\n\n    # Initialize a Consumer instance.\n    #\n    # You should create a new instance of the Consumer object with\n    # every HTTP request that handles OpenID transactions.\n    #\n    # session: the session object to use to store request information.\n    # The session should behave like a hash.\n    #\n    # store: an object that implements the interface in Store.\n    def initialize(session, store)\n      @origin_session = session\n      @session = Session.new(session, OpenID::OpenIDServiceEndpoint)\n      @store = store\n      @session_key_prefix = 'OpenID::Consumer::'\n    end\n\n    # Start the OpenID authentication process. See steps 1-2 in the\n    # overview for the Consumer class.\n    #\n    # user_url: Identity URL given by the user. This method performs a\n    # textual transformation of the URL to try and make sure it is\n    # normalized. For example, a user_url of example.com will be\n    # normalized to http://example.com/ normalizing and resolving any\n    # redirects the server might issue.\n    #\n    # anonymous: A boolean value.  Whether to make an anonymous\n    # request of the OpenID provider.  Such a request does not ask for\n    # an authorization assertion for an OpenID identifier, but may be\n    # used with extensions to pass other data.  e.g. \"I don't care who\n    # you are, but I'd like to know your time zone.\"\n    #\n    # Returns a CheckIDRequest object containing the discovered\n    # information, with a method for building a redirect URL to the\n    # server, as described in step 3 of the overview. This object may\n    # also be used to add extension arguments to the request, using\n    # its add_extension_arg method.\n    #\n    # Raises DiscoveryFailure when no OpenID server can be found for\n    # this URL.\n    def begin(openid_identifier, anonymous=false)\n      manager = discovery_manager(openid_identifier)\n      service = manager.get_next_service(&method(:discover))\n\n      if service.nil?\n        raise DiscoveryFailure.new(\"No usable OpenID services were found \"\\\n                                   \"for #{openid_identifier.inspect}\", nil)\n      else\n        begin_without_discovery(service, anonymous)\n      end\n    end\n\n    # Start OpenID verification without doing OpenID server\n    # discovery. This method is used internally by Consumer.begin()\n    # after discovery is performed, and exists to provide an interface\n    # for library users needing to perform their own discovery.\n    #\n    # service: an OpenID service endpoint descriptor.  This object and\n    # factories for it are found in the openid/consumer/discovery.rb\n    # module.\n    #\n    # Returns an OpenID authentication request object.\n    def begin_without_discovery(service, anonymous)\n      assoc = association_manager(service).get_association\n      checkid_request = CheckIDRequest.new(assoc, service)\n      checkid_request.anonymous = anonymous\n\n      if service.compatibility_mode\n        rt_args = checkid_request.return_to_args\n        rt_args[Consumer.openid1_return_to_nonce_name] = Nonce.mk_nonce\n        rt_args[Consumer.openid1_return_to_claimed_id_name] =\n          service.claimed_id\n      end\n\n      self.last_requested_endpoint = service\n      return checkid_request\n    end\n\n    # Called to interpret the server's response to an OpenID\n    # request. It is called in step 4 of the flow described in the\n    # Consumer overview.\n    #\n    # query: A hash of the query parameters for this HTTP request.\n    # Note that in rails, this is <b>not</b> <tt>params</tt> but\n    # <tt>params.reject{|k,v|request.path_parameters[k]}</tt>\n    # because <tt>controller</tt> and <tt>action</tt> and other\n    # \"path parameters\" are included in params.\n    #\n    # current_url: Extract the URL of the current request from your\n    # application's web request framework and specify it here to have it\n    # checked against the openid.return_to value in the response.  Do not\n    # just pass <tt>args['openid.return_to']</tt> here; that will defeat the\n    # purpose of this check.  (See OpenID Authentication 2.0 section 11.1.)\n    #\n    # If the return_to URL check fails, the status of the completion will be\n    # FAILURE.\n\n    #\n    # Returns a subclass of Response. The type of response is\n    # indicated by the status attribute, which will be one of\n    # SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.\n    def complete(query, current_url)\n      message = Message.from_post_args(query)\n      mode = message.get_arg(OPENID_NS, 'mode', 'invalid')\n      begin\n        meth = method('complete_' + mode)\n      rescue NameError\n        meth = method(:complete_invalid)\n      end\n      response = meth.call(message, current_url)\n      cleanup_last_requested_endpoint\n      if [SUCCESS, CANCEL].member?(response.status)\n        cleanup_session\n      end\n      return response\n    end\n\n    protected\n\n    def session_get(name)\n      @session[session_key(name)]\n    end\n\n    def session_set(name, val)\n      @session[session_key(name)] = val\n    end\n\n    def session_key(suffix)\n      @session_key_prefix + suffix\n    end\n\n    def last_requested_endpoint\n      session_get('last_requested_endpoint')\n    end\n\n    def last_requested_endpoint=(endpoint)\n      session_set('last_requested_endpoint', endpoint)\n    end\n\n    def cleanup_last_requested_endpoint\n      @session[session_key('last_requested_endpoint')] = nil\n    end\n\n    def discovery_manager(openid_identifier)\n      DiscoveryManager.new(@origin_session, openid_identifier, @session_key_prefix)\n    end\n\n    def cleanup_session\n      discovery_manager(nil).cleanup(true)\n    end\n\n\n    def discover(identifier)\n      OpenID.discover(identifier)\n    end\n\n    def negotiator\n      DefaultNegotiator\n    end\n\n    def association_manager(service)\n      AssociationManager.new(@store, service.server_url,\n                             service.compatibility_mode, negotiator)\n    end\n\n    def handle_idres(message, current_url)\n      IdResHandler.new(message, current_url, @store, last_requested_endpoint)\n    end\n\n    def complete_invalid(message, unused_return_to)\n      mode = message.get_arg(OPENID_NS, 'mode', '<No mode set>')\n      return FailureResponse.new(last_requested_endpoint,\n                                 \"Invalid openid.mode: #{mode}\")\n    end\n\n    def complete_cancel(unused_message, unused_return_to)\n      return CancelResponse.new(last_requested_endpoint)\n    end\n\n    def complete_error(message, unused_return_to)\n      error = message.get_arg(OPENID_NS, 'error')\n      contact = message.get_arg(OPENID_NS, 'contact')\n      reference = message.get_arg(OPENID_NS, 'reference')\n\n      return FailureResponse.new(last_requested_endpoint,\n                                 error, contact, reference)\n    end\n\n    def complete_setup_needed(message, unused_return_to)\n      if message.is_openid1\n        return complete_invalid(message, nil)\n      else\n        setup_url = message.get_arg(OPENID2_NS, 'user_setup_url')\n        return SetupNeededResponse.new(last_requested_endpoint, setup_url)\n      end\n    end\n\n    def complete_id_res(message, current_url)\n      if message.is_openid1\n        setup_url = message.get_arg(OPENID_NS, 'user_setup_url')\n        if !setup_url.nil?\n          return SetupNeededResponse.new(last_requested_endpoint, setup_url)\n        end\n      end\n\n      begin\n        idres = handle_idres(message, current_url)\n      rescue OpenIDError => why\n        return FailureResponse.new(last_requested_endpoint, why.message)\n      else\n        return SuccessResponse.new(idres.endpoint, message,\n                                     idres.signed_fields)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/cryptutil.rb",
    "content": "require \"openid/util\"\nrequire \"digest/sha1\"\nrequire \"digest/sha2\"\nbegin\n  require \"openssl\"\nrescue LoadError\n  begin\n    # Try loading the ruby-hmac files if they exist\n    require \"hmac-sha1\"\n    require \"hmac-sha2\"\n  rescue LoadError\n    # Nothing exists use included hmac files\n    require \"hmac/sha1\"\n    require \"hmac/sha2\"\n  end\nend\n\nmodule OpenID\n  # This module contains everything needed to perform low-level\n  # cryptograph and data manipulation tasks.\n  module CryptUtil\n\n    # Generate a random number, doing a little extra work to make it\n    # more likely that it's suitable for cryptography. If your system\n    # doesn't have /dev/urandom then this number is not\n    # cryptographically safe. See\n    # <http://www.cosine.org/2007/08/07/security-ruby-kernel-rand/>\n    # for more information.  max is the largest possible value of such\n    # a random number, where the result will be less than max.\n    def CryptUtil.rand(max)\n      Kernel.srand()\n      return Kernel.rand(max)\n    end\n\n    def CryptUtil.sha1(text)\n      return Digest::SHA1.digest(text)\n    end\n\n    def CryptUtil.hmac_sha1(key, text)\n      if defined? OpenSSL\n        OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, text)\n      else\n        return HMAC::SHA1.digest(key, text)\n      end\n    end\n\n    def CryptUtil.sha256(text)\n      return Digest::SHA256.digest(text)\n    end\n\n    def CryptUtil.hmac_sha256(key, text)\n      if defined? OpenSSL\n        OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, text)\n      else\n        return HMAC::SHA256.digest(key, text)\n      end\n    end\n\n    # Generate a random string of the given length, composed of the\n    # specified characters.  If chars is nil, generate a string\n    # composed of characters in the range 0..255.\n    def CryptUtil.random_string(length, chars=nil)\n      s = \"\"\n\n      unless chars.nil?\n        length.times { s << chars[rand(chars.length)] }\n      else\n        length.times { s << rand(256).chr }\n      end\n      return s\n    end\n\n    # Convert a number to its binary representation; return a string\n    # of bytes.\n    def CryptUtil.num_to_binary(n)\n      bits = n.to_s(2)\n      prepend = (8 - bits.length % 8)\n      bits = ('0' * prepend) + bits\n      return [bits].pack('B*')\n    end\n\n    # Convert a string of bytes into a number.\n    def CryptUtil.binary_to_num(s)\n      # taken from openid-ruby 0.0.1\n      s = \"\\000\" * (4 - (s.length % 4)) + s\n      num = 0\n      s.unpack('N*').each do |x|\n        num <<= 32\n        num |= x\n      end\n      return num\n    end\n\n    # Encode a number as a base64-encoded byte string.\n    def CryptUtil.num_to_base64(l)\n      return OpenID::Util.to_base64(num_to_binary(l))\n    end\n\n    # Decode a base64 byte string to a number.\n    def CryptUtil.base64_to_num(s)\n      return binary_to_num(OpenID::Util.from_base64(s))\n    end\n\n    def CryptUtil.const_eq(s1, s2)\n      if s1.length != s2.length\n        return false\n      end\n      result = true\n      s1.length.times do |i|\n        result &= (s1[i] == s2[i])\n      end\n      return result\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/dh.rb",
    "content": "require \"openid/util\"\nrequire \"openid/cryptutil\"\n\nmodule OpenID\n\n  # Encapsulates a Diffie-Hellman key exchange.  This class is used\n  # internally by both the consumer and server objects.\n  #\n  # Read more about Diffie-Hellman on wikipedia:\n  # http://en.wikipedia.org/wiki/Diffie-Hellman\n\n  class DiffieHellman\n\n    # From the OpenID specification\n    @@default_mod = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443\n    @@default_gen = 2\n\n    attr_reader :modulus, :generator, :public\n\n    # A new DiffieHellman object, using the modulus and generator from\n    # the OpenID specification\n    def DiffieHellman.from_defaults\n      DiffieHellman.new(@@default_mod, @@default_gen)\n    end\n\n    def initialize(modulus=nil, generator=nil, priv=nil)\n      @modulus = modulus.nil? ? @@default_mod : modulus\n      @generator = generator.nil? ? @@default_gen : generator\n      set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus-2) + 1 : priv)\n    end\n\n    def get_shared_secret(composite)\n      DiffieHellman.powermod(composite, @private, @modulus)\n    end\n\n    def xor_secret(algorithm, composite, secret)\n      dh_shared = get_shared_secret(composite)\n      packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared)\n      hashed_dh_shared = algorithm.call(packed_dh_shared)\n      return DiffieHellman.strxor(secret, hashed_dh_shared)\n    end\n\n    def using_default_values?\n      @generator == @@default_gen && @modulus == @@default_mod\n    end\n\n    private\n    def set_private(priv)\n      @private = priv\n      @public = DiffieHellman.powermod(@generator, @private, @modulus)\n    end\n\n    def DiffieHellman.strxor(s, t)\n      if s.length != t.length\n        raise ArgumentError, \"strxor: lengths don't match. \" +\n          \"Inputs were #{s.inspect} and #{t.inspect}\"\n      end\n\n      if String.method_defined? :bytes\n        s.bytes.to_a.zip(t.bytes.to_a).map{|sb,tb| sb^tb}.pack('C*')\n      else\n        indices = 0...(s.length)\n        chrs = indices.collect {|i| (s[i]^t[i]).chr}\n        chrs.join(\"\")\n      end\n    end\n\n    # This code is taken from this post:\n    # <http://blade.nagaokaut.ac.jp/cgi-bin/scat.\\rb/ruby/ruby-talk/19098>\n    # by Eric Lee Green.\n    def DiffieHellman.powermod(x, n, q)\n      counter=0\n      n_p=n\n      y_p=1\n      z_p=x\n      while n_p != 0\n        if n_p[0]==1\n          y_p=(y_p*z_p) % q\n        end\n        n_p = n_p >> 1\n        z_p = (z_p * z_p) % q\n        counter += 1\n      end\n      return y_p\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/extension.rb",
    "content": "require 'openid/message'\n\nmodule OpenID\n  # An interface for OpenID extensions.\n  class Extension < Object\n\n    def initialize\n      @ns_uri = nil\n      @ns_alias = nil\n    end\n\n    # Get the string arguments that should be added to an OpenID\n    # message for this extension.\n    def get_extension_args\n      raise NotImplementedError\n    end\n\n    # Add the arguments from this extension to the provided\n    # message, or create a new message containing only those\n    # arguments.  Returns the message with added extension args.\n    def to_message(message = nil)\n      if message.nil?\n#         warnings.warn('Passing None to Extension.toMessage is deprecated. '\n#                       'Creating a message assuming you want OpenID 2.',\n#                       DeprecationWarning, stacklevel=2)\n        Message.new(OPENID2_NS)\n      end\n      message = Message.new if message.nil?\n\n      implicit = message.is_openid1()\n\n      message.namespaces.add_alias(@ns_uri, @ns_alias, implicit)\n      # XXX python ignores keyerror if m.ns.getAlias(uri) == alias\n\n      message.update_args(@ns_uri, get_extension_args)\n      return message\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/extensions/ax.rb",
    "content": "# Implements the OpenID attribute exchange specification, version 1.0\n\nrequire 'openid/extension'\nrequire 'openid/trustroot'\nrequire 'openid/message'\n\nmodule OpenID\n  module AX\n\n    UNLIMITED_VALUES = \"unlimited\"\n    MINIMUM_SUPPORTED_ALIAS_LENGTH = 32\n\n    # check alias for invalid characters, raise AXError if found\n    def self.check_alias(name)\n      if name.match(/(,|\\.)/)\n        raise Error, (\"Alias #{name.inspect} must not contain a \"\\\n                      \"comma or period.\")\n      end\n    end\n\n    # Raised when data does not comply with AX 1.0 specification\n    class Error < ArgumentError\n    end\n\n    # Abstract class containing common code for attribute exchange messages\n    class AXMessage < Extension\n      attr_accessor :ns_alias, :mode, :ns_uri\n\n      NS_URI = 'http://openid.net/srv/ax/1.0'\n\n      begin\n        Message.register_namespace_alias(NS_URI, 'ax')\n      rescue NamespaceAliasRegistrationError => e\n        Util.log(e)\n      end\n\n      def initialize\n        @ns_alias = 'ax'\n        @ns_uri = NS_URI\n        @mode = nil\n      end\n\n      protected\n\n      # Raise an exception if the mode in the attribute exchange\n      # arguments does not match what is expected for this class.\n      def check_mode(ax_args)\n        actual_mode = ax_args ? ax_args['mode'] : nil\n        if actual_mode != @mode\n          raise Error, \"Expected mode #{mode.inspect}, got #{actual_mode.inspect}\"\n        end\n      end\n\n      def new_args\n        {'mode' => @mode}\n      end\n    end\n\n    # Represents a single attribute in an attribute exchange\n    # request. This should be added to an Request object in order to\n    # request the attribute.\n    #\n    # @ivar required: Whether the attribute will be marked as required\n    #     when presented to the subject of the attribute exchange\n    #     request.\n    # @type required: bool\n    #\n    # @ivar count: How many values of this type to request from the\n    #      subject. Defaults to one.\n    # @type count: int\n    #\n    # @ivar type_uri: The identifier that determines what the attribute\n    #      represents and how it is serialized. For example, one type URI\n    #      representing dates could represent a Unix timestamp in base 10\n    #      and another could represent a human-readable string.\n    # @type type_uri: str\n    #\n    # @ivar ns_alias: The name that should be given to this alias in the\n    #      request. If it is not supplied, a generic name will be\n    #      assigned. For example, if you want to call a Unix timestamp\n    #      value 'tstamp', set its alias to that value. If two attributes\n    #      in the same message request to use the same alias, the request\n    #      will fail to be generated.\n    # @type alias: str or NoneType\n    class AttrInfo < Object\n      attr_reader :type_uri, :count, :ns_alias\n      attr_accessor :required\n      def initialize(type_uri, ns_alias=nil, required=false, count=1)\n        @type_uri = type_uri\n        @count = count\n        @required = required\n        @ns_alias = ns_alias\n      end\n\n      def wants_unlimited_values?\n        @count == UNLIMITED_VALUES\n      end\n    end\n\n    # Given a namespace mapping and a string containing a\n    # comma-separated list of namespace aliases, return a list of type\n    # URIs that correspond to those aliases.\n    # namespace_map: OpenID::NamespaceMap\n    def self.to_type_uris(namespace_map, alias_list_s)\n      return [] if alias_list_s.nil?\n      alias_list_s.split(',').inject([]) {|uris, name|\n        type_uri = namespace_map.get_namespace_uri(name)\n        raise IndexError, \"No type defined for attribute name #{name.inspect}\" if type_uri.nil?\n        uris << type_uri\n      }\n    end\n\n\n    # An attribute exchange 'fetch_request' message. This message is\n    # sent by a relying party when it wishes to obtain attributes about\n    # the subject of an OpenID authentication request.\n    class FetchRequest < AXMessage\n      attr_reader :requested_attributes\n      attr_accessor :update_url\n\n      MODE = 'fetch_request'\n\n      def initialize(update_url = nil)\n        super()\n        @mode = MODE\n        @requested_attributes = {}\n        @update_url = update_url\n      end\n\n      # Add an attribute to this attribute exchange request.\n      # attribute: AttrInfo, the attribute being requested\n      # Raises IndexError if the requested attribute is already present\n      #   in this request.\n      def add(attribute)\n        if @requested_attributes[attribute.type_uri]\n          raise IndexError, \"The attribute #{attribute.type_uri} has already been requested\"\n        end\n        @requested_attributes[attribute.type_uri] = attribute\n      end\n\n      # Get the serialized form of this attribute fetch request.\n      # returns a hash of the arguments\n      def get_extension_args\n        aliases = NamespaceMap.new\n        required = []\n        if_available = []\n        ax_args = new_args\n        @requested_attributes.each{|type_uri, attribute|\n          if attribute.ns_alias\n            name = aliases.add_alias(type_uri, attribute.ns_alias)\n          else\n            name = aliases.add(type_uri)\n          end\n          if attribute.required\n            required << name\n          else\n            if_available << name\n          end\n          if attribute.count != 1\n            ax_args[\"count.#{name}\"] = attribute.count.to_s\n          end\n          ax_args[\"type.#{name}\"] = type_uri\n        }\n\n        unless required.empty?\n          ax_args['required'] = required.join(',')\n        end\n        unless if_available.empty?\n          ax_args['if_available'] = if_available.join(',')\n        end\n        return ax_args\n      end\n\n      # Get the type URIs for all attributes that have been marked\n      # as required.\n      def get_required_attrs\n        @requested_attributes.inject([]) {|required, (type_uri, attribute)|\n          if attribute.required\n            required << type_uri\n          else\n            required\n          end\n        }\n      end\n\n      # Extract a FetchRequest from an OpenID message\n      # message: OpenID::Message\n      # return a FetchRequest or nil if AX arguments are not present\n      def self.from_openid_request(oidreq)\n        message = oidreq.message\n        ax_args = message.get_args(NS_URI)\n        return nil if ax_args == {} or ax_args['mode'] != MODE\n        req = new\n        req.parse_extension_args(ax_args)\n\n        if req.update_url\n          realm = message.get_arg(OPENID_NS, 'realm',\n                                  message.get_arg(OPENID_NS, 'return_to'))\n          if realm.nil? or realm.empty?\n            raise Error, \"Cannot validate update_url #{req.update_url.inspect} against absent realm\"\n          end\n          tr = TrustRoot::TrustRoot.parse(realm)\n          unless tr.validate_url(req.update_url)\n            raise Error, \"Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}\"\n          end\n        end\n\n        return req\n      end\n\n      def parse_extension_args(ax_args)\n        check_mode(ax_args)\n\n        aliases = NamespaceMap.new\n\n        ax_args.each{|k,v|\n          if k.index('type.') == 0\n            name = k[5..-1]\n            type_uri = v\n            aliases.add_alias(type_uri, name)\n\n            count_key = 'count.'+name\n            count_s = ax_args[count_key]\n            count = 1\n            if count_s\n              if count_s == UNLIMITED_VALUES\n                count = count_s\n              else\n                count = count_s.to_i\n                if count <= 0\n                  raise Error, \"Invalid value for count #{count_key.inspect}: #{count_s.inspect}\"\n                end\n              end\n            end\n            add(AttrInfo.new(type_uri, name, false, count))\n          end\n        }\n\n        required = AX.to_type_uris(aliases, ax_args['required'])\n        required.each{|type_uri|\n          @requested_attributes[type_uri].required = true\n        }\n        if_available = AX.to_type_uris(aliases, ax_args['if_available'])\n        all_type_uris = required + if_available\n\n        aliases.namespace_uris.each{|type_uri|\n          unless all_type_uris.member? type_uri\n            raise Error, \"Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'\"\n          end\n        }\n        @update_url = ax_args['update_url']\n      end\n\n      # return the list of AttrInfo objects contained in the FetchRequest\n      def attributes\n        @requested_attributes.values\n      end\n\n      # return the list of requested attribute type URIs\n      def requested_types\n        @requested_attributes.keys\n      end\n\n      def member?(type_uri)\n        ! @requested_attributes[type_uri].nil?\n      end\n\n    end\n\n    # Abstract class that implements a message that has attribute\n    # keys and values. It contains the common code between\n    # fetch_response and store_request.\n    class KeyValueMessage < AXMessage\n      attr_reader :data\n      def initialize\n        super()\n        @mode = nil\n        @data = Hash.new { |hash, key| hash[key] = [] }\n      end\n\n      # Add a single value for the given attribute type to the\n      # message. If there are already values specified for this type,\n      # this value will be sent in addition to the values already\n      # specified.\n      def add_value(type_uri, value)\n        @data[type_uri] = @data[type_uri] << value\n      end\n\n      # Set the values for the given attribute type. This replaces\n      # any values that have already been set for this attribute.\n      def set_values(type_uri, values)\n        @data[type_uri] = values\n      end\n\n      # Get the extension arguments for the key/value pairs\n      # contained in this message.\n      def _get_extension_kv_args(aliases = nil)\n        aliases = NamespaceMap.new if aliases.nil?\n\n        ax_args = new_args\n\n        @data.each{|type_uri, values|\n          name = aliases.add(type_uri)\n          ax_args['type.'+name] = type_uri\n          if values.size > 1\n            ax_args['count.'+name] = values.size.to_s\n\n            values.each_with_index{|value, i|\n              key = \"value.#{name}.#{i+1}\"\n              ax_args[key] = value\n            }\n            # for attributes with only a single value, use a\n            # nice shortcut to only show the value w/o the count\n          else \n            values.each do |value|\n              key = \"value.#{name}\"\n              ax_args[key] = value\n            end\n          end\n        }\n        return ax_args\n      end\n\n      # Parse attribute exchange key/value arguments into this object.\n\n      def parse_extension_args(ax_args)\n        check_mode(ax_args)\n        aliases = NamespaceMap.new\n\n        ax_args.each{|k, v|\n          if k.index('type.') == 0\n            type_uri = v\n            name = k[5..-1]\n\n            AX.check_alias(name)\n            aliases.add_alias(type_uri,name)\n          end\n        }\n\n        aliases.each{|type_uri, name|\n          count_s = ax_args['count.'+name]\n          count = count_s.to_i\n          if count_s.nil?\n            value = ax_args['value.'+name]\n            if value.nil?\n              raise IndexError, \"Missing #{'value.'+name} in FetchResponse\"\n            elsif value.empty?\n              values = []\n            else\n              values = [value]\n            end\n          elsif count_s.to_i == 0\n            values = []\n          else\n            values = (1..count).inject([]){|l,i|\n              key = \"value.#{name}.#{i}\"\n              v = ax_args[key]\n              raise IndexError, \"Missing #{key} in FetchResponse\" if v.nil?\n              l << v\n            }\n          end\n          @data[type_uri] = values\n        }\n      end\n\n      # Get a single value for an attribute. If no value was sent\n      # for this attribute, use the supplied default. If there is more\n      # than one value for this attribute, this method will fail.\n      def get_single(type_uri, default = nil)\n        values = @data[type_uri]\n        return default if values.empty?\n        if values.size != 1\n          raise Error, \"More than one value present for #{type_uri.inspect}\"\n        else\n          return values[0]\n        end\n      end\n\n      # retrieve the list of values for this attribute\n      def get(type_uri)\n        @data[type_uri]\n      end\n\n      # retrieve the list of values for this attribute\n      def [](type_uri)\n        @data[type_uri]\n      end\n\n      # get the number of responses for this attribute\n      def count(type_uri)\n        @data[type_uri].size\n      end\n\n    end\n\n    # A fetch_response attribute exchange message\n    class FetchResponse < KeyValueMessage\n      attr_reader :update_url\n      # Use the aliases variable to manually add alias names in the response.\n      # They'll be returned to the client in the format: \n      #   openid.ax.type.email=http://openid.net/schema/contact/internet/email\n      #   openid.ax.value.email=guy@example.com\n      attr_accessor :aliases\n\n      def initialize(update_url = nil)\n        super()\n        @mode = 'fetch_response'\n        @update_url = update_url\n        @aliases = NamespaceMap.new\n      end\n\n      # Serialize this object into arguments in the attribute\n      # exchange namespace\n      # Takes an optional FetchRequest.  If specified, the response will be\n      # validated against this request, and empty responses for requested\n      # fields with no data will be sent.\n      def get_extension_args(request = nil)\n        zero_value_types = []\n\n        if request\n          # Validate the data in the context of the request (the\n          # same attributes should be present in each, and the\n          # counts in the response must be no more than the counts\n          # in the request)\n          @data.keys.each{|type_uri|\n            unless request.member? type_uri\n              raise IndexError, \"Response attribute not present in request: #{type_uri.inspect}\"\n            end\n          }\n\n          request.attributes.each{|attr_info|\n            # Copy the aliases from the request so that reading\n            # the response in light of the request is easier\n            if attr_info.ns_alias.nil?\n              @aliases.add(attr_info.type_uri)\n            else\n              @aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)\n            end\n            values = @data[attr_info.type_uri]\n            if values.empty? # @data defaults to []\n              zero_value_types << attr_info\n            end\n\n            if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size\n              raise Error, \"More than the number of requested values were specified for #{attr_info.type_uri.inspect}\"\n            end\n          }\n        end\n\n        kv_args = _get_extension_kv_args(@aliases)\n\n        # Add the KV args into the response with the args that are\n        # unique to the fetch_response\n        ax_args = new_args\n\n        zero_value_types.each{|attr_info|\n          name = @aliases.get_alias(attr_info.type_uri)\n          kv_args['type.' + name] = attr_info.type_uri\n          kv_args['count.' + name] = '0'\n        }\n        update_url = (request and request.update_url or @update_url)\n        ax_args['update_url'] = update_url unless update_url.nil?\n        ax_args.update(kv_args)\n        return ax_args\n      end\n\n      def parse_extension_args(ax_args)\n        super\n        @update_url = ax_args['update_url']\n      end\n\n      # Construct a FetchResponse object from an OpenID library\n      # SuccessResponse object.\n      def self.from_success_response(success_response, signed=true)\n        obj = self.new\n        if signed\n          ax_args = success_response.get_signed_ns(obj.ns_uri)\n        else\n          ax_args = success_response.message.get_args(obj.ns_uri)\n        end\n\n        begin\n          obj.parse_extension_args(ax_args)\n          return obj\n        rescue Error\n          return nil\n        end\n      end\n    end\n\n    # A store request attribute exchange message representation\n    class StoreRequest < KeyValueMessage\n\n      MODE = 'store_request'\n\n      def initialize\n        super\n        @mode = MODE\n      end\n\n      # Extract a StoreRequest from an OpenID message\n      # message: OpenID::Message\n      # return a StoreRequest or nil if AX arguments are not present\n      def self.from_openid_request(oidreq)\n        message = oidreq.message\n        ax_args = message.get_args(NS_URI)\n        return nil if ax_args.empty? or ax_args['mode'] != MODE\n        req = new\n        req.parse_extension_args(ax_args)\n        req\n      end\n\n      def get_extension_args(aliases=nil)\n        ax_args = new_args\n        kv_args = _get_extension_kv_args(aliases)\n        ax_args.update(kv_args)\n        return ax_args\n      end\n    end\n\n    # An indication that the store request was processed along with\n    # this OpenID transaction.\n    class StoreResponse < AXMessage\n      SUCCESS_MODE = 'store_response_success'\n      FAILURE_MODE = 'store_response_failure'\n      attr_reader :error_message\n\n      def initialize(succeeded = true, error_message = nil)\n        super()\n        if succeeded and error_message\n          raise Error, \"Error message included in a success response\"\n        end\n        if succeeded\n          @mode = SUCCESS_MODE\n        else\n          @mode = FAILURE_MODE\n        end\n        @error_message = error_message\n      end\n\n      def self.from_success_response(success_response)\n        ax_args = success_response.message.get_args(NS_URI)\n        ax_args.key?('error') ? new(false, ax_args['error']) : new\n      end\n\n      def succeeded?\n        @mode == SUCCESS_MODE\n      end\n\n      def get_extension_args\n        ax_args = new_args\n        if !succeeded? and error_message\n          ax_args['error'] = @error_message\n        end\n        return ax_args\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/extensions/oauth.rb",
    "content": "# An implementation of the OpenID OAuth Extension\n# Extension 1.0\n# see: http://openid.net/specs/\n\nrequire 'openid/extension'\n\nmodule OpenID\n\n  module OAuth\n    NS_URI = \"http://specs.openid.net/extensions/oauth/1.0\"\n    # An OAuth token request, sent from a relying\n    # party to a provider\n    class Request < Extension\n      attr_accessor :consumer, :scope, :ns_alias, :ns_uri\n      def initialize(consumer=nil, scope=nil)\n        @ns_alias = 'oauth'\n        @ns_uri = NS_URI\n        @consumer = consumer\n        @scope = scope\n      end\n\n\n      def get_extension_args\n        ns_args = {}\n        ns_args['consumer'] = @consumer if @consumer        \n        ns_args['scope'] = @scope if @scope\n        return ns_args\n      end\n\n      # Instantiate a Request object from the arguments in a\n      # checkid_* OpenID message\n      # return nil if the extension was not requested.\n      def self.from_openid_request(oid_req)\n        oauth_req = new\n        args = oid_req.message.get_args(NS_URI)\n        if args == {}\n          return nil\n        end\n        oauth_req.parse_extension_args(args)\n        return oauth_req\n      end\n\n      # Set the state of this request to be that expressed in these\n      # OAuth arguments\n      def parse_extension_args(args)\n        @consumer = args[\"consumer\"]\n        @scope = args[\"scope\"]\n      end\n\n    end\n\n    # A OAuth request token response, sent from a provider\n    # to a relying party\n    class Response < Extension\n      attr_accessor :request_token, :scope\n      def initialize(request_token=nil, scope=nil)\n        @ns_alias = 'oauth'\n        @ns_uri = NS_URI\n        @request_token = request_token\n        @scope = scope\n      end\n\n      # Create a Response object from an OpenID::Consumer::SuccessResponse\n      def self.from_success_response(success_response)\n        args = success_response.get_signed_ns(NS_URI)\n        return nil if args.nil?\n        oauth_resp = new\n        oauth_resp.parse_extension_args(args)\n        return oauth_resp\n      end\n\n      # parse the oauth request arguments into the\n      # internal state of this object\n      # if strict is specified, raise an exception when bad data is\n      # encountered\n      def parse_extension_args(args, strict=false)\n        @request_token = args[\"request_token\"]\n        @scope = args[\"scope\"]\n      end\n\n      def get_extension_args\n        ns_args = {}\n        ns_args['request_token'] = @request_token if @request_token\n        ns_args['scope'] = @scope if @scope\n        return ns_args\n      end\n\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/extensions/pape.rb",
    "content": "# An implementation of the OpenID Provider Authentication Policy\n# Extension 1.0\n# see: http://openid.net/specs/\n\nrequire 'openid/extension'\n\nmodule OpenID\n\n  module PAPE\n    NS_URI = \"http://specs.openid.net/extensions/pape/1.0\"\n    AUTH_MULTI_FACTOR_PHYSICAL =\n      'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical'\n    AUTH_MULTI_FACTOR =\n      'http://schemas.openid.net/pape/policies/2007/06/multi-factor'\n    AUTH_PHISHING_RESISTANT =\n      'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant'\n    TIME_VALIDATOR = /\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ/\n    # A Provider Authentication Policy request, sent from a relying\n    # party to a provider\n    class Request < Extension\n      attr_accessor :preferred_auth_policies, :max_auth_age, :ns_alias, :ns_uri\n      def initialize(preferred_auth_policies=[], max_auth_age=nil)\n        @ns_alias = 'pape'\n        @ns_uri = NS_URI\n        @preferred_auth_policies = preferred_auth_policies\n        @max_auth_age = max_auth_age\n      end\n\n      # Add an acceptable authentication policy URI to this request\n      # This method is intended to be used by the relying party to add\n      # acceptable authentication types to the request.\n      def add_policy_uri(policy_uri)\n        unless @preferred_auth_policies.member? policy_uri\n          @preferred_auth_policies << policy_uri\n        end\n      end\n\n      def get_extension_args\n        ns_args = {\n          'preferred_auth_policies' => @preferred_auth_policies.join(' ')\n        }\n        ns_args['max_auth_age'] = @max_auth_age.to_s if @max_auth_age\n        return ns_args\n      end\n\n      # Instantiate a Request object from the arguments in a\n      # checkid_* OpenID message\n      # return nil if the extension was not requested.\n      def self.from_openid_request(oid_req)\n        pape_req = new\n        args = oid_req.message.get_args(NS_URI)\n        if args == {}\n          return nil\n        end\n        pape_req.parse_extension_args(args)\n        return pape_req\n      end\n\n      # Set the state of this request to be that expressed in these\n      # PAPE arguments\n      def parse_extension_args(args)\n        @preferred_auth_policies = []\n        policies_str = args['preferred_auth_policies']\n        if policies_str\n          policies_str.split(' ').each{|uri|\n            add_policy_uri(uri)\n          }\n        end\n\n        max_auth_age_str = args['max_auth_age']\n        if max_auth_age_str\n          @max_auth_age = max_auth_age_str.to_i\n        else\n          @max_auth_age = nil\n        end\n      end\n\n      # Given a list of authentication policy URIs that a provider\n      # supports, this method returns the subset of those types\n      # that are preferred by the relying party.\n      def preferred_types(supported_types)\n        @preferred_auth_policies.select{|uri| supported_types.member? uri}\n      end\n    end\n\n    # A Provider Authentication Policy response, sent from a provider\n    # to a relying party\n    class Response < Extension\n      attr_accessor :ns_alias, :auth_policies, :auth_time, :nist_auth_level\n      def initialize(auth_policies=[], auth_time=nil, nist_auth_level=nil)\n        @ns_alias = 'pape'\n        @ns_uri = NS_URI\n        @auth_policies = auth_policies\n        @auth_time = auth_time\n        @nist_auth_level = nist_auth_level\n      end\n\n      # Add a policy URI to the response\n      # see http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies\n      def add_policy_uri(policy_uri)\n        @auth_policies << policy_uri unless @auth_policies.member?(policy_uri)\n      end\n\n      # Create a Response object from an OpenID::Consumer::SuccessResponse\n      def self.from_success_response(success_response)\n        args = success_response.get_signed_ns(NS_URI)\n        return nil if args.nil?\n        pape_resp = new\n        pape_resp.parse_extension_args(args)\n        return pape_resp\n      end\n\n      # parse the provider authentication policy arguments into the\n      # internal state of this object\n      # if strict is specified, raise an exception when bad data is\n      # encountered\n      def parse_extension_args(args, strict=false)\n        policies_str = args['auth_policies']\n        if policies_str and policies_str != 'none'\n          @auth_policies = policies_str.split(' ')\n        end\n\n        nist_level_str = args['nist_auth_level']\n        if nist_level_str\n          # special handling of zero to handle to_i behavior\n          if nist_level_str.strip == '0'\n            nist_level = 0\n          else\n            nist_level = nist_level_str.to_i\n            # if it's zero here we have a bad value\n            if nist_level == 0\n              nist_level = nil\n            end\n          end\n          if nist_level and nist_level >= 0 and nist_level < 5\n            @nist_auth_level = nist_level\n          elsif strict\n            raise ArgumentError, \"nist_auth_level must be an integer 0 through 4, not #{nist_level_str.inspect}\"\n          end\n        end\n\n        auth_time_str = args['auth_time']\n        if auth_time_str\n          # validate time string\n          if auth_time_str =~ TIME_VALIDATOR\n            @auth_time = auth_time_str\n          elsif strict\n            raise ArgumentError, \"auth_time must be in RFC3339 format\"\n          end\n        end\n      end\n\n      def get_extension_args\n        ns_args = {}\n        if @auth_policies.empty?\n          ns_args['auth_policies'] = 'none'\n        else\n          ns_args['auth_policies'] = @auth_policies.join(' ')\n        end\n        if @nist_auth_level\n          unless (0..4).member? @nist_auth_level\n            raise ArgumentError, \"nist_auth_level must be an integer 0 through 4, not #{@nist_auth_level.inspect}\"\n          end\n          ns_args['nist_auth_level'] = @nist_auth_level.to_s\n        end\n\n        if @auth_time\n          unless @auth_time =~ TIME_VALIDATOR\n            raise ArgumentError, \"auth_time must be in RFC3339 format\"\n          end\n          ns_args['auth_time'] = @auth_time\n        end\n        return ns_args\n      end\n\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/extensions/sreg.rb",
    "content": "require 'openid/extension'\nrequire 'openid/util'\nrequire 'openid/message'\n\nmodule OpenID\n  module SReg\n    DATA_FIELDS = {\n      'fullname'=>'Full Name',\n      'nickname'=>'Nickname',\n      'dob'=>'Date of Birth',\n      'email'=>'E-mail Address',\n      'gender'=>'Gender',\n      'postcode'=>'Postal Code',\n      'country'=>'Country',\n      'language'=>'Language',\n      'timezone'=>'Time Zone',\n    }\n\n    NS_URI_1_0 = 'http://openid.net/sreg/1.0'\n    NS_URI_1_1 = 'http://openid.net/extensions/sreg/1.1'\n    NS_URI = NS_URI_1_1\n\n    begin\n      Message.register_namespace_alias(NS_URI_1_1, 'sreg')\n    rescue NamespaceAliasRegistrationError => e\n      Util.log(e)\n    end\n\n    # raise ArgumentError if fieldname is not in the defined sreg fields\n    def OpenID.check_sreg_field_name(fieldname)\n      unless DATA_FIELDS.member? fieldname\n        raise ArgumentError, \"#{fieldname} is not a defined simple registration field\"\n      end\n    end\n\n    # Does the given endpoint advertise support for simple registration?\n    def OpenID.supports_sreg?(endpoint)\n      endpoint.uses_extension(NS_URI_1_1) || endpoint.uses_extension(NS_URI_1_0)\n    end\n\n    # Extract the simple registration namespace URI from the given\n    # OpenID message. Handles OpenID 1 and 2, as well as both sreg\n    # namespace URIs found in the wild, as well as missing namespace\n    # definitions (for OpenID 1)\n    def OpenID.get_sreg_ns(message)\n      [NS_URI_1_1, NS_URI_1_0].each{|ns|\n        if message.namespaces.get_alias(ns)\n          return ns\n        end\n      }\n      # try to add an alias, since we didn't find one\n      ns = NS_URI_1_1\n      begin\n        message.namespaces.add_alias(ns, 'sreg')\n      rescue IndexError\n        raise NamespaceError\n      end\n      return ns\n    end\n\n    # The simple registration namespace was not found and could not\n    # be created using the expected name (there's another extension\n    # using the name 'sreg')\n    #\n    # This is not <em>illegal</em>, for OpenID 2, although it probably\n    # indicates a problem, since it's not expected that other extensions\n    # will re-use the alias that is in use for OpenID 1.\n    #\n    # If this is an OpenID 1 request, then there is no recourse. This\n    # should not happen unless some code has modified the namespaces for\n    # the message that is being processed.\n    class NamespaceError < ArgumentError\n    end\n\n    # An object to hold the state of a simple registration request.\n    class Request < Extension\n      attr_reader :optional, :required, :ns_uri\n      attr_accessor :policy_url\n      def initialize(required = nil, optional = nil, policy_url = nil, ns_uri = NS_URI)\n        super()\n\n        @policy_url = policy_url\n        @ns_uri = ns_uri\n        @ns_alias = 'sreg'\n        @required = []\n        @optional = []\n\n        if required\n          request_fields(required, true, true)\n        end\n        if optional\n          request_fields(optional, false, true)\n        end\n      end\n\n      # Create a simple registration request that contains the\n      # fields that were requested in the OpenID request with the\n      # given arguments\n      # Takes an OpenID::CheckIDRequest, returns an OpenID::Sreg::Request\n      # return nil if the extension was not requested.\n      def self.from_openid_request(request)\n        # Since we're going to mess with namespace URI mapping, don't\n        # mutate the object that was passed in.\n        message = request.message.copy\n        ns_uri = OpenID::get_sreg_ns(message)\n        args = message.get_args(ns_uri)\n        return nil if args == {}\n        req = new(nil,nil,nil,ns_uri)\n        req.parse_extension_args(args)\n        return req\n      end\n\n      # Parse the unqualified simple registration request\n      # parameters and add them to this object.\n      #\n      # This method is essentially the inverse of\n      # getExtensionArgs. This method restores the serialized simple\n      # registration request fields.\n      #\n      # If you are extracting arguments from a standard OpenID\n      # checkid_* request, you probably want to use fromOpenIDRequest,\n      # which will extract the sreg namespace and arguments from the\n      # OpenID request. This method is intended for cases where the\n      # OpenID server needs more control over how the arguments are\n      # parsed than that method provides.\n      def parse_extension_args(args, strict = false)\n        required_items = args['required']\n        unless required_items.nil? or required_items.empty?\n          required_items.split(',').each{|field_name|\n            begin\n              request_field(field_name, true, strict)\n            rescue ArgumentError\n              raise if strict\n            end\n          }\n        end\n\n        optional_items = args['optional']\n        unless optional_items.nil? or optional_items.empty?\n          optional_items.split(',').each{|field_name|\n            begin\n              request_field(field_name, false, strict)\n            rescue ArgumentError\n              raise if strict\n            end\n          }\n        end\n        @policy_url = args['policy_url']\n      end\n\n      # A list of all of the simple registration fields that were\n      # requested, whether they were required or optional.\n      def all_requested_fields\n        @required + @optional\n      end\n\n      # Have any simple registration fields been requested?\n      def were_fields_requested?\n        !all_requested_fields.empty?\n      end\n\n      # Request the specified field from the OpenID user\n      # field_name: the unqualified simple registration field name\n      # required: whether the given field should be presented\n      #        to the user as being a required to successfully complete\n      #        the request\n      # strict: whether to raise an exception when a field is\n      #        added to a request more than once\n      # Raises ArgumentError if the field_name is not a simple registration\n      # field, or if strict is set and a field is added more than once\n      def request_field(field_name, required=false, strict=false)\n        OpenID::check_sreg_field_name(field_name)\n\n        if strict\n          if (@required + @optional).member? field_name\n            raise ArgumentError, 'That field has already been requested'\n          end\n        else\n          return if @required.member? field_name\n          if @optional.member? field_name\n            if required\n              @optional.delete field_name\n            else\n              return\n            end\n          end\n        end\n        if required\n          @required << field_name\n        else\n          @optional << field_name\n        end\n      end\n\n      # Add the given list of fields to the request.\n      def request_fields(field_names, required = false, strict = false)\n        raise ArgumentError unless field_names.respond_to?(:each) and\n                                   field_names[0].is_a?(String)\n        field_names.each{|fn|request_field(fn, required, strict)}\n      end\n\n      # Get a hash of unqualified simple registration arguments\n      # representing this request.\n      # This method is essentially the inverse of parse_extension_args.\n      # This method serializes the simple registration request fields.\n      def get_extension_args\n        args = {}\n        args['required'] = @required.join(',') unless @required.empty?\n        args['optional'] = @optional.join(',') unless @optional.empty?\n        args['policy_url'] = @policy_url unless @policy_url.nil?\n        return args\n      end\n\n      def member?(field_name)\n        all_requested_fields.member?(field_name)\n      end\n\n    end\n\n    # Represents the data returned in a simple registration response\n    # inside of an OpenID id_res response. This object will be\n    # created by the OpenID server, added to the id_res response\n    # object, and then extracted from the id_res message by the Consumer.\n    class Response < Extension\n      attr_reader :ns_uri, :data\n\n      def initialize(data = {}, ns_uri=NS_URI)\n        @ns_alias = 'sreg'\n        @data = data\n        @ns_uri = ns_uri\n      end\n\n      # Take a Request and a hash of simple registration\n      # values and create a Response object containing that data.\n      def self.extract_response(request, data)\n        arf = request.all_requested_fields\n        resp_data = data.reject{|k,v| !arf.member?(k) || v.nil? }\n        new(resp_data, request.ns_uri)\n      end\n\n      # Create an Response object from an\n      # OpenID::Consumer::SuccessResponse from consumer.complete\n      # If you set the signed_only parameter to false, unsigned data from\n      # the id_res message from the server will be processed.\n      def self.from_success_response(success_response, signed_only = true)\n        ns_uri = OpenID::get_sreg_ns(success_response.message)\n        if signed_only\n          args = success_response.get_signed_ns(ns_uri)\n          return nil if args.nil? # No signed args, so fail\n        else\n          args = success_response.message.get_args(ns_uri)\n        end\n        args.reject!{|k,v| !DATA_FIELDS.member?(k) }\n        new(args, ns_uri)\n      end\n\n      # Get the fields to put in the simple registration namespace\n      # when adding them to an id_res message.\n      def get_extension_args\n        return @data\n      end\n\n      # Read-only hashlike interface.\n      # Raises an exception if the field name is bad\n      def [](field_name)\n        OpenID::check_sreg_field_name(field_name)\n        data[field_name]\n      end\n\n      def empty?\n        @data.empty?\n      end\n      # XXX is there more to a hashlike interface I should add?\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/openid/extensions/ui.rb",
    "content": "# An implementation of the OpenID User Interface Extension 1.0 - DRAFT 0.5\n# see: http://svn.openid.net/repos/specifications/user_interface/1.0/trunk/openid-user-interface-extension-1_0.html\n\nrequire 'openid/extension'\n\nmodule OpenID\n\n  module UI\n    NS_URI = \"http://specs.openid.net/extensions/ui/1.0\"\n\n    class Request < Extension\n      attr_accessor :lang, :icon, :mode, :ns_alias, :ns_uri\n      def initialize(mode = nil, icon = nil, lang = nil)\n        @ns_alias = 'ui'\n        @ns_uri = NS_URI\n        @lang = lang\n        @icon = icon\n        @mode = mode\n      end\n\n      def get_extension_args\n        ns_args = {}\n        ns_args['lang'] = @lang if @lang\n        ns_args['icon'] = @icon if @icon\n        ns_args['mode'] = @mode if @mode\n        return ns_args\n      end\n\n      # Instantiate a Request object from the arguments in a\n      # checkid_* OpenID message\n      # return nil if the extension was not requested.\n      def self.from_openid_request(oid_req)\n        ui_req = new\n        args = oid_req.message.get_args(NS_URI)\n        if args == {}\n          return nil\n        end\n        ui_req.parse_extension_args(args)\n        return ui_req\n      end\n\n      # Set UI extension parameters\n      def parse_extension_args(args)\n        @lang = args[\"lang\"]\n        @icon = args[\"icon\"]\n        @mode = args[\"mode\"]\n      end\n\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/fetchers.rb",
    "content": "require 'net/http'\nrequire 'openid/util'\nrequire 'openid/version'\n\nbegin\n  require 'net/https'\nrescue LoadError\n  OpenID::Util.log('WARNING: no SSL support found.  Will not be able ' +\n                   'to fetch HTTPS URLs!')\n  require 'net/http'\nend\n\nMAX_RESPONSE_KB = 10485760 # 10 MB (can be smaller, I guess)\n\nmodule Net\n  class HTTP\n    def post_connection_check(hostname)\n      check_common_name = true\n      cert = @socket.io.peer_cert\n      cert.extensions.each { |ext|\n        next if ext.oid != \"subjectAltName\"\n        ext.value.split(/,\\s+/).each{ |general_name|\n          if /\\ADNS:(.*)/ =~ general_name\n            check_common_name = false\n            reg = Regexp.escape($1).gsub(/\\\\\\*/, \"[^.]+\")\n            return true if /\\A#{reg}\\z/i =~ hostname\n          elsif /\\AIP Address:(.*)/ =~ general_name\n            check_common_name = false\n            return true if $1 == hostname\n          end\n        }\n      }\n      if check_common_name\n        cert.subject.to_a.each{ |oid, value|\n          if oid == \"CN\"\n            reg = Regexp.escape(value).gsub(/\\\\\\*/, \"[^.]+\")\n            return true if /\\A#{reg}\\z/i =~ hostname\n          end\n        }\n      end\n      raise OpenSSL::SSL::SSLError, \"hostname does not match\"\n    end\n  end\nend\n\nmodule OpenID\n  # Our HTTPResponse class extends Net::HTTPResponse with an additional\n  # method, final_url.\n  class HTTPResponse\n    attr_accessor :final_url\n\n    attr_accessor :_response\n\n    def self._from_net_response(response, final_url, headers=nil)\n      me = self.new\n      me._response = response\n      me.final_url = final_url\n      return me\n    end\n\n    def method_missing(method, *args)\n      @_response.send(method, *args)\n    end\n\n    def body=(s)\n      @_response.instance_variable_set('@body', s)\n      # XXX Hack to work around ruby's HTTP library behavior.  @body\n      # is only returned if it has been read from the response\n      # object's socket, but since we're not using a socket in this\n      # case, we need to set the @read flag to true to avoid a bug in\n      # Net::HTTPResponse.stream_check when @socket is nil.\n      @_response.instance_variable_set('@read', true)\n    end\n  end\n\n  class FetchingError < OpenIDError\n  end\n\n  class HTTPRedirectLimitReached < FetchingError\n  end\n\n  class SSLFetchingError < FetchingError\n  end\n\n  @fetcher = nil\n\n  def self.fetch(url, body=nil, headers=nil,\n                 redirect_limit=StandardFetcher::REDIRECT_LIMIT)\n    return fetcher.fetch(url, body, headers, redirect_limit)\n  end\n\n  def self.fetcher\n    if @fetcher.nil?\n      @fetcher = StandardFetcher.new\n    end\n\n    return @fetcher\n  end\n\n  def self.fetcher=(fetcher)\n    @fetcher = fetcher\n  end\n\n  # Set the default fetcher to use the HTTP proxy defined in the environment\n  # variable 'http_proxy'.\n  def self.fetcher_use_env_http_proxy\n    proxy_string = ENV['http_proxy']\n    return unless proxy_string\n\n    proxy_uri = URI.parse(proxy_string)\n    @fetcher = StandardFetcher.new(proxy_uri.host, proxy_uri.port,\n                                   proxy_uri.user, proxy_uri.password)\n  end\n  \n  class StandardFetcher\n\n    USER_AGENT = \"ruby-openid/#{OpenID::VERSION} (#{RUBY_PLATFORM})\"\n\n    REDIRECT_LIMIT = 5\n    TIMEOUT = ENV['RUBY_OPENID_FETCHER_TIMEOUT'] || 60\n\n    attr_accessor :ca_file\n    attr_accessor :timeout\n\n    # I can fetch through a HTTP proxy; arguments are as for Net::HTTP::Proxy.\n    def initialize(proxy_addr=nil, proxy_port=nil,\n                   proxy_user=nil, proxy_pass=nil)\n      @ca_file = nil\n      @proxy = Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user, proxy_pass)\n      @timeout = TIMEOUT\n    end\n\n    def supports_ssl?(conn)\n      return conn.respond_to?(:use_ssl=)\n    end\n\n    def make_http(uri)\n      http = @proxy.new(uri.host, uri.port)\n      http.read_timeout = @timeout\n      http.open_timeout = @timeout\n      return http\n    end\n\n    def set_verified(conn, verify)\n      if verify\n        conn.verify_mode = OpenSSL::SSL::VERIFY_PEER\n      else\n        conn.verify_mode = OpenSSL::SSL::VERIFY_NONE\n      end\n    end\n\n    def make_connection(uri)\n      conn = make_http(uri)\n\n      if !conn.is_a?(Net::HTTP)\n        raise RuntimeError, sprintf(\"Expected Net::HTTP object from make_http; got %s\",\n                                    conn.class)\n      end\n\n      if uri.scheme == 'https'\n        if supports_ssl?(conn)\n\n          conn.use_ssl = true\n\n          if @ca_file\n            set_verified(conn, true)\n            conn.ca_file = @ca_file\n          else\n            Util.log(\"WARNING: making https request to #{uri} without verifying \" +\n                     \"server certificate; no CA path was specified.\")\n            set_verified(conn, false)\n          end\n        else\n          raise RuntimeError, \"SSL support not found; cannot fetch #{uri}\"\n        end\n      end\n\n      return conn\n    end\n\n    def fetch(url, body=nil, headers=nil, redirect_limit=REDIRECT_LIMIT)\n      unparsed_url = url.dup\n      url = URI::parse(url)\n      if url.nil?\n        raise FetchingError, \"Invalid URL: #{unparsed_url}\"\n      end\n\n      headers ||= {}\n      headers['User-agent'] ||= USER_AGENT\n\n      begin\n        conn = make_connection(url)\n        response = nil\n\n        whole_body = ''\n        body_size_limitter = lambda do |r|\n          r.read_body do |partial|   # read body now\n            whole_body << partial\n            if whole_body.length > MAX_RESPONSE_KB\n              raise FetchingError.new(\"Response Too Large\")\n            end\n          end\n          whole_body\n        end\n        response = conn.start {\n          # Check the certificate against the URL's hostname\n          if supports_ssl?(conn) and conn.use_ssl?\n            conn.post_connection_check(url.host)\n          end\n\n          if body.nil?\n            conn.request_get(url.request_uri, headers, &body_size_limitter)\n          else\n            headers[\"Content-type\"] ||= \"application/x-www-form-urlencoded\"\n            conn.request_post(url.request_uri, body, headers, &body_size_limitter)\n          end\n        }\n      rescue Timeout::Error => why\n        raise FetchingError, \"Error fetching #{url}: #{why}\"\n      rescue RuntimeError => why\n        raise why\n      rescue OpenSSL::SSL::SSLError => why\n        raise SSLFetchingError, \"Error connecting to SSL URL #{url}: #{why}\"\n      rescue FetchingError => why\n        raise why\n      rescue Exception => why\n        raise FetchingError, \"Error fetching #{url}: #{why}\"\n      end\n\n      case response\n      when Net::HTTPRedirection\n        if redirect_limit <= 0\n          raise HTTPRedirectLimitReached.new(\n            \"Too many redirects, not fetching #{response['location']}\")\n        end\n        begin\n          return fetch(response['location'], body, headers, redirect_limit - 1)\n        rescue HTTPRedirectLimitReached => e\n          raise e\n        rescue FetchingError => why\n          raise FetchingError, \"Error encountered in redirect from #{url}: #{why}\"\n        end\n      else\n        response = HTTPResponse._from_net_response(response, unparsed_url)\n        response.body = whole_body\n        setup_encoding(response)\n        return response\n      end\n    end\n\n    private\n    def setup_encoding(response)\n      return unless defined?(Encoding.default_external)\n      return unless charset = response.type_params[\"charset\"]\n\n      begin\n        encoding = Encoding.find(charset)\n      rescue ArgumentError\n      end\n      encoding ||= Encoding.default_external\n\n      body = response.body\n      if body.respond_to?(:force_encoding)\n        body.force_encoding(encoding)\n      else\n        body.set_encoding(encoding)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/kvform.rb",
    "content": "\nmodule OpenID\n\n  class KVFormError < Exception\n  end\n\n  module Util\n\n    def Util.seq_to_kv(seq, strict=false)\n      # Represent a sequence of pairs of strings as newline-terminated\n      # key:value pairs. The pairs are generated in the order given.\n      #\n      # @param seq: The pairs\n      #\n      # returns a string representation of the sequence\n      err = lambda { |msg|\n        msg = \"seq_to_kv warning: #{msg}: #{seq.inspect}\"\n        if strict\n          raise KVFormError, msg\n        else\n          Util.log(msg)\n        end\n      }\n\n      lines = []\n      seq.each { |k, v|\n        if !k.is_a?(String)\n          err.call(\"Converting key to string: #{k.inspect}\")\n          k = k.to_s\n        end\n\n        if !k.index(\"\\n\").nil?\n          raise KVFormError, \"Invalid input for seq_to_kv: key contains newline: #{k.inspect}\"\n        end\n\n        if !k.index(\":\").nil?\n          raise KVFormError, \"Invalid input for seq_to_kv: key contains colon: #{k.inspect}\"\n        end\n\n        if k.strip() != k\n          err.call(\"Key has whitespace at beginning or end: #{k.inspect}\")\n        end\n\n        if !v.is_a?(String)\n          err.call(\"Converting value to string: #{v.inspect}\")\n          v = v.to_s\n        end\n\n        if !v.index(\"\\n\").nil?\n          raise KVFormError, \"Invalid input for seq_to_kv: value contains newline: #{v.inspect}\"\n        end\n\n        if v.strip() != v\n          err.call(\"Value has whitespace at beginning or end: #{v.inspect}\")\n        end\n\n        lines << k + \":\" + v + \"\\n\"\n      }\n\n      return lines.join(\"\")\n    end\n\n    def Util.kv_to_seq(data, strict=false)\n      # After one parse, seq_to_kv and kv_to_seq are inverses, with no\n      # warnings:\n      #\n      # seq = kv_to_seq(s)\n      # seq_to_kv(kv_to_seq(seq)) == seq\n      err = lambda { |msg|\n        msg = \"kv_to_seq warning: #{msg}: #{data.inspect}\"\n        if strict\n          raise KVFormError, msg\n        else\n          Util.log(msg)\n        end\n      }\n\n      lines = data.split(\"\\n\")\n      if data.length == 0\n        return []\n      end\n\n      if data[-1].chr != \"\\n\"\n        err.call(\"Does not end in a newline\")\n        # We don't expect the last element of lines to be an empty\n        # string because split() doesn't behave that way.\n      end\n\n      pairs = []\n      line_num = 0\n      lines.each { |line|\n        line_num += 1\n\n        # Ignore blank lines\n        if line.strip() == \"\"\n          next\n        end\n\n        pair = line.split(':', 2)\n        if pair.length == 2\n          k, v = pair\n          k_s = k.strip()\n          if k_s != k\n            msg = \"In line #{line_num}, ignoring leading or trailing whitespace in key #{k.inspect}\"\n            err.call(msg)\n          end\n\n          if k_s.length == 0\n            err.call(\"In line #{line_num}, got empty key\")\n          end\n\n          v_s = v.strip()\n          if v_s != v\n            msg = \"In line #{line_num}, ignoring leading or trailing whitespace in value #{v.inspect}\"\n            err.call(msg)\n          end\n\n          pairs << [k_s, v_s]\n        else\n          err.call(\"Line #{line_num} does not contain a colon\")\n        end\n      }\n\n      return pairs\n    end\n\n    def Util.dict_to_kv(d)\n      return seq_to_kv(d.entries.sort)\n    end\n\n    def Util.kv_to_dict(s)\n      seq = kv_to_seq(s)\n      return Hash[*seq.flatten]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/kvpost.rb",
    "content": "require \"openid/message\"\nrequire \"openid/fetchers\"\n\nmodule OpenID\n  # Exception that is raised when the server returns a 400 response\n  # code to a direct request.\n  class ServerError < OpenIDError\n    attr_reader :error_text, :error_code, :message\n\n    def initialize(error_text, error_code, message)\n      super(error_text)\n      @error_text = error_text\n      @error_code = error_code\n      @message = message\n    end\n\n    def self.from_message(msg)\n      error_text = msg.get_arg(OPENID_NS, 'error',\n                               '<no error message supplied>')\n      error_code = msg.get_arg(OPENID_NS, 'error_code')\n      return self.new(error_text, error_code, msg)\n    end\n  end\n\n  class KVPostNetworkError < OpenIDError\n  end\n  class HTTPStatusError < OpenIDError\n  end\n\n  class Message\n    def self.from_http_response(response, server_url)\n      msg = self.from_kvform(response.body)\n      case response.code.to_i\n      when 200\n        return msg\n      when 206\n        return msg\n      when 400\n        raise ServerError.from_message(msg)\n      else\n        error_message = \"bad status code from server #{server_url}: \"\\\n        \"#{response.code}\"\n        raise HTTPStatusError.new(error_message)\n      end\n    end\n  end\n\n  # Send the message to the server via HTTP POST and receive and parse\n  # a response in KV Form\n  def self.make_kv_post(request_message, server_url)\n    begin\n      http_response = self.fetch(server_url, request_message.to_url_encoded)\n    rescue Exception\n      raise KVPostNetworkError.new(\"Unable to contact OpenID server: #{$!.to_s}\")\n    end\n    return Message.from_http_response(http_response, server_url)\n  end\nend\n"
  },
  {
    "path": "lib/openid/message.rb",
    "content": "require 'openid/util'\nrequire 'openid/kvform'\n\nmodule OpenID\n\n  IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select'\n\n  # URI for Simple Registration extension, the only commonly deployed\n  # OpenID 1.x extension, and so a special case.\n  SREG_URI = 'http://openid.net/sreg/1.0'\n\n  # The OpenID 1.x namespace URIs\n  OPENID1_NS = 'http://openid.net/signon/1.0'\n  OPENID11_NS = 'http://openid.net/signon/1.1'\n  OPENID1_NAMESPACES = [OPENID1_NS, OPENID11_NS]\n\n  # The OpenID 2.0 namespace URI\n  OPENID2_NS = 'http://specs.openid.net/auth/2.0'\n\n  # The namespace consisting of pairs with keys that are prefixed with\n  # \"openid.\" but not in another namespace.\n  NULL_NAMESPACE = :null_namespace\n\n  # The null namespace, when it is an allowed OpenID namespace\n  OPENID_NS = :openid_namespace\n\n  # The top-level namespace, excluding all pairs with keys that start\n  # with \"openid.\"\n  BARE_NS = :bare_namespace\n\n  # Limit, in bytes, of identity provider and return_to URLs,\n  # including response payload.  See OpenID 1.1 specification,\n  # Appendix D.\n  OPENID1_URL_LIMIT = 2047\n\n  # All OpenID protocol fields.  Used to check namespace aliases.\n  OPENID_PROTOCOL_FIELDS = [\n                            'ns', 'mode', 'error', 'return_to',\n                            'contact', 'reference', 'signed',\n                            'assoc_type', 'session_type',\n                            'dh_modulus', 'dh_gen',\n                            'dh_consumer_public', 'claimed_id',\n                            'identity', 'realm', 'invalidate_handle',\n                            'op_endpoint', 'response_nonce', 'sig',\n                            'assoc_handle', 'trust_root', 'openid',\n                           ]\n\n  # Sentinel used for Message implementation to indicate that getArg\n  # should raise an exception instead of returning a default.\n  NO_DEFAULT = :no_default\n\n  # Raised if the generic OpenID namespace is accessed when there\n  # is no OpenID namespace set for this message.\n  class UndefinedOpenIDNamespace < Exception; end\n\n  # Raised when an alias or namespace URI has already been registered.\n  class NamespaceAliasRegistrationError < Exception; end\n\n  # Raised if openid.ns is not a recognized value.\n  # See Message class variable @@allowed_openid_namespaces\n  class InvalidOpenIDNamespace < Exception; end\n\n  class Message\n    attr_reader :namespaces\n\n    # Raised when key lookup fails\n    class KeyNotFound < IndexError ; end\n\n    # Namespace / alias registration map.  See\n    # register_namespace_alias.\n    @@registered_aliases = {}\n\n    # Registers a (namespace URI, alias) mapping in a global namespace\n    # alias map.  Raises NamespaceAliasRegistrationError if either the\n    # namespace URI or alias has already been registered with a\n    # different value.  This function is required if you want to use a\n    # namespace with an OpenID 1 message.\n    def Message.register_namespace_alias(namespace_uri, alias_)\n      if @@registered_aliases[alias_] == namespace_uri\n          return\n      end\n\n      if @@registered_aliases.values.include?(namespace_uri)\n        raise NamespaceAliasRegistrationError,\n          'Namespace uri #{namespace_uri} already registered'\n      end\n\n      if @@registered_aliases.member?(alias_)\n        raise NamespaceAliasRegistrationError,\n          'Alias #{alias_} already registered'\n      end\n\n      @@registered_aliases[alias_] = namespace_uri\n    end\n\n    @@allowed_openid_namespaces = [OPENID1_NS, OPENID2_NS, OPENID11_NS]\n\n    # Raises InvalidNamespaceError if you try to instantiate a Message\n    # with a namespace not in the above allowed list\n    def initialize(openid_namespace=nil)\n      @args = {}\n      @namespaces = NamespaceMap.new\n      if openid_namespace\n        implicit = OPENID1_NAMESPACES.member? openid_namespace\n        self.set_openid_namespace(openid_namespace, implicit)\n      else\n        @openid_ns_uri = nil\n      end\n    end\n\n    # Construct a Message containing a set of POST arguments.\n    # Raises InvalidNamespaceError if you try to instantiate a Message\n    # with a namespace not in the above allowed list\n    def Message.from_post_args(args)\n      m = Message.new\n      openid_args = {}\n      args.each do |key,value|\n        if value.is_a?(Array)\n          raise ArgumentError, \"Query dict must have one value for each key, \" +\n            \"not lists of values.  Query is #{args.inspect}\"\n        end\n\n        prefix, rest = key.split('.', 2)\n\n        if prefix != 'openid' or rest.nil?\n          m.set_arg(BARE_NS, key, value)\n        else\n          openid_args[rest] = value\n        end\n      end\n\n      m._from_openid_args(openid_args)\n      return m\n    end\n\n    # Construct a Message from a parsed KVForm message.\n    # Raises InvalidNamespaceError if you try to instantiate a Message\n    # with a namespace not in the above allowed list\n    def Message.from_openid_args(openid_args)\n      m = Message.new\n      m._from_openid_args(openid_args)\n      return m\n    end\n\n    # Raises InvalidNamespaceError if you try to instantiate a Message\n    # with a namespace not in the above allowed list\n    def _from_openid_args(openid_args)\n      ns_args = []\n\n      # resolve namespaces\n      openid_args.each { |rest, value|\n        ns_alias, ns_key = rest.split('.', 2)\n        if ns_key.nil?\n          ns_alias = NULL_NAMESPACE\n          ns_key = rest\n        end\n\n        if ns_alias == 'ns'\n          @namespaces.add_alias(value, ns_key)\n        elsif ns_alias == NULL_NAMESPACE and ns_key == 'ns'\n          set_openid_namespace(value, false)\n        else\n          ns_args << [ns_alias, ns_key, value]\n        end\n      }\n\n      # implicitly set an OpenID 1 namespace\n      unless get_openid_namespace\n        set_openid_namespace(OPENID1_NS, true)\n      end\n\n      # put the pairs into the appropriate namespaces\n      ns_args.each { |ns_alias, ns_key, value|\n        ns_uri = @namespaces.get_namespace_uri(ns_alias)\n        unless ns_uri\n          ns_uri = _get_default_namespace(ns_alias)\n          unless ns_uri\n            ns_uri = get_openid_namespace\n            ns_key = \"#{ns_alias}.#{ns_key}\"\n          else\n            @namespaces.add_alias(ns_uri, ns_alias, true)\n          end\n        end\n        self.set_arg(ns_uri, ns_key, value)\n      }\n    end\n\n    def _get_default_namespace(mystery_alias)\n      # only try to map an alias to a default if it's an\n      # OpenID 1.x namespace\n      if is_openid1\n        @@registered_aliases[mystery_alias]\n      end\n    end\n\n    def set_openid_namespace(openid_ns_uri, implicit)\n      if !@@allowed_openid_namespaces.include?(openid_ns_uri)\n        raise InvalidOpenIDNamespace, \"Invalid null namespace: #{openid_ns_uri}\"\n      end\n      @namespaces.add_alias(openid_ns_uri, NULL_NAMESPACE, implicit)\n      @openid_ns_uri = openid_ns_uri\n    end\n\n    def get_openid_namespace\n      return @openid_ns_uri\n    end\n\n    def is_openid1\n      return OPENID1_NAMESPACES.member?(@openid_ns_uri)\n    end\n\n    def is_openid2\n      return @openid_ns_uri == OPENID2_NS\n    end\n\n    # Create a message from a KVForm string\n    def Message.from_kvform(kvform_string)\n      return Message.from_openid_args(Util.kv_to_dict(kvform_string))\n    end\n\n    def copy\n      return Marshal.load(Marshal.dump(self))\n    end\n\n    # Return all arguments with \"openid.\" in from of namespaced arguments.\n    def to_post_args\n      args = {}\n\n      # add namespace defs to the output\n      @namespaces.each { |ns_uri, ns_alias|\n        if @namespaces.implicit?(ns_uri)\n          next\n        end\n        if ns_alias == NULL_NAMESPACE\n          ns_key = 'openid.ns'\n        else\n          ns_key = 'openid.ns.' + ns_alias\n        end\n        args[ns_key] = ns_uri\n      }\n\n      @args.each { |k, value|\n        ns_uri, ns_key = k\n        key = get_key(ns_uri, ns_key)\n        args[key] = value\n      }\n\n      return args\n    end\n\n    # Return all namespaced arguments, failing if any non-namespaced arguments\n    # exist.\n    def to_args\n      post_args = self.to_post_args\n      kvargs = {}\n      post_args.each { |k,v|\n        if !k.start_with?('openid.')\n          raise ArgumentError, \"This message can only be encoded as a POST, because it contains arguments that are not prefixed with 'openid.'\"\n        else\n          kvargs[k[7..-1]] = v\n        end\n      }\n      return kvargs\n    end\n\n    # Generate HTML form markup that contains the values in this\n    # message, to be HTTP POSTed as x-www-form-urlencoded UTF-8.\n    def to_form_markup(action_url, form_tag_attrs=nil, submit_text='Continue')\n      form_tag_attr_map = {}\n\n      if form_tag_attrs\n        form_tag_attrs.each { |name, attr|\n          form_tag_attr_map[name] = attr\n        }\n      end\n\n      form_tag_attr_map['action'] = action_url\n      form_tag_attr_map['method'] = 'post'\n      form_tag_attr_map['accept-charset'] = 'UTF-8'\n      form_tag_attr_map['enctype'] = 'application/x-www-form-urlencoded'\n\n      markup = \"<form \"\n\n      form_tag_attr_map.each { |k, v|\n        markup += \" #{k}=\\\"#{v}\\\"\"\n      }\n\n      markup += \">\\n\"\n\n      to_post_args.each { |k,v|\n        markup += \"<input type='hidden' name='#{k}' value='#{OpenID::Util.html_encode(v)}' />\\n\"\n      }\n      markup += \"<input type='submit' value='#{submit_text}' />\\n\"\n      markup += \"\\n</form>\"\n      return markup\n    end\n\n    # Generate a GET URL with the paramters in this message attacked as\n    # query parameters.\n    def to_url(base_url)\n      return Util.append_args(base_url, self.to_post_args)\n    end\n\n    # Generate a KVForm string that contains the parameters in this message.\n    # This will fail is the message contains arguments outside of the\n    # \"openid.\" prefix.\n    def to_kvform\n      return Util.dict_to_kv(to_args)\n    end\n\n    # Generate an x-www-urlencoded string.\n    def to_url_encoded\n      args = to_post_args.map.sort\n      return Util.urlencode(args)\n    end\n\n    # Convert an input value into the internally used values of this obejct.\n    def _fix_ns(namespace)\n      if namespace == OPENID_NS\n        unless @openid_ns_uri\n          raise UndefinedOpenIDNamespace, 'OpenID namespace not set'\n        else\n          namespace = @openid_ns_uri\n        end\n      end\n\n      if namespace == BARE_NS\n        return namespace\n      end\n\n      if !namespace.is_a?(String)\n        raise ArgumentError, (\"Namespace must be BARE_NS, OPENID_NS or \"\\\n                              \"a string. Got #{namespace.inspect}\")\n      end\n\n      if namespace.index(':').nil?\n        msg = (\"OpenID 2.0 namespace identifiers SHOULD be URIs. \"\\\n               \"Got #{namespace.inspect}\")\n        Util.log(msg)\n\n        if namespace == 'sreg'\n          msg = \"Using #{SREG_URI} instead of \\\"sreg\\\" as namespace\"\n          Util.log(msg)\n          return SREG_URI\n        end\n      end\n\n      return namespace\n    end\n\n    def has_key?(namespace, ns_key)\n      namespace = _fix_ns(namespace)\n      return @args.member?([namespace, ns_key])\n    end\n\n    # Get the key for a particular namespaced argument\n    def get_key(namespace, ns_key)\n      namespace = _fix_ns(namespace)\n      return ns_key if namespace == BARE_NS\n\n      ns_alias = @namespaces.get_alias(namespace)\n\n      # no alias is defined, so no key can exist\n      return nil if ns_alias.nil?\n\n      if ns_alias == NULL_NAMESPACE\n        tail = ns_key\n      else\n        tail = \"#{ns_alias}.#{ns_key}\"\n      end\n\n      return 'openid.' + tail\n    end\n\n    # Get a value for a namespaced key.\n    def get_arg(namespace, key, default=nil)\n      namespace = _fix_ns(namespace)\n      @args.fetch([namespace, key]) {\n        if default == NO_DEFAULT\n          raise KeyNotFound, \"<#{namespace}>#{key} not in this message\"\n        else\n          default\n        end\n      }\n    end\n\n    # Get the arguments that are defined for this namespace URI.\n    def get_args(namespace)\n      namespace = _fix_ns(namespace)\n      args = {}\n      @args.each { |k,v|\n        pair_ns, ns_key = k\n        args[ns_key] = v if pair_ns == namespace\n      }\n      return args\n    end\n\n    # Set multiple key/value pairs in one call.\n    def update_args(namespace, updates)\n      namespace = _fix_ns(namespace)\n      updates.each {|k,v| set_arg(namespace, k, v)}\n    end\n\n    # Set a single argument in this namespace\n    def set_arg(namespace, key, value)\n      namespace = _fix_ns(namespace)\n      @args[[namespace, key].freeze] = value\n      if namespace != BARE_NS\n        @namespaces.add(namespace)\n      end\n    end\n\n    # Remove a single argument from this namespace.\n    def del_arg(namespace, key)\n      namespace = _fix_ns(namespace)\n      _key = [namespace, key]\n      @args.delete(_key)\n    end\n\n    def ==(other)\n      other.is_a?(self.class) && @args == other.instance_eval { @args }\n    end\n\n    def get_aliased_arg(aliased_key, default=nil)\n      if aliased_key == 'ns'\n        return get_openid_namespace()\n      end\n\n      ns_alias, key = aliased_key.split('.', 2)\n      if ns_alias == 'ns'\n        uri = @namespaces.get_namespace_uri(key)\n        if uri.nil? and default == NO_DEFAULT\n          raise KeyNotFound, \"Namespace #{key} not defined when looking \"\\\n                             \"for #{aliased_key}\"\n        else\n          return (uri.nil? ? default : uri)\n        end\n      end\n\n      if key.nil?\n        key = aliased_key\n        ns = nil\n      else\n        ns = @namespaces.get_namespace_uri(ns_alias)\n      end\n\n      if ns.nil?\n        key = aliased_key\n        ns = get_openid_namespace\n      end\n\n      return get_arg(ns, key, default)\n    end\n  end\n\n\n  # Maintains a bidirectional map between namespace URIs and aliases.\n  class NamespaceMap\n\n    def initialize\n      @alias_to_namespace = {}\n      @namespace_to_alias = {}\n      @implicit_namespaces = []\n    end\n\n    def get_alias(namespace_uri)\n      @namespace_to_alias[namespace_uri]\n    end\n\n    def get_namespace_uri(namespace_alias)\n      @alias_to_namespace[namespace_alias]\n    end\n\n    # Add an alias from this namespace URI to the alias.\n    def add_alias(namespace_uri, desired_alias, implicit=false)\n      # Check that desired_alias is not an openid protocol field as\n      # per the spec.\n      Util.assert(!OPENID_PROTOCOL_FIELDS.include?(desired_alias),\n             \"#{desired_alias} is not an allowed namespace alias\")\n\n      # check that there is not a namespace already defined for the\n      # desired alias\n      current_namespace_uri = @alias_to_namespace.fetch(desired_alias, nil)\n      if current_namespace_uri and current_namespace_uri != namespace_uri\n        raise IndexError, \"Cannot map #{namespace_uri} to alias #{desired_alias}. #{current_namespace_uri} is already mapped to alias #{desired_alias}\"\n      end\n\n      # Check that desired_alias does not contain a period as per the\n      # spec.\n      if desired_alias.is_a?(String)\n          Util.assert(desired_alias.index('.').nil?,\n                 \"#{desired_alias} must not contain a dot\")\n      end\n\n      # check that there is not already a (different) alias for this\n      # namespace URI.\n      _alias = @namespace_to_alias[namespace_uri]\n      if _alias and _alias != desired_alias\n        raise IndexError, \"Cannot map #{namespace_uri} to alias #{desired_alias}. It is already mapped to alias #{_alias}\"\n      end\n\n      @alias_to_namespace[desired_alias] = namespace_uri\n      @namespace_to_alias[namespace_uri] = desired_alias\n      @implicit_namespaces << namespace_uri if implicit\n      return desired_alias\n    end\n\n    # Add this namespace URI to the mapping, without caring what alias\n    # it ends up with.\n    def add(namespace_uri)\n      # see if this namepace is already mapped to an alias\n      _alias = @namespace_to_alias[namespace_uri]\n      return _alias if _alias\n\n      # Fall back to generating a numberical alias\n      i = 0\n      while true\n        _alias = 'ext' + i.to_s\n        begin\n          add_alias(namespace_uri, _alias)\n        rescue IndexError\n          i += 1\n        else\n          return _alias\n        end\n      end\n\n      raise StandardError, 'Unreachable'\n    end\n\n    def member?(namespace_uri)\n      @namespace_to_alias.has_key?(namespace_uri)\n    end\n\n    def each\n      @namespace_to_alias.each {|k,v| yield k,v}\n    end\n\n    def namespace_uris\n      # Return an iterator over the namespace URIs\n      return @namespace_to_alias.keys()\n    end\n\n    def implicit?(namespace_uri)\n      return @implicit_namespaces.member?(namespace_uri)\n    end\n\n    def aliases\n      # Return an iterator over the aliases\n      return @alias_to_namespace.keys()\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/protocolerror.rb",
    "content": "require 'openid/util'\n\nmodule OpenID\n\n  # An error in the OpenID protocol\n  class ProtocolError < OpenIDError\n  end\nend\n"
  },
  {
    "path": "lib/openid/server.rb",
    "content": "\nrequire 'openid/cryptutil'\nrequire 'openid/util'\nrequire 'openid/dh'\nrequire 'openid/store/nonce'\nrequire 'openid/trustroot'\nrequire 'openid/association'\nrequire 'openid/message'\n\nrequire 'time'\n\nmodule OpenID\n\n  module Server\n\n    HTTP_OK = 200\n    HTTP_REDIRECT = 302\n    HTTP_ERROR = 400\n\n    BROWSER_REQUEST_MODES = ['checkid_setup', 'checkid_immediate']\n\n    ENCODE_KVFORM = ['kvform'].freeze\n    ENCODE_URL = ['URL/redirect'].freeze\n    ENCODE_HTML_FORM = ['HTML form'].freeze\n\n    UNUSED = nil\n\n    class OpenIDRequest\n      attr_accessor :message, :mode\n\n      # I represent an incoming OpenID request.\n      #\n      # Attributes:\n      # mode:: The \"openid.mode\" of this request\n      def initialize\n        @mode = nil\n        @message = nil\n      end\n\n      def namespace\n        if @message.nil?\n          raise RuntimeError, \"Request has no message\"\n        else\n          return @message.get_openid_namespace\n        end\n      end\n    end\n\n    # A request to verify the validity of a previous response.\n    #\n    # See OpenID Specs, Verifying Directly with the OpenID Provider\n    # <http://openid.net/specs/openid-authentication-2_0-12.html#verifying_signatures>\n    class CheckAuthRequest < OpenIDRequest\n\n      # The association handle the response was signed with.\n      attr_accessor :assoc_handle\n\n      # The message with the signature which wants checking.\n      attr_accessor :signed\n\n      # An association handle the client is asking about the validity\n      # of. May be nil.\n      attr_accessor :invalidate_handle\n\n      attr_accessor :sig\n\n      # Construct me.\n      #\n      # These parameters are assigned directly as class attributes.\n      #\n      # Parameters:\n      # assoc_handle:: the association handle for this request\n      # signed:: The signed message\n      # invalidate_handle:: An association handle that the relying\n      #                     party is checking to see if it is invalid\n      def initialize(assoc_handle, signed, invalidate_handle=nil)\n        super()\n\n        @mode = \"check_authentication\"\n        @required_fields = [\"identity\", \"return_to\", \"response_nonce\"].freeze\n\n        @sig = nil\n        @assoc_handle = assoc_handle\n        @signed = signed\n        @invalidate_handle = invalidate_handle\n      end\n\n      # Construct me from an OpenID::Message.\n      def self.from_message(message, op_endpoint=UNUSED)\n        assoc_handle = message.get_arg(OPENID_NS, 'assoc_handle')\n        invalidate_handle = message.get_arg(OPENID_NS, 'invalidate_handle')\n\n        signed = message.copy()\n        # openid.mode is currently check_authentication because\n        # that's the mode of this request.  But the signature\n        # was made on something with a different openid.mode.\n        # http://article.gmane.org/gmane.comp.web.openid.general/537\n        if signed.has_key?(OPENID_NS, \"mode\")\n          signed.set_arg(OPENID_NS, \"mode\", \"id_res\")\n        end\n\n        obj = self.new(assoc_handle, signed, invalidate_handle)\n        obj.message = message\n        obj.sig = message.get_arg(OPENID_NS, 'sig')\n\n        if !obj.assoc_handle or\n            !obj.sig\n          msg = sprintf(\"%s request missing required parameter from message %s\",\n                        obj.mode, message)\n            raise ProtocolError.new(message, msg)\n        end\n\n        return obj\n      end\n\n      # Respond to this request.\n      #\n      # Given a Signatory, I can check the validity of the signature\n      # and the invalidate_handle.  I return a response with an\n      # is_valid (and, if appropriate invalidate_handle) field.\n      def answer(signatory)\n        is_valid = signatory.verify(@assoc_handle, @signed)\n        # Now invalidate that assoc_handle so it this checkAuth\n        # message cannot be replayed.\n        signatory.invalidate(@assoc_handle, true)\n        response = OpenIDResponse.new(self)\n        valid_str = is_valid ? \"true\" : \"false\"\n        response.fields.set_arg(OPENID_NS, 'is_valid', valid_str)\n\n        if @invalidate_handle\n          assoc = signatory.get_association(@invalidate_handle, false)\n          if !assoc\n            response.fields.set_arg(\n                    OPENID_NS, 'invalidate_handle', @invalidate_handle)\n          end\n        end\n\n        return response\n      end\n\n      def to_s\n        ih = nil\n\n        if @invalidate_handle\n          ih = sprintf(\" invalidate? %s\", @invalidate_handle)\n        else\n          ih = \"\"\n        end\n\n        s = sprintf(\"<%s handle: %s sig: %s: signed: %s%s>\",\n                    self.class, @assoc_handle,\n                    @sig, @signed, ih)\n        return s\n      end\n    end\n\n    class BaseServerSession\n      attr_reader :session_type\n\n      def initialize(session_type, allowed_assoc_types)\n        @session_type = session_type\n        @allowed_assoc_types = allowed_assoc_types.dup.freeze\n      end\n\n      def allowed_assoc_type?(typ)\n        @allowed_assoc_types.member?(typ)\n      end\n    end\n\n    # An object that knows how to handle association requests with\n    # no session type.\n    #\n    # See OpenID Specs, Section 8: Establishing Associations\n    # <http://openid.net/specs/openid-authentication-2_0-12.html#associations>\n    class PlainTextServerSession < BaseServerSession\n      # The session_type for this association session. There is no\n      # type defined for plain-text in the OpenID specification, so we\n      # use 'no-encryption'.\n      attr_reader :session_type\n\n      def initialize\n        super('no-encryption', ['HMAC-SHA1', 'HMAC-SHA256'])\n      end\n\n      def self.from_message(unused_request)\n        return self.new\n      end\n\n      def answer(secret)\n        return {'mac_key' => Util.to_base64(secret)}\n      end\n    end\n\n    # An object that knows how to handle association requests with the\n    # Diffie-Hellman session type.\n    #\n    # See OpenID Specs, Section 8: Establishing Associations\n    # <http://openid.net/specs/openid-authentication-2_0-12.html#associations>\n    class DiffieHellmanSHA1ServerSession < BaseServerSession\n\n      # The Diffie-Hellman algorithm values for this request\n      attr_accessor :dh\n\n      # The public key sent by the consumer in the associate request\n      attr_accessor :consumer_pubkey\n\n      # The session_type for this association session.\n      attr_reader :session_type\n\n      def initialize(dh, consumer_pubkey)\n        super('DH-SHA1', ['HMAC-SHA1'])\n\n        @hash_func = CryptUtil.method('sha1')\n        @dh = dh\n        @consumer_pubkey = consumer_pubkey\n      end\n\n      # Construct me from OpenID Message\n      #\n      # Raises ProtocolError when parameters required to establish the\n      # session are missing.\n      def self.from_message(message)\n        dh_modulus = message.get_arg(OPENID_NS, 'dh_modulus')\n        dh_gen = message.get_arg(OPENID_NS, 'dh_gen')\n        if ((!dh_modulus and dh_gen) or\n            (!dh_gen and dh_modulus))\n\n          if !dh_modulus\n            missing = 'modulus'\n          else\n            missing = 'generator'\n          end\n\n          raise ProtocolError.new(message,\n                  sprintf('If non-default modulus or generator is ' +\n                          'supplied, both must be supplied. Missing %s',\n                          missing))\n        end\n\n        if dh_modulus or dh_gen\n          dh_modulus = CryptUtil.base64_to_num(dh_modulus)\n          dh_gen = CryptUtil.base64_to_num(dh_gen)\n          dh = DiffieHellman.new(dh_modulus, dh_gen)\n        else\n          dh = DiffieHellman.from_defaults()\n        end\n\n        consumer_pubkey = message.get_arg(OPENID_NS, 'dh_consumer_public')\n        if !consumer_pubkey\n          raise ProtocolError.new(message,\n                  sprintf(\"Public key for DH-SHA1 session \" +\n                          \"not found in message %s\", message))\n        end\n\n        consumer_pubkey = CryptUtil.base64_to_num(consumer_pubkey)\n\n        return self.new(dh, consumer_pubkey)\n      end\n\n      def answer(secret)\n        mac_key = @dh.xor_secret(@hash_func,\n                                 @consumer_pubkey,\n                                 secret)\n        return {\n            'dh_server_public' => CryptUtil.num_to_base64(@dh.public),\n            'enc_mac_key' => Util.to_base64(mac_key),\n            }\n      end\n    end\n\n    class DiffieHellmanSHA256ServerSession < DiffieHellmanSHA1ServerSession\n      def initialize(*args)\n        super(*args)\n        @session_type = 'DH-SHA256'\n        @hash_func = CryptUtil.method('sha256')\n        @allowed_assoc_types = ['HMAC-SHA256'].freeze\n      end\n    end\n\n    # A request to establish an association.\n    #\n    # See OpenID Specs, Section 8: Establishing Associations\n    # <http://openid.net/specs/openid-authentication-2_0-12.html#associations>\n    class AssociateRequest < OpenIDRequest\n      # An object that knows how to handle association requests of a\n      # certain type.\n      attr_accessor :session\n\n      # The type of association. Supported values include HMAC-SHA256\n      # and HMAC-SHA1\n      attr_accessor :assoc_type\n\n      @@session_classes = {\n        'no-encryption' => PlainTextServerSession,\n        'DH-SHA1' => DiffieHellmanSHA1ServerSession,\n        'DH-SHA256' => DiffieHellmanSHA256ServerSession,\n      }\n\n      # Construct me.\n      #\n      # The session is assigned directly as a class attribute. See my\n      # class documentation for its description.\n      def initialize(session, assoc_type)\n        super()\n        @session = session\n        @assoc_type = assoc_type\n\n        @mode = \"associate\"\n      end\n\n      # Construct me from an OpenID Message.\n      def self.from_message(message, op_endpoint=UNUSED)\n        if message.is_openid1()\n          session_type = message.get_arg(OPENID_NS, 'session_type')\n          if session_type == 'no-encryption'\n            Util.log('Received OpenID 1 request with a no-encryption ' +\n                     'association session type. Continuing anyway.')\n          elsif !session_type\n            session_type = 'no-encryption'\n          end\n        else\n          session_type = message.get_arg(OPENID2_NS, 'session_type')\n          if !session_type\n            raise ProtocolError.new(message,\n                                    \"session_type missing from request\")\n          end\n        end\n\n        session_class = @@session_classes[session_type]\n\n        if !session_class\n          raise ProtocolError.new(message,\n                  sprintf(\"Unknown session type %s\", session_type))\n        end\n\n        begin\n          session = session_class.from_message(message)\n        rescue ArgumentError => why\n          # XXX\n          raise ProtocolError.new(message,\n                                  sprintf('Error parsing %s session: %s',\n                                          session_type, why))\n        end\n\n        assoc_type = message.get_arg(OPENID_NS, 'assoc_type', 'HMAC-SHA1')\n        if !session.allowed_assoc_type?(assoc_type)\n          msg = sprintf('Session type %s does not support association type %s',\n                        session_type, assoc_type)\n          raise ProtocolError.new(message, msg)\n        end\n\n        obj = self.new(session, assoc_type)\n        obj.message = message\n        return obj\n      end\n\n      # Respond to this request with an association.\n      #\n      # assoc:: The association to send back.\n      #\n      # Returns a response with the association information, encrypted\n      # to the consumer's public key if appropriate.\n      def answer(assoc)\n        response = OpenIDResponse.new(self)\n        response.fields.update_args(OPENID_NS, {\n            'expires_in' => sprintf('%d', assoc.expires_in()),\n            'assoc_type' => @assoc_type,\n            'assoc_handle' => assoc.handle,\n            })\n        response.fields.update_args(OPENID_NS,\n                                   @session.answer(assoc.secret))\n        unless (@session.session_type == 'no-encryption' and\n                @message.is_openid1)\n          response.fields.set_arg(\n              OPENID_NS, 'session_type', @session.session_type)\n        end\n\n        return response\n      end\n\n      # Respond to this request indicating that the association type\n      # or association session type is not supported.\n      def answer_unsupported(message, preferred_association_type=nil,\n                             preferred_session_type=nil)\n        if @message.is_openid1()\n          raise ProtocolError.new(@message)\n        end\n\n        response = OpenIDResponse.new(self)\n        response.fields.set_arg(OPENID_NS, 'error_code', 'unsupported-type')\n        response.fields.set_arg(OPENID_NS, 'error', message)\n\n        if preferred_association_type\n          response.fields.set_arg(\n              OPENID_NS, 'assoc_type', preferred_association_type)\n        end\n\n        if preferred_session_type\n          response.fields.set_arg(\n              OPENID_NS, 'session_type', preferred_session_type)\n        end\n\n        return response\n      end\n    end\n\n    # A request to confirm the identity of a user.\n    #\n    # This class handles requests for openid modes\n    # +checkid_immediate+ and +checkid_setup+ .\n    class CheckIDRequest < OpenIDRequest\n\n      # Provided in smart mode requests, a handle for a previously\n      # established association.  nil for dumb mode requests.\n      attr_accessor :assoc_handle\n\n      # Is this an immediate-mode request?\n      attr_accessor :immediate\n\n      # The URL to send the user agent back to to reply to this\n      # request.\n      attr_accessor :return_to\n\n      # The OP-local identifier being checked.\n      attr_accessor :identity\n\n      # The claimed identifier.  Not present in OpenID 1.x\n      # messages.\n      attr_accessor :claimed_id\n\n      # This URL identifies the party making the request, and the user\n      # will use that to make her decision about what answer she\n      # trusts them to have. Referred to as \"realm\" in OpenID 2.0.\n      attr_accessor :trust_root\n\n      # mode:: +checkid_immediate+ or +checkid_setup+\n      attr_accessor :mode\n\n      attr_accessor :op_endpoint\n\n      # These parameters are assigned directly as attributes,\n      # see the #CheckIDRequest class documentation for their\n      # descriptions.\n      #\n      # Raises #MalformedReturnURL when the +return_to+ URL is not\n      # a URL.\n      def initialize(identity, return_to, op_endpoint, trust_root=nil,\n                     immediate=false, assoc_handle=nil, claimed_id=nil)\n        @assoc_handle = assoc_handle\n        @identity = identity\n        @claimed_id = (claimed_id or identity)\n        @return_to = return_to\n        @trust_root = (trust_root or return_to)\n        @op_endpoint = op_endpoint\n        @message = nil\n\n        if immediate\n          @immediate = true\n          @mode = \"checkid_immediate\"\n        else\n          @immediate = false\n          @mode = \"checkid_setup\"\n        end\n\n        if @return_to and\n            !TrustRoot::TrustRoot.parse(@return_to)\n          raise MalformedReturnURL.new(nil, @return_to)\n        end\n\n        if !trust_root_valid()\n          raise UntrustedReturnURL.new(nil, @return_to, @trust_root)\n        end\n      end\n\n      # Construct me from an OpenID message.\n      #\n      # message:: An OpenID checkid_* request Message\n      #\n      # op_endpoint:: The endpoint URL of the server that this\n      #               message was sent to.\n      #\n      # Raises:\n      # ProtocolError:: When not all required parameters are present\n      #                 in the message.\n      #\n      # MalformedReturnURL:: When the +return_to+ URL is not a URL.\n      #\n      # UntrustedReturnURL:: When the +return_to+ URL is\n      #                      outside the +trust_root+.\n      def self.from_message(message, op_endpoint)\n        obj = self.allocate\n        obj.message = message\n        obj.op_endpoint = op_endpoint\n        mode = message.get_arg(OPENID_NS, 'mode')\n        if mode == \"checkid_immediate\"\n          obj.immediate = true\n          obj.mode = \"checkid_immediate\"\n        else\n          obj.immediate = false\n          obj.mode = \"checkid_setup\"\n        end\n\n        obj.return_to = message.get_arg(OPENID_NS, 'return_to')\n        if message.is_openid1 and !obj.return_to\n          msg = sprintf(\"Missing required field 'return_to' from %s\",\n                        message)\n          raise ProtocolError.new(message, msg)\n        end\n\n        obj.identity = message.get_arg(OPENID_NS, 'identity')\n        obj.claimed_id = message.get_arg(OPENID_NS, 'claimed_id')\n        if message.is_openid1()\n          if !obj.identity\n            s = \"OpenID 1 message did not contain openid.identity\"\n            raise ProtocolError.new(message, s)\n          end\n        else\n          if obj.identity and not obj.claimed_id\n            s = (\"OpenID 2.0 message contained openid.identity but not \" +\n                 \"claimed_id\")\n            raise ProtocolError.new(message, s)\n          elsif obj.claimed_id and not obj.identity\n            s = (\"OpenID 2.0 message contained openid.claimed_id but not \" +\n                 \"identity\")\n            raise ProtocolError.new(message, s)\n          end\n        end\n\n        # There's a case for making self.trust_root be a TrustRoot\n        # here.  But if TrustRoot isn't currently part of the \"public\"\n        # API, I'm not sure it's worth doing.\n        if message.is_openid1\n          trust_root_param = 'trust_root'\n        else\n          trust_root_param = 'realm'\n        end\n        trust_root = message.get_arg(OPENID_NS, trust_root_param)\n        trust_root = obj.return_to if (trust_root.nil? || trust_root.empty?)\n        obj.trust_root = trust_root\n\n        if !message.is_openid1 and !obj.return_to and !obj.trust_root\n          raise ProtocolError.new(message, \"openid.realm required when \" +\n                                  \"openid.return_to absent\")\n        end\n\n        obj.assoc_handle = message.get_arg(OPENID_NS, 'assoc_handle')\n\n        # Using TrustRoot.parse here is a bit misleading, as we're not\n        # parsing return_to as a trust root at all.  However, valid\n        # URLs are valid trust roots, so we can use this to get an\n        # idea if it is a valid URL.  Not all trust roots are valid\n        # return_to URLs, however (particularly ones with wildcards),\n        # so this is still a little sketchy.\n        if obj.return_to and \\\n          !TrustRoot::TrustRoot.parse(obj.return_to)\n          raise MalformedReturnURL.new(message, obj.return_to)\n        end\n\n        # I first thought that checking to see if the return_to is\n        # within the trust_root is premature here, a\n        # logic-not-decoding thing.  But it was argued that this is\n        # really part of data validation.  A request with an invalid\n        # trust_root/return_to is broken regardless of application,\n        # right?\n        if !obj.trust_root_valid()\n          raise UntrustedReturnURL.new(message, obj.return_to, obj.trust_root)\n        end\n\n        return obj\n      end\n\n      # Is the identifier to be selected by the IDP?\n      def id_select\n        # So IDPs don't have to import the constant\n        return @identity == IDENTIFIER_SELECT\n      end\n\n      # Is my return_to under my trust_root?\n      def trust_root_valid\n        if !@trust_root\n          return true\n        end\n\n        tr = TrustRoot::TrustRoot.parse(@trust_root)\n        if !tr\n          raise MalformedTrustRoot.new(@message, @trust_root)\n        end\n\n        if @return_to\n          return tr.validate_url(@return_to)\n        else\n          return true\n        end\n      end\n\n      # Does the relying party publish the return_to URL for this\n      # response under the realm? It is up to the provider to set a\n      # policy for what kinds of realms should be allowed. This\n      # return_to URL verification reduces vulnerability to\n      # data-theft attacks based on open proxies,\n      # corss-site-scripting, or open redirectors.\n      #\n      # This check should only be performed after making sure that\n      # the return_to URL matches the realm.\n      #\n      # Raises DiscoveryFailure if the realm\n      # URL does not support Yadis discovery (and so does not\n      # support the verification process).\n      #\n      # Returns true if the realm publishes a document with the\n      # return_to URL listed\n      def return_to_verified\n        return TrustRoot.verify_return_to(@trust_root, @return_to)\n      end\n\n      # Respond to this request.\n      #\n      # allow:: Allow this user to claim this identity, and allow the\n      #         consumer to have this information?\n      #\n      # server_url:: DEPRECATED.  Passing op_endpoint to the\n      #              #Server constructor makes this optional.\n      #\n      #              When an OpenID 1.x immediate mode request does\n      #              not succeed, it gets back a URL where the request\n      #              may be carried out in a not-so-immediate fashion.\n      #              Pass my URL in here (the fully qualified address\n      #              of this server's endpoint, i.e.\n      #              <tt>http://example.com/server</tt>), and I will\n      #              use it as a base for the URL for a new request.\n      #\n      #              Optional for requests where\n      #              #CheckIDRequest.immediate is false or +allow+ is\n      #              true.\n      #\n      # identity:: The OP-local identifier to answer with.  Only for use\n      #            when the relying party requested identifier selection.\n      #\n      # claimed_id:: The claimed identifier to answer with,\n      #              for use with identifier selection in the case where the\n      #              claimed identifier and the OP-local identifier differ,\n      #              i.e. when the claimed_id uses delegation.\n      #\n      #              If +identity+ is provided but this is not,\n      #              +claimed_id+ will default to the value of +identity+.\n      #              When answering requests that did not ask for identifier\n      #              selection, the response +claimed_id+ will default to\n      #              that of the request.\n      #\n      #              This parameter is new in OpenID 2.0.\n      #\n      # Returns an OpenIDResponse object containing a OpenID id_res message.\n      #\n      # Raises NoReturnToError if the return_to is missing.\n      #\n      # Version 2.0 deprecates +server_url+ and adds +claimed_id+.\n      def answer(allow, server_url=nil, identity=nil, claimed_id=nil)\n        if !@return_to\n          raise NoReturnToError\n        end\n\n        if !server_url\n          if @message.is_openid2 and !@op_endpoint\n            # In other words, that warning I raised in\n            # Server.__init__?  You should pay attention to it now.\n            raise RuntimeError, (\"#{self} should be constructed with \"\\\n                                 \"op_endpoint to respond to OpenID 2.0 \"\\\n                                 \"messages.\")\n          end\n\n          server_url = @op_endpoint\n        end\n\n        if allow\n          mode = 'id_res'\n        elsif @message.is_openid1\n          if @immediate\n            mode = 'id_res'\n          else\n            mode = 'cancel'\n          end\n        else\n          if @immediate\n            mode = 'setup_needed'\n          else\n            mode = 'cancel'\n          end\n        end\n\n        response = OpenIDResponse.new(self)\n\n        if claimed_id and @message.is_openid1\n          raise VersionError, (\"claimed_id is new in OpenID 2.0 and not \"\\\n                               \"available for #{@message.get_openid_namespace}\")\n        end\n\n        if identity and !claimed_id\n          claimed_id = identity\n        end\n\n        if allow\n          if @identity == IDENTIFIER_SELECT\n            if !identity\n              raise ArgumentError, (\"This request uses IdP-driven \"\\\n                                    \"identifier selection.You must supply \"\\\n                                    \"an identifier in the response.\")\n            end\n\n            response_identity = identity\n            response_claimed_id = claimed_id\n\n          elsif @identity\n            if identity and (@identity != identity)\n              raise ArgumentError, (\"Request was for identity #{@identity}, \"\\\n                                    \"cannot reply with identity #{identity}\")\n            end\n\n            response_identity = @identity\n            response_claimed_id = @claimed_id\n          else\n            if identity\n              raise ArgumentError, (\"This request specified no identity \"\\\n                                    \"and you supplied #{identity}\")\n            end\n            response_identity = nil\n          end\n\n          if @message.is_openid1 and !response_identity\n            raise ArgumentError, (\"Request was an OpenID 1 request, so \"\\\n                                  \"response must include an identifier.\")\n          end\n\n          response.fields.update_args(OPENID_NS, {\n                'mode' => mode,\n                'op_endpoint' => server_url,\n                'return_to' => @return_to,\n                'response_nonce' => Nonce.mk_nonce(),\n                })\n\n          if response_identity\n            response.fields.set_arg(OPENID_NS, 'identity', response_identity)\n            if @message.is_openid2\n              response.fields.set_arg(OPENID_NS,\n                                      'claimed_id', response_claimed_id)\n            end\n          end\n        else\n          response.fields.set_arg(OPENID_NS, 'mode', mode)\n          if @immediate\n            if @message.is_openid1 and !server_url\n              raise ArgumentError, (\"setup_url is required for allow=false \"\\\n                                    \"in OpenID 1.x immediate mode.\")\n            end\n\n            # Make a new request just like me, but with\n            # immediate=false.\n            setup_request = self.class.new(@identity, @return_to,\n                                           @op_endpoint, @trust_root, false,\n                                           @assoc_handle, @claimed_id)\n            setup_request.message = Message.new(@message.get_openid_namespace)\n            setup_url = setup_request.encode_to_url(server_url)\n            response.fields.set_arg(OPENID_NS, 'user_setup_url', setup_url)\n          end\n        end\n\n        return response\n      end\n\n      def encode_to_url(server_url)\n        # Encode this request as a URL to GET.\n        #\n        # server_url:: The URL of the OpenID server to make this\n        #              request of.\n        if !@return_to\n          raise NoReturnToError\n        end\n\n        # Imported from the alternate reality where these classes are\n        # used in both the client and server code, so Requests are\n        # Encodable too.  That's right, code imported from alternate\n        # realities all for the love of you, id_res/user_setup_url.\n        q = {'mode' => @mode,\n             'identity' => @identity,\n             'claimed_id' => @claimed_id,\n             'return_to' => @return_to}\n\n        if @trust_root\n          if @message.is_openid1\n            q['trust_root'] = @trust_root\n          else\n            q['realm'] = @trust_root\n          end\n        end\n\n        if @assoc_handle\n          q['assoc_handle'] = @assoc_handle\n        end\n\n        response = Message.new(@message.get_openid_namespace)\n        response.update_args(@message.get_openid_namespace, q)\n        return response.to_url(server_url)\n      end\n\n      def cancel_url\n        # Get the URL to cancel this request.\n        #\n        # Useful for creating a \"Cancel\" button on a web form so that\n        # operation can be carried out directly without another trip\n        # through the server.\n        #\n        # (Except you may want to make another trip through the\n        # server so that it knows that the user did make a decision.)\n        #\n        # Returns a URL as a string.\n        if !@return_to\n          raise NoReturnToError\n        end\n\n        if @immediate\n          raise ArgumentError.new(\"Cancel is not an appropriate response to \" +\n                                  \"immediate mode requests.\")\n        end\n\n        response = Message.new(@message.get_openid_namespace)\n        response.set_arg(OPENID_NS, 'mode', 'cancel')\n        return response.to_url(@return_to)\n      end\n\n      def to_s\n        return sprintf('<%s id:%s im:%s tr:%s ah:%s>', self.class,\n                       @identity,\n                       @immediate,\n                       @trust_root,\n                       @assoc_handle)\n      end\n    end\n\n    # I am a response to an OpenID request.\n    #\n    # Attributes:\n    # signed:: A list of the names of the fields which should be signed.\n    #\n    # Implementer's note: In a more symmetric client/server\n    # implementation, there would be more types of #OpenIDResponse\n    # object and they would have validated attributes according to\n    # the type of response.  But as it is, Response objects in a\n    # server are basically write-only, their only job is to go out\n    # over the wire, so this is just a loose wrapper around\n    # #OpenIDResponse.fields.\n    class OpenIDResponse\n      # The #OpenIDRequest I respond to.\n      attr_accessor :request\n\n      # An #OpenID::Message with the data to be returned.\n      # Keys are parameter names with no\n      # leading openid. e.g. identity and mac_key\n      # never openid.identity.\n      attr_accessor :fields\n\n      def initialize(request)\n        # Make a response to an OpenIDRequest.\n        @request = request\n        @fields = Message.new(request.namespace)\n      end\n\n      def to_s\n        return sprintf(\"%s for %s: %s\",\n                       self.class,\n                       @request.class,\n                       @fields)\n      end\n\n      # form_tag_attrs is a hash of attributes to be added to the form\n      # tag. 'accept-charset' and 'enctype' have defaults that can be\n      # overridden. If a value is supplied for 'action' or 'method',\n      # it will be replaced.       \n      # Returns the form markup for this response.\n      def to_form_markup(form_tag_attrs=nil)\n        return @fields.to_form_markup(@request.return_to, form_tag_attrs)\n      end\n\n      # Wraps the form tag from to_form_markup in a complete HTML document\n      # that uses javascript to autosubmit the form.\n      def to_html(form_tag_attrs=nil)\n        return Util.auto_submit_html(to_form_markup(form_tag_attrs))\n      end\n\n      def render_as_form\n        # Returns true if this response's encoding is\n        # ENCODE_HTML_FORM.  Convenience method for server authors.\n        return self.which_encoding == ENCODE_HTML_FORM\n      end\n\n      def needs_signing\n        # Does this response require signing?\n        return @fields.get_arg(OPENID_NS, 'mode') == 'id_res'\n      end\n\n      # implements IEncodable\n\n      def which_encoding\n        # How should I be encoded?\n        # returns one of ENCODE_URL or ENCODE_KVFORM.\n        if BROWSER_REQUEST_MODES.member?(@request.mode)\n          if @fields.is_openid2 and\n              encode_to_url.length > OPENID1_URL_LIMIT\n            return ENCODE_HTML_FORM\n          else\n            return ENCODE_URL\n          end\n        else\n          return ENCODE_KVFORM\n        end\n      end\n\n      def encode_to_url\n        # Encode a response as a URL for the user agent to GET.\n        # You will generally use this URL with a HTTP redirect.\n        return @fields.to_url(@request.return_to)\n      end\n\n      def add_extension(extension_response)\n        # Add an extension response to this response message.\n        #\n        # extension_response:: An object that implements the\n        #     #OpenID::Extension interface for adding arguments to an OpenID\n        #     message.\n        extension_response.to_message(@fields)\n      end\n\n      def encode_to_kvform\n        # Encode a response in key-value colon/newline format.\n        #\n        # This is a machine-readable format used to respond to\n        # messages which came directly from the consumer and not\n        # through the user agent.\n        #\n        # see: OpenID Specs,\n        #    <a href=\"http://openid.net/specs.bml#keyvalue\">Key-Value Colon/Newline format</a>\n        return @fields.to_kvform\n      end\n\n      def copy\n        return Marshal.load(Marshal.dump(self))\n      end\n    end\n\n    # I am a response to an OpenID request in terms a web server\n    # understands.\n    #\n    # I generally come from an #Encoder, either directly or from\n    # #Server.encodeResponse.\n    class WebResponse\n\n      # The HTTP code of this response as an integer.\n      attr_accessor :code\n\n      # #Hash of headers to include in this response.\n      attr_accessor :headers\n\n      # The body of this response.\n      attr_accessor :body\n\n      def initialize(code=HTTP_OK, headers=nil, body=\"\")\n        # Construct me.\n        #\n        # These parameters are assigned directly as class attributes,\n        # see my class documentation for their\n        # descriptions.\n        @code = code\n        if headers\n          @headers = headers\n        else\n          @headers = {}\n        end\n        @body = body\n      end\n    end\n\n    # I sign things.\n    #\n    # I also check signatures.\n    #\n    # All my state is encapsulated in a store, which means I'm not\n    # generally pickleable but I am easy to reconstruct.\n    class Signatory\n      # The number of seconds a secret remains valid. Defaults to 14 days.\n      attr_accessor :secret_lifetime\n\n      # keys have a bogus server URL in them because the filestore\n      # really does expect that key to be a URL.  This seems a little\n      # silly for the server store, since I expect there to be only\n      # one server URL.\n      @@_normal_key = 'http://localhost/|normal'\n      @@_dumb_key = 'http://localhost/|dumb'\n\n      def self._normal_key\n        @@_normal_key\n      end\n\n      def self._dumb_key\n        @@_dumb_key\n      end\n\n      attr_accessor :store\n\n      # Create a new Signatory. store is The back-end where my\n      # associations are stored.\n      def initialize(store)\n        Util.assert(store)\n        @store = store\n        @secret_lifetime = 14 * 24 * 60 * 60\n      end\n\n      # Verify that the signature for some data is valid.\n      def verify(assoc_handle, message)\n        assoc = get_association(assoc_handle, true)\n        if !assoc\n          Util.log(sprintf(\"failed to get assoc with handle %s to verify \" +\n                           \"message %s\", assoc_handle, message))\n          return false\n        end\n\n        begin\n          valid = assoc.check_message_signature(message)\n        rescue StandardError => ex\n          Util.log(sprintf(\"Error in verifying %s with %s: %s\",\n                           message, assoc, ex))\n          return false\n        end\n\n        return valid\n      end\n\n      # Sign a response.\n      #\n      # I take an OpenIDResponse, create a signature for everything in\n      # its signed list, and return a new copy of the response object\n      # with that signature included.\n      def sign(response)\n        signed_response = response.copy\n        assoc_handle = response.request.assoc_handle\n        if assoc_handle\n          # normal mode disabling expiration check because even if the\n          # association is expired, we still need to know some\n          # properties of the association so that we may preserve\n          # those properties when creating the fallback association.\n          assoc = get_association(assoc_handle, false, false)\n\n          if !assoc or assoc.expires_in <= 0\n            # fall back to dumb mode\n            signed_response.fields.set_arg(\n                  OPENID_NS, 'invalidate_handle', assoc_handle)\n            assoc_type = assoc ? assoc.assoc_type : 'HMAC-SHA1'\n            if assoc and assoc.expires_in <= 0\n              # now do the clean-up that the disabled checkExpiration\n              # code didn't get to do.\n              invalidate(assoc_handle, false)\n            end\n            assoc = create_association(true, assoc_type)\n          end\n        else\n          # dumb mode.\n          assoc = create_association(true)\n        end\n\n        begin\n          signed_response.fields = assoc.sign_message(signed_response.fields)\n        rescue KVFormError => err\n          raise EncodingError, err\n        end\n        return signed_response\n      end\n\n      # Make a new association.\n      def create_association(dumb=true, assoc_type='HMAC-SHA1')\n        secret = CryptUtil.random_string(OpenID.get_secret_size(assoc_type))\n        uniq = Util.to_base64(CryptUtil.random_string(4))\n        handle = sprintf('{%s}{%x}{%s}', assoc_type, Time.now.to_i, uniq)\n\n        assoc = Association.from_expires_in(\n            secret_lifetime, handle, secret, assoc_type)\n\n        if dumb\n          key = @@_dumb_key\n        else\n          key = @@_normal_key\n        end\n\n        @store.store_association(key, assoc)\n        return assoc\n      end\n\n      # Get the association with the specified handle.\n      def get_association(assoc_handle, dumb, checkExpiration=true)\n        # Hmm.  We've created an interface that deals almost entirely\n        # with assoc_handles.  The only place outside the Signatory\n        # that uses this (and thus the only place that ever sees\n        # Association objects) is when creating a response to an\n        # association request, as it must have the association's\n        # secret.\n\n        if !assoc_handle\n          raise ArgumentError.new(\"assoc_handle must not be None\")\n        end\n\n        if dumb\n          key = @@_dumb_key\n        else\n          key = @@_normal_key\n        end\n\n        assoc = @store.get_association(key, assoc_handle)\n        if assoc and assoc.expires_in <= 0\n          Util.log(sprintf(\"requested %sdumb key %s is expired (by %s seconds)\",\n                           (!dumb) ? 'not-' : '',\n                           assoc_handle, assoc.expires_in))\n          if checkExpiration\n            @store.remove_association(key, assoc_handle)\n            assoc = nil\n          end\n        end\n\n        return assoc\n      end\n\n      # Invalidates the association with the given handle.\n      def invalidate(assoc_handle, dumb)\n        if dumb\n          key = @@_dumb_key\n        else\n          key = @@_normal_key\n        end\n\n        @store.remove_association(key, assoc_handle)\n      end\n    end\n\n    # I encode responses in to WebResponses.\n    #\n    # If you don't like WebResponses, you can do\n    # your own handling of OpenIDResponses with\n    # OpenIDResponse.whichEncoding,\n    # OpenIDResponse.encodeToURL, and\n    # OpenIDResponse.encodeToKVForm.\n    class Encoder\n      @@responseFactory = WebResponse\n\n      # Encode a response to a WebResponse.\n      #\n      # Raises EncodingError when I can't figure out how to encode\n      # this message.\n      def encode(response)\n        encode_as = response.which_encoding()\n        if encode_as == ENCODE_KVFORM\n          wr = @@responseFactory.new(HTTP_OK, nil,\n                                     response.encode_to_kvform())\n          if response.is_a?(Exception)\n            wr.code = HTTP_ERROR\n          end\n        elsif encode_as == ENCODE_URL\n          location = response.encode_to_url()\n          wr = @@responseFactory.new(HTTP_REDIRECT,\n                                     {'location' => location})\n        elsif encode_as == ENCODE_HTML_FORM\n          wr = @@responseFactory.new(HTTP_OK, nil,\n                                     response.to_form_markup())\n        else\n          # Can't encode this to a protocol message.  You should\n          # probably render it to HTML and show it to the user.\n          raise EncodingError.new(response)\n        end\n\n        return wr\n      end\n    end\n\n    # I encode responses in to WebResponses, signing\n    # them when required.\n    class SigningEncoder < Encoder\n\n      attr_accessor :signatory\n\n      # Create a SigningEncoder given a Signatory\n      def initialize(signatory)\n        @signatory = signatory\n      end\n\n      # Encode a response to a WebResponse, signing it first if\n      # appropriate.\n      #\n      # Raises EncodingError when I can't figure out how to encode this\n      # message.\n      #\n      # Raises AlreadySigned when this response is already signed.\n      def encode(response)\n        # the is_a? is a bit of a kludge... it means there isn't\n        # really an adapter to make the interfaces quite match.\n        if !response.is_a?(Exception) and response.needs_signing()\n          if !@signatory\n            raise ArgumentError.new(\n              sprintf(\"Must have a store to sign this request: %s\",\n                      response), response)\n          end\n\n          if response.fields.has_key?(OPENID_NS, 'sig')\n            raise AlreadySigned.new(response)\n          end\n\n          response = @signatory.sign(response)\n        end\n\n        return super(response)\n      end\n    end\n\n    # I decode an incoming web request in to a OpenIDRequest.\n    class Decoder\n\n      @@handlers = {\n        'checkid_setup' => CheckIDRequest.method('from_message'),\n        'checkid_immediate' => CheckIDRequest.method('from_message'),\n        'check_authentication' => CheckAuthRequest.method('from_message'),\n        'associate' => AssociateRequest.method('from_message'),\n        }\n\n      attr_accessor :server\n\n      # Construct a Decoder. The server is necessary because some\n      # replies reference their server.\n      def initialize(server)\n        @server = server\n      end\n\n      # I transform query parameters into an OpenIDRequest.\n      #\n      # If the query does not seem to be an OpenID request at all, I\n      # return nil.\n      #\n      # Raises ProtocolError when the query does not seem to be a valid\n      # OpenID request.\n      def decode(query)\n        if query.nil? or query.length == 0\n          return nil\n        end\n\n        begin\n          message = Message.from_post_args(query)\n        rescue InvalidOpenIDNamespace => e\n          query = query.dup\n          query['openid.ns'] = OPENID2_NS\n          message = Message.from_post_args(query)\n          raise ProtocolError.new(message, e.to_s)\n        end\n\n        mode = message.get_arg(OPENID_NS, 'mode')\n        if !mode\n          msg = sprintf(\"No mode value in message %s\", message)\n          raise ProtocolError.new(message, msg)\n        end\n\n        handler = @@handlers.fetch(mode, self.method('default_decoder'))\n        return handler.call(message, @server.op_endpoint)\n      end\n\n      # Called to decode queries when no handler for that mode is\n      # found.\n      #\n      # This implementation always raises ProtocolError.\n      def default_decoder(message, server)\n        mode = message.get_arg(OPENID_NS, 'mode')\n        msg = sprintf(\"Unrecognized OpenID mode %s\", mode)\n        raise ProtocolError.new(message, msg)\n      end\n    end\n\n    # I handle requests for an OpenID server.\n    #\n    # Some types of requests (those which are not checkid requests)\n    # may be handed to my handleRequest method, and I will take care\n    # of it and return a response.\n    #\n    # For your convenience, I also provide an interface to\n    # Decoder.decode and SigningEncoder.encode through my methods\n    # decodeRequest and encodeResponse.\n    #\n    # All my state is encapsulated in an store, which means I'm not\n    # generally pickleable but I am easy to reconstruct.\n    class Server\n      @@signatoryClass = Signatory\n      @@encoderClass = SigningEncoder\n      @@decoderClass = Decoder\n\n      # The back-end where my associations and nonces are stored.\n      attr_accessor :store\n\n      # I'm using this for associate requests and to sign things.\n      attr_accessor :signatory\n\n      # I'm using this to encode things.\n      attr_accessor :encoder\n\n      # I'm using this to decode things.\n      attr_accessor :decoder\n\n      # I use this instance of OpenID::AssociationNegotiator to\n      # determine which kinds of associations I can make and how.\n      attr_accessor :negotiator\n\n      # My URL.\n      attr_accessor :op_endpoint\n\n      # op_endpoint is new in library version 2.0.\n      def initialize(store, op_endpoint)\n        @store = store\n        @signatory = @@signatoryClass.new(@store)\n        @encoder = @@encoderClass.new(@signatory)\n        @decoder = @@decoderClass.new(self)\n        @negotiator = DefaultNegotiator.copy()\n        @op_endpoint = op_endpoint\n      end\n\n      # Handle a request.\n      #\n      # Give me a request, I will give you a response.  Unless it's a\n      # type of request I cannot handle myself, in which case I will\n      # raise RuntimeError.  In that case, you can handle it yourself,\n      # or add a method to me for handling that request type.\n      def handle_request(request)\n        begin\n          handler = self.method('openid_' + request.mode)\n        rescue NameError\n          raise RuntimeError.new(\n            sprintf(\"%s has no handler for a request of mode %s.\",\n                    self, request.mode))\n        end\n\n        return handler.call(request)\n      end\n\n      # Handle and respond to check_authentication requests.\n      def openid_check_authentication(request)\n        return request.answer(@signatory)\n      end\n\n      # Handle and respond to associate requests.\n      def openid_associate(request)\n        assoc_type = request.assoc_type\n        session_type = request.session.session_type\n        if @negotiator.allowed?(assoc_type, session_type)\n          assoc = @signatory.create_association(false,\n                                                assoc_type)\n          return request.answer(assoc)\n        else\n          message = sprintf('Association type %s is not supported with ' +\n                            'session type %s', assoc_type, session_type)\n          preferred_assoc_type, preferred_session_type = @negotiator.get_allowed_type()\n          return request.answer_unsupported(message,\n                                            preferred_assoc_type,\n                                            preferred_session_type)\n        end\n      end\n\n      # Transform query parameters into an OpenIDRequest.\n      # query should contain the query parameters as a Hash with\n      # each key mapping to one value.\n      #\n      # If the query does not seem to be an OpenID request at all, I\n      # return nil.\n      def decode_request(query)\n        return @decoder.decode(query)\n      end\n\n      # Encode a response to a WebResponse, signing it first if\n      # appropriate.\n      #\n      # Raises EncodingError when I can't figure out how to encode this\n      # message.\n      #\n      # Raises AlreadySigned When this response is already signed.\n      def encode_response(response)\n        return @encoder.encode(response)\n      end\n    end\n\n    # A message did not conform to the OpenID protocol.\n    class ProtocolError < Exception\n      # The query that is failing to be a valid OpenID request.\n      attr_accessor :openid_message\n      attr_accessor :reference\n      attr_accessor :contact\n\n      # text:: A message about the encountered error.\n      def initialize(message, text=nil, reference=nil, contact=nil)\n        @openid_message = message\n        @reference = reference\n        @contact = contact\n        Util.assert(!message.is_a?(String))\n        super(text)\n      end\n\n      # Get the return_to argument from the request, if any.\n      def get_return_to\n        if @openid_message.nil?\n          return nil\n        else\n          return @openid_message.get_arg(OPENID_NS, 'return_to')\n        end\n      end\n\n      # Did this request have a return_to parameter?\n      def has_return_to\n        return !get_return_to.nil?\n      end\n\n      # Generate a Message object for sending to the relying party,\n      # after encoding.\n      def to_message\n        namespace = @openid_message.get_openid_namespace()\n        reply = Message.new(namespace)\n        reply.set_arg(OPENID_NS, 'mode', 'error')\n        reply.set_arg(OPENID_NS, 'error', self.to_s)\n\n        if @contact\n          reply.set_arg(OPENID_NS, 'contact', @contact.to_s)\n        end\n\n        if @reference\n          reply.set_arg(OPENID_NS, 'reference', @reference.to_s)\n        end\n\n        return reply\n      end\n\n      # implements IEncodable\n\n      def encode_to_url\n        return to_message().to_url(get_return_to())\n      end\n\n      def encode_to_kvform\n        return to_message().to_kvform()\n      end\n\n      def to_form_markup\n        return to_message().to_form_markup(get_return_to())\n      end\n\n      def to_html\n        return Util.auto_submit_html(to_form_markup)\n      end\n\n      # How should I be encoded?\n      #\n      # Returns one of ENCODE_URL, ENCODE_KVFORM, or None.  If None,\n      # I cannot be encoded as a protocol message and should be\n      # displayed to the user.\n      def which_encoding\n        if has_return_to()\n          if @openid_message.is_openid2 and\n              encode_to_url().length > OPENID1_URL_LIMIT\n            return ENCODE_HTML_FORM\n          else\n            return ENCODE_URL\n          end\n        end\n\n        if @openid_message.nil?\n          return nil\n        end\n\n        mode = @openid_message.get_arg(OPENID_NS, 'mode')\n        if mode\n          if !BROWSER_REQUEST_MODES.member?(mode)\n            return ENCODE_KVFORM\n          end\n        end\n\n        # If your request was so broken that you didn't manage to\n        # include an openid.mode, I'm not going to worry too much\n        # about returning you something you can't parse.\n        return nil\n      end\n    end\n\n    # Raised when an operation was attempted that is not compatible\n    # with the protocol version being used.\n    class VersionError < Exception\n    end\n\n    # Raised when a response to a request cannot be generated\n    # because the request contains no return_to URL.\n    class NoReturnToError < Exception\n    end\n\n    # Could not encode this as a protocol message.\n    #\n    # You should probably render it and show it to the user.\n    class EncodingError < Exception\n      # The response that failed to encode.\n      attr_reader :response\n\n      def initialize(response)\n        super(response)\n        @response = response\n      end\n    end\n\n    # This response is already signed.\n    class AlreadySigned < EncodingError\n    end\n\n    # A return_to is outside the trust_root.\n    class UntrustedReturnURL < ProtocolError\n      attr_reader :return_to, :trust_root\n\n      def initialize(message, return_to, trust_root)\n        super(message)\n        @return_to = return_to\n        @trust_root = trust_root\n      end\n\n      def to_s\n        return sprintf(\"return_to %s not under trust_root %s\",\n                       @return_to,\n                       @trust_root)\n      end\n    end\n\n    # The return_to URL doesn't look like a valid URL.\n    class MalformedReturnURL < ProtocolError\n      attr_reader :return_to\n\n      def initialize(openid_message, return_to)\n        @return_to = return_to\n        super(openid_message)\n      end\n    end\n\n    # The trust root is not well-formed.\n    class MalformedTrustRoot < ProtocolError\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/store/filesystem.rb",
    "content": "require 'fileutils'\nrequire 'pathname'\nrequire 'tempfile'\n\nrequire 'openid/util'\nrequire 'openid/store/interface'\nrequire 'openid/association'\n\nmodule OpenID\n  module Store\n    class Filesystem < Interface\n      @@FILENAME_ALLOWED = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-\".split(\"\")\n\n      # Create a Filesystem store instance, putting all data in +directory+.\n      def initialize(directory)\n        @nonce_dir = File.join(directory, 'nonces')\n        @association_dir = File.join(directory, 'associations')\n        @temp_dir = File.join(directory, 'temp')\n\n        self.ensure_dir(@nonce_dir)\n        self.ensure_dir(@association_dir)\n        self.ensure_dir(@temp_dir)\n      end\n\n      # Create a unique filename for a given server url and handle. The\n      # filename that is returned will contain the domain name from the\n      # server URL for ease of human inspection of the data dir.\n      def get_association_filename(server_url, handle)\n        unless server_url.index('://')\n          raise ArgumentError, \"Bad server URL: #{server_url}\"\n        end\n\n        proto, rest = server_url.split('://', 2)\n        domain = filename_escape(rest.split('/',2)[0])\n        url_hash = safe64(server_url)\n        if handle\n          handle_hash = safe64(handle)\n        else\n          handle_hash = ''\n        end\n        filename = [proto,domain,url_hash,handle_hash].join('-')\n        File.join(@association_dir, filename)\n      end\n\n      # Store an association in the assoc directory\n      def store_association(server_url, association)\n        assoc_s = association.serialize\n        filename = get_association_filename(server_url, association.handle)\n        f, tmp = mktemp\n\n        begin\n          begin\n            f.write(assoc_s)\n            f.fsync\n          ensure\n            f.close\n          end\n\n          begin\n            File.rename(tmp, filename)\n          rescue Errno::EEXIST\n\n            begin\n              File.unlink(filename)\n            rescue Errno::ENOENT\n              # do nothing\n            end\n\n            File.rename(tmp, filename)\n          end\n\n        rescue\n          self.remove_if_present(tmp)\n          raise\n        end\n      end\n\n      # Retrieve an association\n      def get_association(server_url, handle=nil)\n        # the filename with empty handle is the prefix for the associations\n        # for a given server url\n        filename = get_association_filename(server_url, handle)\n        if handle\n          return _get_association(filename)\n        end\n        assoc_filenames = Dir.glob(filename.to_s + '*')\n\n        assocs = assoc_filenames.collect do |f|\n          _get_association(f)\n        end\n\n        assocs = assocs.find_all { |a| not a.nil? }\n        assocs = assocs.sort_by { |a| a.issued }\n\n        return nil if assocs.empty?\n        return assocs[-1]\n      end\n\n      def _get_association(filename)\n        begin\n          assoc_file = File.open(filename, \"r\")\n        rescue Errno::ENOENT\n          return nil\n        else\n          begin\n            assoc_s = assoc_file.read\n          ensure\n            assoc_file.close\n          end\n\n          begin\n            association = Association.deserialize(assoc_s)\n          rescue\n            self.remove_if_present(filename)\n            return nil\n          end\n\n          # clean up expired associations\n          if association.expires_in == 0\n            self.remove_if_present(filename)\n            return nil\n          else\n            return association\n          end\n        end\n      end\n\n      # Remove an association if it exists, otherwise do nothing.\n      def remove_association(server_url, handle)\n        assoc = get_association(server_url, handle)\n\n        if assoc.nil?\n          return false\n        else\n          filename = get_association_filename(server_url, handle)\n          return self.remove_if_present(filename)\n        end\n      end\n\n      # Return whether the nonce is valid\n      def use_nonce(server_url, timestamp, salt)\n        return false if (timestamp - Time.now.to_i).abs > Nonce.skew\n\n        if server_url and !server_url.empty?\n          proto, rest = server_url.split('://',2)\n        else\n          proto, rest = '',''\n        end\n        raise \"Bad server URL\" unless proto && rest\n\n        domain = filename_escape(rest.split('/',2)[0])\n        url_hash = safe64(server_url)\n        salt_hash = safe64(salt)\n\n        nonce_fn = '%08x-%s-%s-%s-%s'%[timestamp, proto, domain, url_hash, salt_hash]\n\n        filename = File.join(@nonce_dir, nonce_fn)\n\n        begin\n          fd = File.new(filename, File::CREAT | File::EXCL | File::WRONLY, 0200)\n          fd.close\n          return true\n        rescue Errno::EEXIST\n          return false\n        end\n      end\n\n      # Remove expired entries from the database. This is potentially expensive,\n      # so only run when it is acceptable to take time.\n      def cleanup\n        cleanup_associations\n        cleanup_nonces\n      end\n\n      def cleanup_associations\n        association_filenames = Dir[File.join(@association_dir, \"*\")]\n        count = 0\n        association_filenames.each do |af|\n          begin\n            f = File.open(af, 'r')\n          rescue Errno::ENOENT\n            next\n          else\n            begin\n              assoc_s = f.read\n            ensure\n              f.close\n            end\n            begin\n              association = OpenID::Association.deserialize(assoc_s)\n            rescue StandardError\n              self.remove_if_present(af)\n              next\n            else\n              if association.expires_in == 0\n                self.remove_if_present(af)\n                count += 1\n              end\n            end\n          end\n        end\n        return count\n      end\n\n      def cleanup_nonces\n        nonces = Dir[File.join(@nonce_dir, \"*\")]\n        now = Time.now.to_i\n\n        count = 0\n        nonces.each do |filename|\n          nonce = filename.split('/')[-1]\n          timestamp = nonce.split('-', 2)[0].to_i(16)\n          nonce_age = (timestamp - now).abs\n          if nonce_age > Nonce.skew\n            self.remove_if_present(filename)\n            count += 1\n          end\n        end\n        return count\n      end\n\n      protected\n\n      # Create a temporary file and return the File object and filename.\n      def mktemp\n        f = Tempfile.new('tmp', @temp_dir)\n        [f, f.path]\n      end\n\n      # create a safe filename from a url\n      def filename_escape(s)\n        s = '' if s.nil?\n        s.each_char.flat_map {|c|\n          @@FILENAME_ALLOWED.include?(c) ? c : c.bytes.map {|b|\n            \"_%02X\" % b\n          }\n        }.join\n      end\n\n      def safe64(s)\n        s = OpenID::CryptUtil.sha1(s)\n        s = OpenID::Util.to_base64(s)\n        s.gsub!('+', '_')\n        s.gsub!('/', '.')\n        s.gsub!('=', '')\n        return s\n      end\n\n      # remove file if present in filesystem\n      def remove_if_present(filename)\n        begin\n          File.unlink(filename)\n        rescue Errno::ENOENT\n          return false\n        end\n        return true\n      end\n\n      # ensure that a path exists\n      def ensure_dir(dir_name)\n        FileUtils::mkdir_p(dir_name)\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/openid/store/interface.rb",
    "content": "require 'openid/util'\n\nmodule OpenID\n\n  # Stores for Associations and nonces. Used by both the Consumer and\n  # the Server. If you have a database abstraction layer or other\n  # state storage in your application or framework already, you can\n  # implement the store interface.\n  module Store\n    # Abstract Store\n    # Changes in 2.0:\n    # * removed store_nonce, get_auth_key, is_dumb\n    # * changed use_nonce to support one-way nonces\n    # * added cleanup_nonces, cleanup_associations, cleanup\n    class Interface < Object\n\n      # Put a Association object into storage.\n      # When implementing a store, don't assume that there are any limitations\n      # on the character set of the server_url.  In particular, expect to see\n      # unescaped non-url-safe characters in the server_url field.\n      def store_association(server_url, association)\n        raise NotImplementedError\n      end\n\n      # Returns a Association object from storage that matches\n      # the server_url.  Returns nil if no such association is found or if\n      # the one matching association is expired. (Is allowed to GC expired\n      # associations when found.)\n      def get_association(server_url, handle=nil)\n        raise NotImplementedError\n      end\n\n      # If there is a matching association, remove it from the store and\n      # return true, otherwise return false.\n      def remove_association(server_url, handle)\n        raise NotImplementedError\n      end\n\n      # Return true if the nonce has not been used before, and store it\n      # for a while to make sure someone doesn't try to use the same value\n      # again.  Return false if the nonce has already been used or if the\n      # timestamp is not current.\n      # You can use OpenID::Store::Nonce::SKEW for your timestamp window.\n      # server_url: URL of the server from which the nonce originated\n      # timestamp: time the nonce was created in seconds since unix epoch\n      # salt: A random string that makes two nonces issued by a server in\n      #       the same second unique\n      def use_nonce(server_url, timestamp, salt)\n        raise NotImplementedError\n      end\n\n      # Remove expired nonces from the store\n      # Discards any nonce that is old enough that it wouldn't pass use_nonce\n      # Not called during normal library operation, this method is for store\n      # admins to keep their storage from filling up with expired data\n      def cleanup_nonces\n        raise NotImplementedError\n      end\n\n      # Remove expired associations from the store\n      # Not called during normal library operation, this method is for store\n      # admins to keep their storage from filling up with expired data\n      def cleanup_associations\n        raise NotImplementedError\n      end\n\n      # Remove expired nonces and associations from the store\n      # Not called during normal library operation, this method is for store\n      # admins to keep their storage from filling up with expired data\n      def cleanup\n        return cleanup_nonces, cleanup_associations\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/store/memcache.rb",
    "content": "require 'openid/util'\nrequire 'openid/store/interface'\nrequire 'openid/store/nonce'\nrequire 'time'\n\nmodule OpenID\n  module Store\n    class Memcache < Interface\n      attr_accessor :key_prefix\n\n      def initialize(cache_client, key_prefix='openid-store:')\n        @cache_client = cache_client\n        self.key_prefix = key_prefix\n      end\n\n      # Put a Association object into storage.\n      # When implementing a store, don't assume that there are any limitations\n      # on the character set of the server_url.  In particular, expect to see\n      # unescaped non-url-safe characters in the server_url field.\n      def store_association(server_url, association)\n        serialized = serialize(association)\n        [nil, association.handle].each do |handle|\n          key = assoc_key(server_url, handle)\n          @cache_client.set(key, serialized, expiry(association.lifetime))\n        end\n      end\n\n      # Returns a Association object from storage that matches\n      # the server_url.  Returns nil if no such association is found or if\n      # the one matching association is expired. (Is allowed to GC expired\n      # associations when found.)\n      def get_association(server_url, handle=nil)\n        serialized = @cache_client.get(assoc_key(server_url, handle))\n        if serialized\n          return deserialize(serialized)\n        else\n          return nil\n        end\n      end\n\n      # If there is a matching association, remove it from the store and\n      # return true, otherwise return false.\n      def remove_association(server_url, handle)\n        deleted = delete(assoc_key(server_url, handle))\n        server_assoc = get_association(server_url)\n        if server_assoc && server_assoc.handle == handle\n          deleted = delete(assoc_key(server_url)) | deleted\n        end\n        return deleted\n      end\n\n      # Return true if the nonce has not been used before, and store it\n      # for a while to make sure someone doesn't try to use the same value\n      # again.  Return false if the nonce has already been used or if the\n      # timestamp is not current.\n      # You can use OpenID::Store::Nonce::SKEW for your timestamp window.\n      # server_url: URL of the server from which the nonce originated\n      # timestamp: time the nonce was created in seconds since unix epoch\n      # salt: A random string that makes two nonces issued by a server in\n      #       the same second unique\n      def use_nonce(server_url, timestamp, salt)\n        return false if (timestamp - Time.now.to_i).abs > Nonce.skew\n        ts = timestamp.to_s # base 10 seconds since epoch\n        nonce_key = key_prefix + 'N' + server_url + '|' + ts + '|' + salt\n        result = @cache_client.add(nonce_key, '', expiry(Nonce.skew + 5))\n        if result.is_a? String\n          return !!(result =~ /^STORED/)\n        else\n          return !!(result)\n        end\n      end\n\n      def assoc_key(server_url, assoc_handle=nil)\n        key = key_prefix + 'A' + server_url\n        if assoc_handle\n          key += '|' + assoc_handle\n        end\n        return key\n      end\n\n      def cleanup_nonces\n      end\n\n      def cleanup\n      end\n\n      def cleanup_associations\n      end\n\n      protected\n\n      def delete(key)\n        result = @cache_client.delete(key)\n        if result.is_a? String\n          return !!(result =~ /^DELETED/)\n        else\n          return !!(result)\n        end\n      end\n\n      def serialize(assoc)\n        Marshal.dump(assoc)\n      end\n\n      def deserialize(assoc_str)\n        Marshal.load(assoc_str)\n      end\n\n      # Convert a lifetime in seconds into a memcache expiry value\n      def expiry(t)\n        Time.now.to_i + t\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/store/memory.rb",
    "content": "require 'openid/store/interface'\nmodule OpenID\n  module Store\n    # An in-memory implementation of Store.  This class is mainly used\n    # for testing, though it may be useful for long-running single\n    # process apps.  Note that this store is NOT thread-safe.\n    #\n    # You should probably be looking at OpenID::Store::Filesystem\n    class Memory < Interface\n\n      def initialize\n        @associations = Hash.new { |hash, key| hash[key] = {} }\n        @nonces = {}\n      end\n\n      def store_association(server_url, assoc)\n        assocs = @associations[server_url]\n        @associations[server_url] = assocs.merge({assoc.handle => deepcopy(assoc)})\n      end\n\n      def get_association(server_url, handle=nil)\n        assocs = @associations[server_url]\n        assoc = nil\n        if handle\n          assoc = assocs[handle]\n        else\n          assoc = assocs.values.sort{|a,b| a.issued <=> b.issued}[-1]\n        end\n\n        return assoc\n      end\n\n      def remove_association(server_url, handle)\n        assocs = @associations[server_url]\n        if assocs.delete(handle)\n          return true\n        else\n          return false\n        end\n      end\n\n      def use_nonce(server_url, timestamp, salt)\n        return false if (timestamp - Time.now.to_i).abs > Nonce.skew\n        nonce = [server_url, timestamp, salt].join('')\n        return false if @nonces[nonce]\n        @nonces[nonce] = timestamp\n        return true\n      end\n\n      def cleanup_associations\n        count = 0\n        @associations.each{|server_url, assocs|\n          assocs.each{|handle, assoc|\n            if assoc.expires_in == 0\n              assocs.delete(handle)\n              count += 1\n            end\n          }\n        }\n        return count\n      end\n\n      def cleanup_nonces\n        count = 0\n        now = Time.now.to_i\n        @nonces.each{|nonce, timestamp|\n          if (timestamp - now).abs > Nonce.skew\n            @nonces.delete(nonce)\n            count += 1\n          end\n        }\n        return count\n      end\n\n      protected\n\n      def deepcopy(o)\n        Marshal.load(Marshal.dump(o))\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/store/nonce.rb",
    "content": "require 'openid/cryptutil'\nrequire 'date'\nrequire 'time'\n\nmodule OpenID\n  module Nonce\n    DEFAULT_SKEW = 60*60*5\n    TIME_FMT = '%Y-%m-%dT%H:%M:%SZ'\n    TIME_STR_LEN = '0000-00-00T00:00:00Z'.size\n    @@NONCE_CHRS = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n    TIME_VALIDATOR = /\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ/\n\n    @skew = DEFAULT_SKEW\n\n    # The allowed nonce time skew in seconds.  Defaults to 5 hours.\n    # Used for checking nonce validity, and by stores' cleanup methods.\n    def Nonce.skew\n      @skew\n    end\n\n    def Nonce.skew=(new_skew)\n      @skew = new_skew\n    end\n\n    # Extract timestamp from a nonce string\n    def Nonce.split_nonce(nonce_str)\n      timestamp_str = nonce_str[0...TIME_STR_LEN]\n      raise ArgumentError if timestamp_str.size < TIME_STR_LEN\n      raise ArgumentError unless timestamp_str.match(TIME_VALIDATOR)\n      ts = Time.parse(timestamp_str).to_i\n      raise ArgumentError if ts < 0\n      return ts, nonce_str[TIME_STR_LEN..-1]\n    end\n\n    # Is the timestamp that is part of the specified nonce string\n    # within the allowed clock-skew of the current time?\n    def Nonce.check_timestamp(nonce_str, allowed_skew=nil, now=nil)\n      allowed_skew = skew if allowed_skew.nil?\n      begin\n        stamp, _ = split_nonce(nonce_str)\n      rescue ArgumentError # bad timestamp\n        return false\n      end\n      now = Time.now.to_i unless now\n\n      # times before this are too old\n      past = now - allowed_skew\n\n      # times newer than this are too far in the future\n      future = now + allowed_skew\n\n      return (past <= stamp and stamp <= future)\n    end\n\n    # generate a nonce with the specified timestamp (defaults to now)\n    def Nonce.mk_nonce(time = nil)\n      salt = CryptUtil::random_string(6, @@NONCE_CHRS)\n      if time.nil?\n        t = Time.now.getutc\n      else\n        t = Time.at(time).getutc\n      end\n      time_str = t.strftime(TIME_FMT)\n      return time_str + salt\n    end\n\n  end\nend\n"
  },
  {
    "path": "lib/openid/trustroot.rb",
    "content": "require 'uri'\nrequire 'openid/urinorm'\n\nmodule OpenID\n\n  class RealmVerificationRedirected < Exception\n    # Attempting to verify this realm resulted in a redirect.\n    def initialize(relying_party_url, rp_url_after_redirects)\n      @relying_party_url = relying_party_url\n      @rp_url_after_redirects = rp_url_after_redirects\n    end\n\n    def to_s\n      return \"Attempting to verify #{@relying_party_url} resulted in \" +\n        \"redirect to #{@rp_url_after_redirects}\"\n    end\n  end\n\n  module TrustRoot\n    TOP_LEVEL_DOMAINS = %w'\n      ac ad ae aero af ag ai al am an ao aq ar arpa as asia at\n      au aw ax az ba bb bd be bf bg bh bi biz bj bm bn bo br bs bt\n      bv bw by bz ca cat cc cd cf cg ch ci ck cl cm cn co com coop\n      cr cu cv cx cy cz de dj dk dm do dz ec edu ee eg er es et eu\n      fi fj fk fm fo fr ga gb gd ge gf gg gh gi gl gm gn gov gp gq\n      gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in info int\n      io iq ir is it je jm jo jobs jp ke kg kh ki km kn kp kr kw\n      ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mil\n      mk ml mm mn mo mobi mp mq mr ms mt mu museum mv mw mx my mz\n      na name nc ne net nf ng ni nl no np nr nu nz om org pa pe pf\n      pg ph pk pl pm pn pr pro ps pt pw py qa re ro rs ru rw sa sb\n      sc sd se sg sh si sj sk sl sm sn so sr st su sv sy sz tc td\n      tel tf tg th tj tk tl tm tn to tp tr travel tt tv tw tz ua\n      ug uk us uy uz va vc ve vg vi vn vu wf ws xn--0zwm56d\n      xn--11b5bs3a9aj6g xn--80akhbyknj4f xn--9t4b11yi5a\n      xn--deba0ad xn--g6w251d xn--hgbk6aj7f53bba\n      xn--hlcj6aya9esc7a xn--jxalpdlp xn--kgbechtv xn--zckzah ye\n      yt yu za zm zw'\n\n    ALLOWED_PROTOCOLS = ['http', 'https']\n\n    # The URI for relying party discovery, used in realm verification.\n    #\n    # XXX: This should probably live somewhere else (like in\n    # OpenID or OpenID::Yadis somewhere)\n    RP_RETURN_TO_URL_TYPE = 'http://specs.openid.net/auth/2.0/return_to'\n\n    # If the endpoint is a relying party OpenID return_to endpoint,\n    # return the endpoint URL. Otherwise, return None.\n    #\n    # This function is intended to be used as a filter for the Yadis\n    # filtering interface.\n    #\n    # endpoint: An XRDS BasicServiceEndpoint, as returned by\n    # performing Yadis dicovery.\n    #\n    # returns the endpoint URL or None if the endpoint is not a\n    # relying party endpoint.\n    def TrustRoot._extract_return_url(endpoint)\n      if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE])\n        return endpoint.uri\n      else\n        return nil\n      end\n    end\n\n    # Is the return_to URL under one of the supplied allowed\n    # return_to URLs?\n    def TrustRoot.return_to_matches(allowed_return_to_urls, return_to)\n      allowed_return_to_urls.each { |allowed_return_to|\n        # A return_to pattern works the same as a realm, except that\n        # it's not allowed to use a wildcard. We'll model this by\n        # parsing it as a realm, and not trying to match it if it has\n        # a wildcard.\n\n        return_realm = TrustRoot.parse(allowed_return_to)\n        if (# Parses as a trust root\n            !return_realm.nil? and\n\n            # Does not have a wildcard\n            !return_realm.wildcard and\n\n            # Matches the return_to that we passed in with it\n            return_realm.validate_url(return_to)\n            )\n          return true\n        end\n      }\n\n      # No URL in the list matched\n      return false\n    end\n\n    # Given a relying party discovery URL return a list of return_to\n    # URLs.\n    def TrustRoot.get_allowed_return_urls(relying_party_url)\n      rp_url_after_redirects, return_to_urls = services.get_service_endpoints(\n        relying_party_url, _extract_return_url)\n\n      if rp_url_after_redirects != relying_party_url\n        # Verification caused a redirect\n        raise RealmVerificationRedirected.new(\n                relying_party_url, rp_url_after_redirects)\n      end\n\n      return return_to_urls\n    end\n\n    # Verify that a return_to URL is valid for the given realm.\n    #\n    # This function builds a discovery URL, performs Yadis discovery\n    # on it, makes sure that the URL does not redirect, parses out\n    # the return_to URLs, and finally checks to see if the current\n    # return_to URL matches the return_to.\n    #\n    # raises DiscoveryFailure when Yadis discovery fails returns\n    # true if the return_to URL is valid for the realm\n    def TrustRoot.verify_return_to(realm_str, return_to, _vrfy=nil)\n      # _vrfy parameter is there to make testing easier\n      if _vrfy.nil?\n        _vrfy = self.method('get_allowed_return_urls')\n      end\n\n      if !(_vrfy.is_a?(Proc) or _vrfy.is_a?(Method))\n        raise ArgumentError, \"_vrfy must be a Proc or Method\"\n      end\n\n      realm = TrustRoot.parse(realm_str)\n      if realm.nil?\n        # The realm does not parse as a URL pattern\n        return false\n      end\n\n      begin\n        allowable_urls = _vrfy.call(realm.build_discovery_url())\n      rescue RealmVerificationRedirected => err\n        Util.log(err.to_s)\n        return false\n      end\n\n      if return_to_matches(allowable_urls, return_to)\n        return true\n      else\n        Util.log(\"Failed to validate return_to #{return_to} for \" +\n            \"realm #{realm_str}, was not in #{allowable_urls}\")\n        return false\n      end\n    end\n\n    class TrustRoot\n\n      attr_reader :unparsed, :proto, :wildcard, :host, :port, :path\n\n      @@empty_re = Regexp.new('^http[s]*:\\/\\/\\*\\/$')\n\n      def TrustRoot._build_path(path, query=nil, frag=nil)\n        s = path.dup\n\n        frag = nil if frag == ''\n        query = nil if query == ''\n\n        if query\n          s << \"?\" << query\n        end\n\n        if frag\n          s << \"#\" << frag\n        end\n\n        return s\n      end\n\n      def TrustRoot._parse_url(url)\n        begin\n          url = URINorm.urinorm(url)\n        rescue URI::InvalidURIError\n          nil\n        end\n\n        begin\n          parsed = URI::DEFAULT_PARSER.parse(url)\n        rescue URI::InvalidURIError\n          return nil\n        end\n\n        path = TrustRoot._build_path(parsed.path,\n                                     parsed.query,\n                                     parsed.fragment)\n\n        return [parsed.scheme || '', parsed.host || '',\n                parsed.port || '', path || '']\n      end\n\n      def TrustRoot.parse(trust_root)\n        trust_root = trust_root.dup\n        unparsed = trust_root.dup\n\n        # look for wildcard\n        wildcard = (not trust_root.index('://*.').nil?)\n        trust_root.sub!('*.', '') if wildcard\n\n        # handle http://*/ case\n        if not wildcard and @@empty_re.match(trust_root)\n          proto = trust_root.split(':')[0]\n          port = proto == 'http' ? 80 : 443\n          return new(unparsed, proto, true, '', port, '/')\n        end\n\n        parts = TrustRoot._parse_url(trust_root)\n        return nil if parts.nil?\n\n        proto, host, port, path = parts\n        return nil if host[0] == '.'\n\n        # check for URI fragment\n        if path and !path.index('#').nil?\n          return nil\n        end\n\n        return nil unless ['http', 'https'].member?(proto)\n        return new(unparsed, proto, wildcard, host, port, path)\n      end\n\n      def TrustRoot.check_sanity(trust_root_string)\n        trust_root = TrustRoot.parse(trust_root_string)\n        if trust_root.nil?\n          return false\n        else\n          return trust_root.sane?\n        end\n      end\n\n      # quick func for validating a url against a trust root.  See the\n      # TrustRoot class if you need more control.\n      def self.check_url(trust_root, url)\n        tr = self.parse(trust_root)\n        return (!tr.nil? and tr.validate_url(url))\n      end\n\n      # Return a discovery URL for this realm.\n      #\n      # This function does not check to make sure that the realm is\n      # valid. Its behaviour on invalid inputs is undefined.\n      #\n      # return_to:: The relying party return URL of the OpenID\n      # authentication request\n      #\n      # Returns the URL upon which relying party discovery should be\n      # run in order to verify the return_to URL\n      def build_discovery_url\n        if self.wildcard\n          # Use \"www.\" in place of the star\n          www_domain = 'www.' + @host\n          port = (!@port.nil? and ![80, 443].member?(@port)) ? (\":\" + @port.to_s) : ''\n          return \"#{@proto}://#{www_domain}#{port}#{@path}\"\n        else\n          return @unparsed\n        end\n      end\n\n      def initialize(unparsed, proto, wildcard, host, port, path)\n        @unparsed = unparsed\n        @proto = proto\n        @wildcard = wildcard\n        @host = host\n        @port = port\n        @path = path\n      end\n\n      def sane?\n        return true if @host == 'localhost'\n\n        host_parts = @host.split('.')\n\n        # a note: ruby string split does not put an empty string at\n        # the end of the list if the split element is last.  for\n        # example, 'foo.com.'.split('.') => ['foo','com'].  Mentioned\n        # because the python code differs here.\n\n        return false if host_parts.length == 0\n\n        # no adjacent dots\n        return false if host_parts.member?('')\n\n        # last part must be a tld\n        tld = host_parts[-1]\n        return false unless TOP_LEVEL_DOMAINS.member?(tld)\n\n        return false if host_parts.length == 1\n\n        if @wildcard\n          if tld.length == 2 and host_parts[-2].length <= 3\n            # It's a 2-letter tld with a short second to last segment\n            # so there needs to be more than two segments specified\n            # (e.g. *.co.uk is insane)\n            return host_parts.length > 2\n          end\n        end\n\n        return true\n      end\n\n      def validate_url(url)\n        parts = TrustRoot._parse_url(url)\n        return false if parts.nil?\n\n        proto, host, port, path = parts\n\n        return false unless proto == @proto\n        return false unless port == @port\n        return false unless host.index('*').nil?\n\n        if !@wildcard\n          if host != @host\n            return false\n          end\n        elsif ((@host != '') and\n               (!host.end_with?('.' + @host)) and\n               (host != @host))\n          return false\n        end\n\n        if path != @path\n          path_len = @path.length\n          trust_prefix = @path[0...path_len]\n          url_prefix = path[0...path_len]\n\n          # must be equal up to the length of the path, at least\n          if trust_prefix != url_prefix\n            return false\n          end\n\n          # These characters must be on the boundary between the end\n          # of the trust root's path and the start of the URL's path.\n          if !@path.index('?').nil?\n            allowed = '&'\n          else\n            allowed = '?/'\n          end\n\n          return (!allowed.index(@path[-1]).nil? or\n                  !allowed.index(path[path_len]).nil?)\n        end\n\n        return true\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/openid/urinorm.rb",
    "content": "require 'uri'\n\nmodule OpenID\n\n  module URINorm\n    public\n    def URINorm.urinorm(uri)\n      uri = URI.parse(uri)\n\n      raise URI::InvalidURIError.new('no scheme') unless uri.scheme\n      uri.scheme = uri.scheme.downcase\n      unless ['http','https'].member?(uri.scheme)\n        raise URI::InvalidURIError.new('Not an HTTP or HTTPS URI')\n      end\n\n      raise URI::InvalidURIError.new('no host') unless uri.host\n      uri.host = uri.host.downcase\n\n      uri.path = remove_dot_segments(uri.path)\n      uri.path = '/' if uri.path.length == 0\n\n      uri = uri.normalize.to_s\n      uri = uri.gsub(PERCENT_ESCAPE_RE) {\n        sub = $&[1..2].to_i(16).chr\n        reserved(sub) ? $&.upcase : sub\n      }\n\n      return uri\n    end\n\n    private\n    RESERVED_RE = /[A-Za-z0-9._~-]/\n    PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/\n\n    def URINorm.reserved(chr)\n      not RESERVED_RE =~ chr\n    end\n\n    def URINorm.remove_dot_segments(path)\n      result_segments = []\n\n      while path.length > 0\n        if path.start_with?('../')\n          path = path[3..-1]\n        elsif path.start_with?('./')\n          path = path[2..-1]\n        elsif path.start_with?('/./')\n          path = path[2..-1]\n        elsif path == '/.'\n          path = '/'\n        elsif path.start_with?('/../')\n          path = path[3..-1]\n          result_segments.pop if result_segments.length > 0\n        elsif path == '/..'\n          path = '/'\n          result_segments.pop if result_segments.length > 0\n        elsif path == '..' or path == '.'\n          path = ''\n        else\n          i = 0\n          i = 1 if path[0].chr == '/'\n          i = path.index('/', i)\n          i = path.length if i.nil?\n          result_segments << path[0...i]\n          path = path[i..-1]\n        end\n      end\n\n      return result_segments.join('')\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/util.rb",
    "content": "require \"cgi\"\nrequire \"uri\"\nrequire \"logger\"\n\n# See OpenID::Consumer or OpenID::Server modules, as well as the store classes\nmodule OpenID\n  class AssertionError < Exception\n  end\n\n  # Exceptions that are raised by the library are subclasses of this\n  # exception type, so if you want to catch all exceptions raised by\n  # the library, you can catch OpenIDError\n  class OpenIDError < StandardError\n  end\n\n  module Util\n\n    BASE64_CHARS = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' \\\n                    'abcdefghijklmnopqrstuvwxyz0123456789+/')\n    BASE64_RE = Regexp.compile(\"\n    \\\\A\n    ([#{BASE64_CHARS}]{4})*\n    ([#{BASE64_CHARS}]{2}==|\n     [#{BASE64_CHARS}]{3}=)?\n    \\\\Z\", Regexp::EXTENDED)\n\n    def Util.assert(value, message=nil)\n      if not value\n        raise AssertionError, message or value\n      end\n    end\n\n    def Util.to_base64(s)\n      [s].pack('m').gsub(\"\\n\", \"\")\n    end\n\n    def Util.from_base64(s)\n      without_newlines = s.gsub(/[\\r\\n]+/, '')\n      if !BASE64_RE.match(without_newlines)\n        raise ArgumentError, \"Malformed input: #{s.inspect}\"\n      end\n      without_newlines.unpack('m').first\n    end\n\n    def Util.urlencode(args)\n      a = []\n      args.each do |key, val|\n        if val.nil?\n          val = '' \n        elsif !!val == val\n          #it's boolean let's convert it to string representation\n          # or else CGI::escape won't like it\n          val = val.to_s\n        end  \n\n        a << (CGI::escape(key) + \"=\" + CGI::escape(val))\n      end\n      a.join(\"&\")\n    end\n\n    def Util.parse_query(qs)\n      query = {}\n      CGI::parse(qs).each {|k,v| query[k] = v[0]}\n      return query\n    end\n\n    def Util.append_args(url, args)\n      url = url.dup\n      return url if args.length == 0\n\n      if args.respond_to?('each_pair')\n        args = args.sort\n      end\n\n      url << (url.include?(\"?\") ? \"&\" : \"?\")\n      url << Util.urlencode(args)\n    end\n\n    @@logger = Logger.new(STDERR)\n    @@logger.progname = \"OpenID\"\n\n    def Util.logger=(logger)\n      @@logger = logger\n    end\n\n    def Util.logger\n      @@logger\n    end\n\n    # change the message below to do whatever you like for logging\n    def Util.log(message)\n      logger.info(message)\n    end\n\n    def Util.auto_submit_html(form, title='OpenID transaction in progress')\n      return \"\n<html>\n<head>\n  <title>#{title}</title>\n</head>\n<body onload='document.forms[0].submit();'>\n#{form}\n<script>\nvar elements = document.forms[0].elements;\nfor (var i = 0; i < elements.length; i++) {\n  elements[i].style.display = \\\"none\\\";\n}\n</script>\n</body>\n</html>\n\"\n    end\n\n    ESCAPE_TABLE = { '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '\"' => '&quot;', \"'\" => '&#039;' }\n    # Modified from ERb's html_encode\n    def Util.html_encode(str)\n      str.to_s.gsub(/[&<>\"']/) {|s| ESCAPE_TABLE[s] }\n    end\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/version.rb",
    "content": "module OpenID\n  VERSION = \"2.9.2\"\nend\n"
  },
  {
    "path": "lib/openid/yadis/accept.rb",
    "content": "module OpenID\n\n  module Yadis\n\n    # Generate an accept header value\n    #\n    # [str or (str, float)] -> str\n    def self.generate_accept_header(*elements)\n      parts = []\n      elements.each { |element|\n        if element.is_a?(String)\n          qs = \"1.0\"\n          mtype = element\n        else\n          mtype, q = element\n          q = q.to_f\n          if q > 1 or q <= 0\n            raise ArgumentError.new(\"Invalid preference factor: #{q}\")\n          end\n          qs = sprintf(\"%0.1f\", q)\n        end\n\n        parts << [qs, mtype]\n      }\n\n      parts.sort!\n      chunks = []\n      parts.each { |q, mtype|\n        if q == '1.0'\n          chunks << mtype\n        else\n          chunks << sprintf(\"%s; q=%s\", mtype, q)\n        end\n      }\n\n      return chunks.join(', ')\n    end\n\n    def self.parse_accept_header(value)\n      # Parse an accept header, ignoring any accept-extensions\n      #\n      # returns a list of tuples containing main MIME type, MIME\n      # subtype, and quality markdown.\n      #\n      # str -> [(str, str, float)]\n      chunks = value.split(',', -1).collect { |v| v.strip }\n      accept = []\n      chunks.each { |chunk|\n        parts = chunk.split(\";\", -1).collect { |s| s.strip }\n\n        mtype = parts.shift\n        if mtype.index('/').nil?\n          # This is not a MIME type, so ignore the bad data\n          next\n        end\n\n        main, sub = mtype.split('/', 2)\n\n        q = nil\n        parts.each { |ext|\n          if !ext.index('=').nil?\n            k, v = ext.split('=', 2)\n            if k == 'q'\n              q = v.to_f\n            end\n          end\n        }\n\n        q = 1.0 if q.nil?\n\n        accept << [q, main, sub]\n      }\n\n      accept.sort!\n      accept.reverse!\n\n      return accept.collect { |q, main, sub| [main, sub, q] }\n    end\n\n    def self.match_types(accept_types, have_types)\n      # Given the result of parsing an Accept: header, and the\n      # available MIME types, return the acceptable types with their\n      # quality markdowns.\n      #\n      # For example:\n      #\n      # >>> acceptable = parse_accept_header('text/html, text/plain; q=0.5')\n      # >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])\n      # [('text/html', 1.0), ('text/plain', 0.5)]\n      #\n      # Type signature: ([(str, str, float)], [str]) -> [(str, float)]\n      if accept_types.nil? or accept_types == []\n        # Accept all of them\n        default = 1\n      else\n        default = 0\n      end\n\n      match_main = {}\n      match_sub = {}\n      accept_types.each { |main, sub, q|\n        if main == '*'\n          default = [default, q].max\n          next\n        elsif sub == '*'\n          match_main[main] = [match_main.fetch(main, 0), q].max\n        else\n          match_sub[[main, sub]] = [match_sub.fetch([main, sub], 0), q].max\n        end\n      }\n\n      accepted_list = []\n      order_maintainer = 0\n      have_types.each { |mtype|\n        main, sub = mtype.split('/', 2)\n        if match_sub.member?([main, sub])\n          q = match_sub[[main, sub]]\n        else\n          q = match_main.fetch(main, default)\n        end\n\n        if q != 0\n          accepted_list << [1 - q, order_maintainer, q, mtype]\n          order_maintainer += 1\n        end\n      }\n\n      accepted_list.sort!\n      return accepted_list.collect { |_, _, q, mtype| [mtype, q] }\n    end\n\n    def self.get_acceptable(accept_header, have_types)\n      # Parse the accept header and return a list of available types\n      # in preferred order. If a type is unacceptable, it will not be\n      # in the resulting list.\n      #\n      # This is a convenience wrapper around matchTypes and\n      # parse_accept_header\n      #\n      # (str, [str]) -> [str]\n      accepted = self.parse_accept_header(accept_header)\n      preferred = self.match_types(accepted, have_types)\n      return preferred.collect { |mtype, _| mtype }\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/yadis/constants.rb",
    "content": "\nrequire 'openid/yadis/accept'\n\nmodule OpenID\n\n  module Yadis\n\n    YADIS_HEADER_NAME = 'X-XRDS-Location'\n    YADIS_CONTENT_TYPE = 'application/xrds+xml'\n\n    # A value suitable for using as an accept header when performing\n    # YADIS discovery, unless the application has special requirements\n    YADIS_ACCEPT_HEADER = generate_accept_header(\n                                                 ['text/html', 0.3],\n                                                 ['application/xhtml+xml', 0.5],\n                                                 [YADIS_CONTENT_TYPE, 1.0]\n                                                 )\n\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/yadis/discovery.rb",
    "content": "\nrequire 'openid/util'\nrequire 'openid/fetchers'\nrequire 'openid/yadis/constants'\nrequire 'openid/yadis/parsehtml'\n\nmodule OpenID\n\n  # Raised when a error occurs in the discovery process\n  class DiscoveryFailure < OpenIDError\n    attr_accessor :identity_url, :http_response\n\n    def initialize(message, http_response)\n      super(message)\n      @identity_url = nil\n      @http_response = http_response\n    end\n  end\n\n  module Yadis\n\n    # Contains the result of performing Yadis discovery on a URI\n    class DiscoveryResult\n\n      # The result of following redirects from the request_uri\n      attr_accessor :normalize_uri\n\n      # The URI from which the response text was returned (set to\n      # nil if there was no XRDS document found)\n      attr_accessor :xrds_uri\n\n      # The content-type returned with the response_text\n      attr_accessor :content_type\n\n      # The document returned from the xrds_uri\n      attr_accessor :response_text\n\n      attr_accessor :request_uri, :normalized_uri\n\n      def initialize(request_uri)\n        # Initialize the state of the object\n        #\n        # sets all attributes to None except the request_uri\n        @request_uri = request_uri\n        @normalized_uri = nil\n        @xrds_uri = nil\n        @content_type = nil\n        @response_text = nil\n      end\n\n      # Was the Yadis protocol's indirection used?\n      def used_yadis_location?\n        return @normalized_uri != @xrds_uri\n      end\n\n      # Is the response text supposed to be an XRDS document?\n      def is_xrds\n        return (used_yadis_location?() or\n                @content_type == YADIS_CONTENT_TYPE)\n      end\n    end\n\n    # Discover services for a given URI.\n    #\n    # uri: The identity URI as a well-formed http or https URI. The\n    # well-formedness and the protocol are not checked, but the\n    # results of this function are undefined if those properties do\n    # not hold.\n    #\n    # returns a DiscoveryResult object\n    #\n    # Raises DiscoveryFailure when the HTTP response does not have\n    # a 200 code.\n    def self.discover(uri)\n      result = DiscoveryResult.new(uri)\n      begin\n        resp = OpenID.fetch(uri, nil, {'Accept' => YADIS_ACCEPT_HEADER})\n      rescue Exception\n        raise DiscoveryFailure.new(\"Failed to fetch identity URL #{uri} : #{$!}\", $!)\n      end\n      if resp.code != \"200\" and resp.code != \"206\"\n        raise DiscoveryFailure.new(\n                \"HTTP Response status from identity URL host is not \\\"200\\\".\"\\\n                \"Got status #{resp.code.inspect} for #{resp.final_url}\", resp)\n      end\n\n      # Note the URL after following redirects\n      result.normalized_uri = resp.final_url\n\n      # Attempt to find out where to go to discover the document or if\n      # we already have it\n      result.content_type = resp['content-type']\n\n      result.xrds_uri = self.where_is_yadis?(resp)\n\n      if result.xrds_uri and result.used_yadis_location?\n        begin\n          resp = OpenID.fetch(result.xrds_uri)\n        rescue\n          raise DiscoveryFailure.new(\"Failed to fetch Yadis URL #{result.xrds_uri} : #{$!}\", $!)\n        end\n        if resp.code != \"200\" and resp.code != \"206\"\n            exc = DiscoveryFailure.new(\n                    \"HTTP Response status from Yadis host is not \\\"200\\\". \" +\n                                       \"Got status #{resp.code.inspect} for #{resp.final_url}\", resp)\n            exc.identity_url = result.normalized_uri\n            raise exc\n        end\n\n        result.content_type = resp['content-type']\n      end\n\n      result.response_text = resp.body\n      return result\n    end\n\n    # Given a HTTPResponse, return the location of the Yadis\n    # document.\n    #\n    # May be the URL just retrieved, another URL, or None, if I\n    # can't find any.\n    #\n    # [non-blocking]\n    def self.where_is_yadis?(resp)\n      # Attempt to find out where to go to discover the document or if\n      # we already have it\n      content_type = resp['content-type']\n\n      # According to the spec, the content-type header must be an\n      # exact match, or else we have to look for an indirection.\n      if (!content_type.nil? and !content_type.to_s.empty? and\n          content_type.split(';', 2)[0].downcase == YADIS_CONTENT_TYPE)\n        return resp.final_url\n      else\n        # Try the header\n        yadis_loc = resp[YADIS_HEADER_NAME.downcase]\n\n        if yadis_loc.nil?\n          # Parse as HTML if the header is missing.\n          #\n          # XXX: do we want to do something with content-type, like\n          # have a whitelist or a blacklist (for detecting that it's\n          # HTML)?\n          yadis_loc = Yadis.html_yadis_location(resp.body)\n        end\n      end\n\n      return yadis_loc\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/openid/yadis/filters.rb",
    "content": "# This file contains functions and classes used for extracting\n# endpoint information out of a Yadis XRD file using the REXML\n# XML parser.\n\n#\nmodule OpenID\n  module Yadis\n    class BasicServiceEndpoint\n      attr_reader :type_uris, :yadis_url, :uri, :service_element\n\n      # Generic endpoint object that contains parsed service\n      # information, as well as a reference to the service element\n      # from which it was generated. If there is more than one\n      # xrd:Type or xrd:URI in the xrd:Service, this object represents\n      # just one of those pairs.\n      #\n      # This object can be used as a filter, because it implements\n      # fromBasicServiceEndpoint.\n      #\n      # The simplest kind of filter you can write implements\n      # fromBasicServiceEndpoint, which takes one of these objects.\n      def initialize(yadis_url, type_uris, uri, service_element)\n        @type_uris = type_uris\n        @yadis_url = yadis_url\n        @uri = uri\n        @service_element = service_element\n      end\n\n      # Query this endpoint to see if it has any of the given type\n      # URIs. This is useful for implementing other endpoint classes\n      # that e.g. need to check for the presence of multiple\n      # versions of a single protocol.\n      def match_types(type_uris)\n        return @type_uris & type_uris\n      end\n\n      # Trivial transform from a basic endpoint to itself. This\n      # method exists to allow BasicServiceEndpoint to be used as a\n      # filter.\n      #\n      # If you are subclassing this object, re-implement this function.\n      def self.from_basic_service_endpoint(endpoint)\n        return endpoint\n      end\n\n      # A hack to make both this class and its instances respond to\n      # this message since Ruby doesn't support static methods.\n      def from_basic_service_endpoint(endpoint)\n        return self.class.from_basic_service_endpoint(endpoint)\n      end\n\n    end\n\n    # Take a list of basic filters and makes a filter that\n    # transforms the basic filter into a top-level filter. This is\n    # mostly useful for the implementation of make_filter, which\n    # should only be needed for special cases or internal use by\n    # this library.\n    #\n    # This object is useful for creating simple filters for services\n    # that use one URI and are specified by one Type (we expect most\n    # Types will fit this paradigm).\n    #\n    # Creates a BasicServiceEndpoint object and apply the filter\n    # functions to it until one of them returns a value.\n    class TransformFilterMaker\n      attr_reader :filter_procs\n\n      # Initialize the filter maker's state\n      #\n      # filter_functions are the endpoint transformer\n      # Procs to apply to the basic endpoint. These are called in\n      # turn until one of them does not return nil, and the result\n      # of that transformer is returned.\n      def initialize(filter_procs)\n        @filter_procs = filter_procs\n      end\n\n      # Returns an array of endpoint objects produced by the\n      # filter procs.\n      def get_service_endpoints(yadis_url, service_element)\n        endpoints = []\n\n        # Do an expansion of the service element by xrd:Type and\n        # xrd:URI\n        Yadis::expand_service(service_element).each { |type_uris, uri, _|\n          # Create a basic endpoint object to represent this\n          # yadis_url, Service, Type, URI combination\n          endpoint = BasicServiceEndpoint.new(\n                yadis_url, type_uris, uri, service_element)\n\n          e = apply_filters(endpoint)\n          if !e.nil?\n            endpoints << e\n          end\n        }\n        return endpoints\n      end\n\n      def apply_filters(endpoint)\n        # Apply filter procs to an endpoint until one of them returns\n        # non-nil.\n        @filter_procs.each { |filter_proc|\n          e = filter_proc.call(endpoint)\n          if !e.nil?\n            # Once one of the filters has returned an endpoint, do not\n            # apply any more.\n            return e\n          end\n        }\n\n        return nil\n      end\n    end\n\n    class CompoundFilter\n      attr_reader :subfilters\n\n      # Create a new filter that applies a set of filters to an\n      # endpoint and collects their results.\n      def initialize(subfilters)\n        @subfilters = subfilters\n      end\n\n      # Generate all endpoint objects for all of the subfilters of\n      # this filter and return their concatenation.\n      def get_service_endpoints(yadis_url, service_element)\n        endpoints = []\n        @subfilters.each { |subfilter|\n          endpoints += subfilter.get_service_endpoints(yadis_url, service_element)\n        }\n        return endpoints\n      end\n    end\n\n    # Exception raised when something is not able to be turned into a\n    # filter\n    @@filter_type_error = TypeError.new(\n      'Expected a filter, an endpoint, a callable or a list of any of these.')\n\n    # Convert a filter-convertable thing into a filter\n    #\n    # parts should be a filter, an endpoint, a callable, or a list of\n    # any of these.\n    def self.make_filter(parts)\n      # Convert the parts into a list, and pass to mk_compound_filter\n      if parts.nil?\n        parts = [BasicServiceEndpoint]\n      end\n\n      if parts.is_a?(Array)\n        return mk_compound_filter(parts)\n      else\n        return mk_compound_filter([parts])\n      end\n    end\n\n    # Create a filter out of a list of filter-like things\n    #\n    # Used by make_filter\n    #\n    # parts should be a list of things that can be passed to make_filter\n    def self.mk_compound_filter(parts)\n\n      if !parts.respond_to?('each')\n        raise TypeError, \"#{parts.inspect} is not iterable\"\n      end\n\n      # Separate into a list of callables and a list of filter objects\n      transformers = []\n      filters = []\n      parts.each { |subfilter|\n        if !subfilter.is_a?(Array)\n          # If it's not an iterable\n          if subfilter.respond_to?('get_service_endpoints')\n            # It's a full filter\n            filters << subfilter\n          elsif subfilter.respond_to?('from_basic_service_endpoint')\n            # It's an endpoint object, so put its endpoint conversion\n            # attribute into the list of endpoint transformers\n            transformers << subfilter.method('from_basic_service_endpoint')\n          elsif subfilter.respond_to?('call')\n            # It's a proc, so add it to the list of endpoint\n            # transformers\n            transformers << subfilter\n          else\n            raise @@filter_type_error\n          end\n        else\n          filters << mk_compound_filter(subfilter)\n        end\n      }\n\n      if transformers.length > 0\n        filters << TransformFilterMaker.new(transformers)\n      end\n\n      if filters.length == 1\n        return filters[0]\n      else\n        return CompoundFilter.new(filters)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/yadis/htmltokenizer.rb",
    "content": "# = HTMLTokenizer\n#\n# Author::    Ben Giddings  (mailto:bg-rubyforge@infofiend.com)\n# Copyright:: Copyright (c) 2004 Ben Giddings\n# License::   Distributes under the same terms as Ruby\n#\n#\n# This is a partial port of the functionality behind Perl's TokeParser\n# Provided a page it progressively returns tokens from that page\n#\n# $Id: htmltokenizer.rb,v 1.7 2005/06/07 21:05:53 merc Exp $\n\n#\n# A class to tokenize HTML.\n#\n# Example:\n#\n#   page = \"<HTML>\n#   <HEAD>\n#   <TITLE>This is the title</TITLE>\n#   </HEAD>\n#    <!-- Here comes the <a href=\\\"missing.link\\\">blah</a>\n#    comment body\n#     -->\n#    <BODY>\n#      <H1>This is the header</H1>\n#      <P>\n#        This is the paragraph, it contains\n#        <a href=\\\"link.html\\\">links</a>,\n#        <img src=\\\"blah.gif\\\" optional alt='images\n#        are\n#        really cool'>.  Ok, here is some more text and\n#        <A href=\\\"http://another.link.com/\\\" target=\\\"_blank\\\">another link</A>.\n#      </P>\n#    </body>\n#    </HTML>\n#    \"\n#    toke = HTMLTokenizer.new(page)\n#\n#    assert(\"<h1>\" == toke.getTag(\"h1\", \"h2\", \"h3\").to_s.downcase)\n#    assert(HTMLTag.new(\"<a href=\\\"link.html\\\">\") == toke.getTag(\"IMG\", \"A\"))\n#    assert(\"links\" == toke.getTrimmedText)\n#    assert(toke.getTag(\"IMG\", \"A\").attr_hash['optional'])\n#    assert(\"_blank\" == toke.getTag(\"IMG\", \"A\").attr_hash['target'])\n#\nclass HTMLTokenizer\n  @@version = 1.0\n\n  # Get version of HTMLTokenizer lib\n  def self.version\n    @@version\n  end\n\n  attr_reader :page\n\n  # Create a new tokenizer, based on the content, used as a string.\n  def initialize(content)\n    @page = content.to_s\n    @cur_pos = 0\n  end\n\n  # Reset the parser, setting the current position back at the stop\n  def reset\n    @cur_pos = 0\n  end\n\n  # Look at the next token, but don't actually grab it\n  def peekNextToken\n    if @cur_pos == @page.length then return nil end\n\n    if ?< == @page[@cur_pos]\n      # Next token is a tag of some kind\n      if '!--' == @page[(@cur_pos + 1), 3]\n        # Token is a comment\n        tag_end = @page.index('-->', (@cur_pos + 1))\n        if tag_end.nil?\n          raise HTMLTokenizerError, \"No end found to started comment:\\n#{@page[@cur_pos,80]}\"\n        end\n        # p @page[@cur_pos .. (tag_end+2)]\n        HTMLComment.new(@page[@cur_pos .. (tag_end + 2)])\n      else\n        # Token is a html tag\n        tag_end = @page.index('>', (@cur_pos + 1))\n        if tag_end.nil?\n          raise HTMLTokenizerError, \"No end found to started tag:\\n#{@page[@cur_pos,80]}\"\n        end\n        # p @page[@cur_pos .. tag_end]\n        HTMLTag.new(@page[@cur_pos .. tag_end])\n      end\n    else\n      # Next token is text\n      text_end = @page.index('<', @cur_pos)\n      text_end = text_end.nil? ? -1 : (text_end - 1)\n      # p @page[@cur_pos .. text_end]\n      HTMLText.new(@page[@cur_pos .. text_end])\n    end\n  end\n\n  # Get the next token, returns an instance of\n  # * HTMLText\n  # * HTMLToken\n  # * HTMLTag\n  def getNextToken\n    token = peekNextToken\n    if token\n      # @page = @page[token.raw.length .. -1]\n      # @page.slice!(0, token.raw.length)\n      @cur_pos += token.raw.length\n    end\n    #p token\n    #print token.raw\n    return token\n  end\n\n  # Get a tag from the specified set of desired tags.\n  # For example:\n  # <tt>foo =  toke.getTag(\"h1\", \"h2\", \"h3\")</tt>\n  # Will return the next header tag encountered.\n  def getTag(*sought_tags)\n    sought_tags.collect! {|elm| elm.downcase}\n\n    while (tag = getNextToken)\n      if tag.kind_of?(HTMLTag) and\n          (0 == sought_tags.length or sought_tags.include?(tag.tag_name))\n        break\n      end\n    end\n    tag\n  end\n\n  # Get all the text between the current position and the next tag\n  # (if specified) or a specific later tag\n  def getText(until_tag = nil)\n    if until_tag.nil?\n      if ?< == @page[@cur_pos]\n        # Next token is a tag, not text\n        \"\"\n      else\n        # Next token is text\n        getNextToken.text\n      end\n    else\n      ret_str = \"\"\n\n      while (tag = peekNextToken)\n        if tag.kind_of?(HTMLTag) and tag.tag_name == until_tag\n          break\n        end\n\n        if (\"\" != tag.text)\n          ret_str << (tag.text + \" \")\n        end\n        getNextToken\n      end\n\n      ret_str\n    end\n  end\n\n  # Like getText, but squeeze all whitespace, getting rid of\n  # leading and trailing whitespace, and squeezing multiple\n  # spaces into a single space.\n  def getTrimmedText(until_tag = nil)\n    getText(until_tag).strip.gsub(/\\s+/m, \" \")\n  end\n\nend\n\nclass HTMLTokenizerError < Exception\nend\n\n# The parent class for all three types of HTML tokens\nclass HTMLToken\n  attr_accessor :raw\n\n  # Initialize the token based on the raw text\n  def initialize(text)\n    @raw = text\n  end\n\n  # By default, return exactly the string used to create the text\n  def to_s\n    raw\n  end\n\n  # By default tokens have no text representation\n  def text\n    \"\"\n  end\n\n  def trimmed_text\n    text.strip.gsub(/\\s+/m, \" \")\n  end\n\n  # Compare to another based on the raw source\n  def ==(other)\n    raw == other.to_s\n  end\nend\n\n# Class representing text that isn't inside a tag\nclass HTMLText < HTMLToken\n  def text\n    raw\n  end\nend\n\n# Class representing an HTML comment\nclass HTMLComment < HTMLToken\n  attr_accessor :contents\n  def initialize(text)\n    super(text)\n    temp_arr = text.scan(/^<!--\\s*(.*?)\\s*-->$/m)\n    if temp_arr[0].nil?\n      raise HTMLTokenizerError, \"Text passed to HTMLComment.initialize is not a comment\"\n    end\n\n    @contents = temp_arr[0][0]\n  end\nend\n\n# Class representing an HTML tag\nclass HTMLTag < HTMLToken\n  attr_reader :end_tag, :tag_name\n  def initialize(text)\n    super(text)\n    if ?< != text[0] or ?> != text[-1]\n      raise HTMLTokenizerError, \"Text passed to HTMLComment.initialize is not a comment\"\n    end\n\n    @attr_hash = Hash.new\n    @raw = text\n\n    tag_name = text.scan(/[\\w:-]+/)[0]\n    if tag_name.nil?\n      raise HTMLTokenizerError, \"Error, tag is nil: #{tag_name}\"\n    end\n\n    if ?/ == text[1]\n      # It's an end tag\n      @end_tag = true\n      @tag_name = '/' + tag_name.downcase\n    else\n      @end_tag = false\n      @tag_name = tag_name.downcase\n    end\n\n    @hashed = false\n  end\n\n  # Retrieve a hash of all the tag's attributes.\n  # Lazily done, so that if you don't look at a tag's attributes\n  # things go quicker\n  def attr_hash\n    # Lazy initialize == don't build the hash until it's needed\n    if !@hashed\n      if !@end_tag\n        # Get the attributes\n        attr_arr = @raw.scan(/<[\\w:-]+\\s+(.*?)\\/?>/m)[0]\n        if attr_arr.kind_of?(Array)\n          # Attributes found, parse them\n          attrs = attr_arr[0]\n          attr_arr = attrs.scan(/\\s*([\\w:-]+)(?:\\s*=\\s*(\"[^\"]*\"|'[^']*'|([^\"'>][^\\s>]*)))?/m)\n          # clean up the array by:\n          # * setting all nil elements to true\n          # * removing enclosing quotes\n          attr_arr.each {\n            |item|\n            val = if item[1].nil?\n                    item[0]\n                  elsif '\"'[0] == item[1][0] or '\\''[0] == item[1][0]\n                    item[1][1 .. -2]\n                  else\n                    item[1]\n                  end\n            @attr_hash[item[0].downcase] = val\n          }\n        end\n      end\n      @hashed = true\n    end\n\n    #p self\n\n    @attr_hash\n  end\n\n  # Get the 'alt' text for a tag, if it exists, or an empty string otherwise\n  def text\n    if !end_tag\n      case tag_name\n      when 'img'\n        if !attr_hash['alt'].nil?\n          return attr_hash['alt']\n        end\n      when 'applet'\n        if !attr_hash['alt'].nil?\n          return attr_hash['alt']\n        end\n      end\n    end\n    return ''\n  end\nend\n\n"
  },
  {
    "path": "lib/openid/yadis/parsehtml.rb",
    "content": "require \"openid/yadis/htmltokenizer\"\nrequire 'cgi'\n\nmodule OpenID\n  module Yadis\n    def Yadis.html_yadis_location(html)\n      parser = HTMLTokenizer.new(html)\n\n      # to keep track of whether or not we are in the head element\n      in_head = false\n\n      begin\n        while el = parser.getTag('head', '/head', 'meta', 'body', '/body',\n                                 'html', 'script')\n\n          # we are leaving head or have reached body, so we bail\n          return nil if ['/head', 'body', '/body'].member?(el.tag_name)\n\n          if el.tag_name == 'head'\n            unless el.to_s[-2] == ?/ # tag ends with a /: a short tag\n              in_head = true\n            end\n          end\n          next unless in_head\n\n          if el.tag_name == 'script'\n            unless el.to_s[-2] == ?/ # tag ends with a /: a short tag\n              parser.getTag('/script')\n            end\n          end\n\n          return nil if el.tag_name == 'html'\n\n          if el.tag_name == 'meta' and (equiv = el.attr_hash['http-equiv'])\n            if ['x-xrds-location','x-yadis-location'].member?(equiv.downcase) &&\n                el.attr_hash.member?('content')\n              return CGI::unescapeHTML(el.attr_hash['content'])\n            end\n          end\n        end\n      rescue HTMLTokenizerError # just stop parsing if there's an error\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/yadis/services.rb",
    "content": "\nrequire 'openid/yadis/filters'\nrequire 'openid/yadis/discovery'\nrequire 'openid/yadis/xrds'\n\nmodule OpenID\n  module Yadis\n    def Yadis.get_service_endpoints(input_url, flt=nil)\n      # Perform the Yadis protocol on the input URL and return an\n      # iterable of resulting endpoint objects.\n      #\n      # @param flt: A filter object or something that is convertable\n      # to a filter object (using mkFilter) that will be used to\n      # generate endpoint objects. This defaults to generating\n      # BasicEndpoint objects.\n      result = Yadis.discover(input_url)\n      begin\n        endpoints = Yadis.apply_filter(result.normalized_uri,\n                                       result.response_text, flt)\n      rescue XRDSError => err\n        raise DiscoveryFailure.new(err.to_s, nil)\n      end\n\n      return [result.normalized_uri, endpoints]\n    end\n\n    def Yadis.apply_filter(normalized_uri, xrd_data, flt=nil)\n      # Generate an iterable of endpoint objects given this input data,\n      # presumably from the result of performing the Yadis protocol.\n\n      flt = Yadis.make_filter(flt)\n      et = Yadis.parseXRDS(xrd_data)\n\n      endpoints = []\n      each_service(et) { |service_element|\n        endpoints += flt.get_service_endpoints(normalized_uri, service_element)\n      }\n\n      return endpoints\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/yadis/xrds.rb",
    "content": "require 'rexml/document'\nrequire 'rexml/element'\nrequire 'rexml/xpath'\n\nrequire 'openid/yadis/xri'\n\nmodule OpenID\n  module Yadis\n\n    XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'\n    XRDS_NS = 'xri://$xrds'\n\n    XRDS_NAMESPACES = {\n      'xrds' => XRDS_NS,\n      'xrd' => XRD_NS_2_0,\n    }\n\n    class XRDSError < StandardError; end\n\n    # Raised when there's an assertion in the XRDS that it does not\n    # have the authority to make.\n    class XRDSFraud < XRDSError\n    end\n\n    def Yadis::get_canonical_id(iname, xrd_tree)\n      # Return the CanonicalID from this XRDS document.\n      #\n      # @param iname: the XRI being resolved.\n      # @type iname: unicode\n      #\n      # @param xrd_tree: The XRDS output from the resolver.\n      #\n      # @returns: The XRI CanonicalID or None.\n      # @returntype: unicode or None\n\n      xrd_list = []\n      REXML::XPath::match(xrd_tree.root, '/xrds:XRDS/xrd:XRD', XRDS_NAMESPACES).each { |el|\n        xrd_list << el\n      }\n\n      xrd_list.reverse!\n\n      cid_elements = []\n\n      if !xrd_list.empty?\n        xrd_list[0].elements.each { |e|\n          if !e.respond_to?('name')\n            next\n          end\n          if e.name == 'CanonicalID'\n            cid_elements << e\n          end\n        }\n      end\n\n      cid_element = cid_elements[0]\n\n      if !cid_element\n        return nil\n      end\n\n      canonicalID = XRI.make_xri(cid_element.text)\n\n      childID = canonicalID.downcase\n\n      xrd_list[1..-1].each { |xrd|\n        parent_sought = childID[0...childID.rindex('!')]\n\n        parent = XRI.make_xri(xrd.elements[\"CanonicalID\"].text)\n\n        if parent_sought != parent.downcase\n          raise XRDSFraud.new(sprintf(\"%s can not come from %s\", parent_sought,\n                                      parent))\n        end\n\n        childID = parent_sought\n      }\n\n      root = XRI.root_authority(iname)\n      if not XRI.provider_is_authoritative(root, childID)\n        raise XRDSFraud.new(sprintf(\"%s can not come from root %s\", childID, root))\n      end\n\n      return canonicalID\n    end\n\n    class XRDSError < StandardError\n    end\n\n    def Yadis::parseXRDS(text)\n      disable_entity_expansion do\n        if text.nil?\n          raise XRDSError.new(\"Not an XRDS document.\")\n        end\n\n        begin\n          d = REXML::Document.new(text)\n        rescue RuntimeError\n          raise XRDSError.new(\"Not an XRDS document. Failed to parse XML.\")\n        end\n\n        if is_xrds?(d)\n          return d\n        else\n          raise XRDSError.new(\"Not an XRDS document.\")\n        end\n      end\n    end\n\n    def Yadis::disable_entity_expansion\n      _previous_ = REXML::Document::entity_expansion_limit\n      REXML::Document::entity_expansion_limit = 0\n      yield\n    ensure\n      REXML::Document::entity_expansion_limit = _previous_\n    end\n\n    def Yadis::is_xrds?(xrds_tree)\n      xrds_root = xrds_tree.root\n      return (!xrds_root.nil? and\n        xrds_root.name == 'XRDS' and\n        xrds_root.namespace == XRDS_NS)\n    end\n\n    def Yadis::get_yadis_xrd(xrds_tree)\n      REXML::XPath.each(xrds_tree.root,\n                        '/xrds:XRDS/xrd:XRD[last()]',\n                        XRDS_NAMESPACES) { |el|\n        return el\n      }\n      raise XRDSError.new(\"No XRD element found.\")\n    end\n\n    # aka iterServices in Python\n    def Yadis::each_service(xrds_tree, &block)\n      xrd = get_yadis_xrd(xrds_tree)\n      xrd.each_element('Service', &block)\n    end\n\n    def Yadis::services(xrds_tree)\n      s = []\n      each_service(xrds_tree) { |service|\n        s << service\n      }\n      return s\n    end\n\n    def Yadis::expand_service(service_element)\n      es = service_element.elements\n      uris = es.each('URI') { |u| }\n      uris = prio_sort(uris)\n      types = es.each('Type/text()')\n      # REXML::Text objects are not strings.\n      types = types.collect { |t| t.to_s }\n      uris.collect { |uri| [types, uri.text, service_element] }\n    end\n\n    # Sort a list of elements that have priority attributes.\n    def Yadis::prio_sort(elements)\n      elements.sort { |a,b|\n        a.attribute('priority').to_s.to_i <=> b.attribute('priority').to_s.to_i\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/yadis/xri.rb",
    "content": "require 'openid/fetchers'\n\nmodule OpenID\n  module Yadis\n    module XRI\n\n      # The '(' is for cross-reference authorities, and hopefully has a\n      # matching ')' somewhere.\n      XRI_AUTHORITIES = [\"!\", \"=\", \"@\", \"+\", \"$\", \"(\"]\n\n      def self.identifier_scheme(identifier)\n        if (!identifier.nil? and\n            identifier.length > 0 and\n            (identifier.match('^xri://') or\n             XRI_AUTHORITIES.member?(identifier[0].chr)))\n          return :xri\n        else\n          return :uri\n        end\n      end\n\n      # Transform an XRI reference to an IRI reference.  Note this is\n      # not not idempotent, so do not apply this to an identifier more\n      # than once.  XRI Syntax section 2.3.1\n      def self.to_iri_normal(xri)\n        iri = xri.dup\n        iri.insert(0, 'xri://') if not iri.match('^xri://')\n        return escape_for_iri(iri)\n      end\n\n      # Note this is not not idempotent, so do not apply this more than\n      # once.  XRI Syntax section 2.3.2\n      def self.escape_for_iri(xri)\n        esc = xri.dup\n        # encode all %\n        esc.gsub!(/%/, '%25')\n        esc.gsub!(/\\((.*?)\\)/) { |xref_match|\n          xref_match.gsub(/[\\/\\?\\#]/) { |char_match|\n            CGI::escape(char_match)\n          }\n        }\n        return esc\n      end\n\n      # Transform an XRI reference to a URI reference.  Note this is not\n      # not idempotent, so do not apply this to an identifier more than\n      # once.  XRI Syntax section 2.3.1\n      def self.to_uri_normal(xri)\n        return iri_to_uri(to_iri_normal(xri))\n      end\n\n      # RFC 3987 section 3.1\n      def self.iri_to_uri(iri)\n        uri = iri.dup\n        # for char in ucschar or iprivate\n        # convert each char to %HH%HH%HH (as many %HH as octets)\n        return uri\n      end\n\n      def self.provider_is_authoritative(provider_id, canonical_id)\n        lastbang = canonical_id.rindex('!')\n        return false unless lastbang\n        parent = canonical_id[0...lastbang]\n        return parent == provider_id\n      end\n\n      def self.root_authority(xri)\n        xri = xri[6..-1] if xri.index('xri://') == 0\n        authority = xri.split('/', 2)[0]\n        if authority[0].chr == '('\n          root = authority[0...authority.index(')')+1]\n        elsif XRI_AUTHORITIES.member?(authority[0].chr)\n          root = authority[0].chr\n        else\n          root = authority.split(/[!*]/)[0]\n        end\n\n        self.make_xri(root)\n      end\n\n      def self.make_xri(xri)\n        if xri.index('xri://') != 0\n          xri = 'xri://' + xri\n        end\n        return xri\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/openid/yadis/xrires.rb",
    "content": "require \"cgi\"\nrequire \"openid/yadis/xri\"\nrequire \"openid/yadis/xrds\"\nrequire \"openid/fetchers\"\n\nmodule OpenID\n\n  module Yadis\n\n    module XRI\n\n      class XRIHTTPError < StandardError; end\n\n      class ProxyResolver\n\n        DEFAULT_PROXY = 'http://proxy.xri.net/'\n\n        def initialize(proxy_url=nil)\n          if proxy_url\n            @proxy_url = proxy_url\n          else\n            @proxy_url = DEFAULT_PROXY\n          end\n\n          @proxy_url += '/' unless @proxy_url.match('/$')\n        end\n\n        def query_url(xri, service_type=nil)\n          # URI normal form has a leading xri://, but we need to strip\n          # that off again for the QXRI.  This is under discussion for\n          # XRI Resolution WD 11.\n          qxri = XRI.to_uri_normal(xri)[6..-1]\n          hxri = @proxy_url + qxri\n          args = {'_xrd_r' => 'application/xrds+xml'}\n          if service_type\n            args['_xrd_t'] = service_type\n          else\n            # don't perform service endpoint selection\n            args['_xrd_r'] += ';sep=false'\n          end\n\n          return XRI.append_args(hxri, args)\n        end\n\n        def query(xri)\n          # these can be query args or http headers, needn't be both.\n          # headers = {'Accept' => 'application/xrds+xml;sep=true'}\n          canonicalID = nil\n\n          url = self.query_url(xri)\n            begin\n              response = OpenID.fetch(url)\n            rescue\n              raise XRIHTTPError, \"Could not fetch #{xri}, #{$!}\"\n            end\n            raise XRIHTTPError, \"Could not fetch #{xri}\" if response.nil?\n\n            xrds = Yadis::parseXRDS(response.body)\n            canonicalID = Yadis::get_canonical_id(xri, xrds)\n\n          return canonicalID, Yadis::services(xrds)\n          # TODO:\n          #  * If we do get hits for multiple service_types, we're almost\n          #    certainly going to have duplicated service entries and\n          #    broken priority ordering.\n        end\n      end\n\n      def self.urlencode(args)\n        a = []\n        args.each do |key, val|\n          a << (CGI::escape(key) + \"=\" + CGI::escape(val))\n        end\n        a.join(\"&\")\n      end\n\n      def self.append_args(url, args)\n        return url if args.length == 0\n\n        # rstrip question marks\n        rstripped = url.dup\n        while rstripped[-1].chr == '?'\n          rstripped = rstripped[0...rstripped.length-1]\n        end\n\n        if rstripped.index('?')\n          sep = '&'\n        else\n          sep = '?'\n        end\n\n        return url + sep + XRI.urlencode(args)\n      end\n\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "lib/openid.rb",
    "content": "# Copyright 2006-2007 JanRain, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you\n# may not use this file except in compliance with the License. You may\n# obtain a copy of the License at\n#\n#   http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied. See the License for the specific language governing\n# permissions and limitations under the License.\n\nmodule OpenID\nend\n\nrequire 'openid/version'\nrequire 'openid/consumer'\nrequire 'openid/server'\n"
  },
  {
    "path": "lib/ruby-openid.rb",
    "content": "require 'openid'\n"
  },
  {
    "path": "ruby-openid.gemspec",
    "content": "# -*- encoding: utf-8 -*-\nrequire File.expand_path('../lib/openid/version', __FILE__)\n\nGem::Specification.new do |s|\n  s.name = 'ruby-openid'\n  s.author = 'JanRain, Inc'\n  s.email = 'openid@janrain.com'\n  s.homepage = 'https://github.com/openid/ruby-openid'\n  s.summary = 'A library for consuming and serving OpenID identities.'\n  s.version = OpenID::VERSION\n  s.licenses = [\"Ruby\", \"Apache Software License 2.0\"]\n\n  # Files\n  files = Dir.glob(\"{examples,lib,test}/**/*\")\n  files << 'NOTICE' << 'CHANGELOG.md'\n  s.files = files.delete_if {|f| f.include?('_darcs') || f.include?('admin')}\n  s.require_paths = ['lib']\n  s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }\n  s.test_files = s.files.grep(%r{^(test|spec|features)/})\n\n  # RDoc\n  s.extra_rdoc_files = ['README.md', 'INSTALL.md', 'LICENSE', 'UPGRADE.md']\n  s.rdoc_options << '--main' << 'README.md'\n\n  s.add_development_dependency 'minitest', '>= 5'\nend\n"
  },
  {
    "path": "setup.rb",
    "content": "#\n# setup.rb\n#\n# Copyright (c) 2000-2005 Minero Aoki\n#\n# This program is free software.\n# You can distribute/modify this program under the terms of\n# the GNU LGPL, Lesser General Public License version 2.1.\n#\n\nunless Enumerable.method_defined?(:map)   # Ruby 1.4.6\n  module Enumerable\n    alias map collect\n  end\nend\n\nunless File.respond_to?(:read)   # Ruby 1.6\n  def File.read(fname)\n    open(fname) {|f|\n      return f.read\n    }\n  end\nend\n\nunless Errno.const_defined?(:ENOTEMPTY)   # Windows?\n  module Errno\n    class ENOTEMPTY\n      # We do not raise this exception, implementation is not needed.\n    end\n  end\nend\n\ndef File.binread(fname)\n  open(fname, 'rb') {|f|\n    return f.read\n  }\nend\n\n# for corrupted Windows' stat(2)\ndef File.dir?(path)\n  File.directory?((path[-1,1] == '/') ? path : path + '/')\nend\n\n\nclass ConfigTable\n\n  include Enumerable\n\n  def initialize(rbconfig)\n    @rbconfig = rbconfig\n    @items = []\n    @table = {}\n    # options\n    @install_prefix = nil\n    @config_opt = nil\n    @verbose = true\n    @no_harm = false\n    @libsrc_pattern = '*.rb'\n  end\n\n  attr_accessor :install_prefix\n  attr_accessor :config_opt\n\n  attr_writer :verbose\n\n  def verbose?\n    @verbose\n  end\n\n  attr_writer :no_harm\n\n  def no_harm?\n    @no_harm\n  end\n\n  attr_accessor :libsrc_pattern\n\n  def [](key)\n    lookup(key).resolve(self)\n  end\n\n  def []=(key, val)\n    lookup(key).set val\n  end\n\n  def names\n    @items.map {|i| i.name }\n  end\n\n  def each(&block)\n    @items.each(&block)\n  end\n\n  def key?(name)\n    @table.key?(name)\n  end\n\n  def lookup(name)\n    @table[name] or setup_rb_error \"no such config item: #{name}\"\n  end\n\n  def add(item)\n    @items.push item\n    @table[item.name] = item\n  end\n\n  def remove(name)\n    item = lookup(name)\n    @items.delete_if {|i| i.name == name }\n    @table.delete_if {|name, i| i.name == name }\n    item\n  end\n\n  def load_script(path, inst = nil)\n    if File.file?(path)\n      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path\n    end\n  end\n\n  def savefile\n    '.config'\n  end\n\n  def load_savefile\n    begin\n      File.foreach(savefile()) do |line|\n        k, v = *line.split(/=/, 2)\n        self[k] = v.strip\n      end\n    rescue Errno::ENOENT\n      setup_rb_error $!.message + \"\\n#{File.basename($0)} config first\"\n    end\n  end\n\n  def save\n    @items.each {|i| i.value }\n    File.open(savefile(), 'w') {|f|\n      @items.each do |i|\n        f.printf \"%s=%s\\n\", i.name, i.value if i.value? and i.value\n      end\n    }\n  end\n\n  def load_standard_entries\n    standard_entries(@rbconfig).each do |ent|\n      add ent\n    end\n  end\n\n  def standard_entries(rbconfig)\n    c = rbconfig\n\n    rubypath = c['bindir'] + '/' + c['ruby_install_name']\n\n    major = c['MAJOR'].to_i\n    minor = c['MINOR'].to_i\n    teeny = c['TEENY'].to_i\n    version = \"#{major}.#{minor}\"\n\n    # ruby ver. >= 1.4.4?\n    newpath_p = ((major >= 2) or\n                 ((major == 1) and\n                  ((minor >= 5) or\n                   ((minor == 4) and (teeny >= 4)))))\n\n    if c['rubylibdir']\n      # V > 1.6.3\n      libruby         = \"#{c['prefix']}/lib/ruby\"\n      librubyver      = c['rubylibdir']\n      librubyverarch  = c['archdir']\n      siteruby        = c['sitedir']\n      siterubyver     = c['sitelibdir']\n      siterubyverarch = c['sitearchdir']\n    elsif newpath_p\n      # 1.4.4 <= V <= 1.6.3\n      libruby         = \"#{c['prefix']}/lib/ruby\"\n      librubyver      = \"#{c['prefix']}/lib/ruby/#{version}\"\n      librubyverarch  = \"#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}\"\n      siteruby        = c['sitedir']\n      siterubyver     = \"$siteruby/#{version}\"\n      siterubyverarch = \"$siterubyver/#{c['arch']}\"\n    else\n      # V < 1.4.4\n      libruby         = \"#{c['prefix']}/lib/ruby\"\n      librubyver      = \"#{c['prefix']}/lib/ruby/#{version}\"\n      librubyverarch  = \"#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}\"\n      siteruby        = \"#{c['prefix']}/lib/ruby/#{version}/site_ruby\"\n      siterubyver     = siteruby\n      siterubyverarch = \"$siterubyver/#{c['arch']}\"\n    end\n    parameterize = lambda {|path|\n      path.sub(/\\A#{Regexp.quote(c['prefix'])}/, '$prefix')\n    }\n\n    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }\n      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]\n    else\n      makeprog = 'make'\n    end\n\n    [\n      ExecItem.new('installdirs', 'std/site/home',\n                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\\\n          {|val, table|\n            case val\n            when 'std'\n              table['rbdir'] = '$librubyver'\n              table['sodir'] = '$librubyverarch'\n            when 'site'\n              table['rbdir'] = '$siterubyver'\n              table['sodir'] = '$siterubyverarch'\n            when 'home'\n              setup_rb_error '$HOME was not set' unless ENV['HOME']\n              table['prefix'] = ENV['HOME']\n              table['rbdir'] = '$libdir/ruby'\n              table['sodir'] = '$libdir/ruby'\n            end\n          },\n      PathItem.new('prefix', 'path', c['prefix'],\n                   'path prefix of target environment'),\n      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),\n                   'the directory for commands'),\n      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),\n                   'the directory for libraries'),\n      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),\n                   'the directory for shared data'),\n      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),\n                   'the directory for man pages'),\n      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),\n                   'the directory for system configuration files'),\n      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),\n                   'the directory for local state data'),\n      PathItem.new('libruby', 'path', libruby,\n                   'the directory for ruby libraries'),\n      PathItem.new('librubyver', 'path', librubyver,\n                   'the directory for standard ruby libraries'),\n      PathItem.new('librubyverarch', 'path', librubyverarch,\n                   'the directory for standard ruby extensions'),\n      PathItem.new('siteruby', 'path', siteruby,\n          'the directory for version-independent aux ruby libraries'),\n      PathItem.new('siterubyver', 'path', siterubyver,\n                   'the directory for aux ruby libraries'),\n      PathItem.new('siterubyverarch', 'path', siterubyverarch,\n                   'the directory for aux ruby binaries'),\n      PathItem.new('rbdir', 'path', '$siterubyver',\n                   'the directory for ruby scripts'),\n      PathItem.new('sodir', 'path', '$siterubyverarch',\n                   'the directory for ruby extentions'),\n      PathItem.new('rubypath', 'path', rubypath,\n                   'the path to set to #! line'),\n      ProgramItem.new('rubyprog', 'name', rubypath,\n                      'the ruby program using for installation'),\n      ProgramItem.new('makeprog', 'name', makeprog,\n                      'the make program to compile ruby extentions'),\n      SelectItem.new('shebang', 'all/ruby/never', 'ruby',\n                     'shebang line (#!) editing mode'),\n      BoolItem.new('without-ext', 'yes/no', 'no',\n                   'does not compile/install ruby extentions')\n    ]\n  end\n  private :standard_entries\n\n  def load_multipackage_entries\n    multipackage_entries().each do |ent|\n      add ent\n    end\n  end\n\n  def multipackage_entries\n    [\n      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',\n                               'package names that you want to install'),\n      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',\n                               'package names that you do not want to install')\n    ]\n  end\n  private :multipackage_entries\n\n  ALIASES = {\n    'std-ruby'         => 'librubyver',\n    'stdruby'          => 'librubyver',\n    'rubylibdir'       => 'librubyver',\n    'archdir'          => 'librubyverarch',\n    'site-ruby-common' => 'siteruby',     # For backward compatibility\n    'site-ruby'        => 'siterubyver',  # For backward compatibility\n    'bin-dir'          => 'bindir',\n    'bin-dir'          => 'bindir',\n    'rb-dir'           => 'rbdir',\n    'so-dir'           => 'sodir',\n    'data-dir'         => 'datadir',\n    'ruby-path'        => 'rubypath',\n    'ruby-prog'        => 'rubyprog',\n    'ruby'             => 'rubyprog',\n    'make-prog'        => 'makeprog',\n    'make'             => 'makeprog'\n  }\n\n  def fixup\n    ALIASES.each do |ali, name|\n      @table[ali] = @table[name]\n    end\n    @items.freeze\n    @table.freeze\n    @options_re = /\\A--(#{@table.keys.join('|')})(?:=(.*))?\\z/\n  end\n\n  def parse_opt(opt)\n    m = @options_re.match(opt) or setup_rb_error \"config: unknown option #{opt}\"\n    m.to_a[1,2]\n  end\n\n  def dllext\n    @rbconfig['DLEXT']\n  end\n\n  def value_config?(name)\n    lookup(name).value?\n  end\n\n  class Item\n    def initialize(name, template, default, desc)\n      @name = name.freeze\n      @template = template\n      @value = default\n      @default = default\n      @description = desc\n    end\n\n    attr_reader :name\n    attr_reader :description\n\n    attr_accessor :default\n    alias help_default default\n\n    def help_opt\n      \"--#{@name}=#{@template}\"\n    end\n\n    def value?\n      true\n    end\n\n    def value\n      @value\n    end\n\n    def resolve(table)\n      @value.gsub(%r<\\$([^/]+)>) { table[$1] }\n    end\n\n    def set(val)\n      @value = check(val)\n    end\n\n    private\n\n    def check(val)\n      setup_rb_error \"config: --#{name} requires argument\" unless val\n      val\n    end\n  end\n\n  class BoolItem < Item\n    def config_type\n      'bool'\n    end\n\n    def help_opt\n      \"--#{@name}\"\n    end\n\n    private\n\n    def check(val)\n      return 'yes' unless val\n      unless /\\A(y(es)?|n(o)?|t(rue)?|f(alse))\\z/i =~ val\n        setup_rb_error \"config: --#{@name} accepts only yes/no for argument\"\n      end\n      (/\\Ay(es)?|\\At(rue)/i =~ value) ? 'yes' : 'no'\n    end\n  end\n\n  class PathItem < Item\n    def config_type\n      'path'\n    end\n\n    private\n\n    def check(path)\n      setup_rb_error \"config: --#{@name} requires argument\"  unless path\n      path[0,1] == '$' ? path : File.expand_path(path)\n    end\n  end\n\n  class ProgramItem < Item\n    def config_type\n      'program'\n    end\n  end\n\n  class SelectItem < Item\n    def initialize(name, selection, default, desc)\n      super\n      @ok = selection.split('/')\n    end\n\n    def config_type\n      'select'\n    end\n\n    private\n\n    def check(val)\n      unless @ok.include?(val.strip)\n        setup_rb_error \"config: use --#{@name}=#{@template} (#{val})\"\n      end\n      val.strip\n    end\n  end\n\n  class ExecItem < Item\n    def initialize(name, selection, desc, &block)\n      super name, selection, nil, desc\n      @ok = selection.split('/')\n      @action = block\n    end\n\n    def config_type\n      'exec'\n    end\n\n    def value?\n      false\n    end\n\n    def resolve(table)\n      setup_rb_error \"$#{name()} wrongly used as option value\"\n    end\n\n    undef set\n\n    def evaluate(val, table)\n      v = val.strip.downcase\n      unless @ok.include?(v)\n        setup_rb_error \"invalid option --#{@name}=#{val} (use #{@template})\"\n      end\n      @action.call v, table\n    end\n  end\n\n  class PackageSelectionItem < Item\n    def initialize(name, template, default, help_default, desc)\n      super name, template, default, desc\n      @help_default = help_default\n    end\n\n    attr_reader :help_default\n\n    def config_type\n      'package'\n    end\n\n    private\n\n    def check(val)\n      unless File.dir?(\"packages/#{val}\")\n        setup_rb_error \"config: no such package: #{val}\"\n      end\n      val\n    end\n  end\n\n  class MetaConfigEnvironment\n    def intiailize(config, installer)\n      @config = config\n      @installer = installer\n    end\n\n    def config_names\n      @config.names\n    end\n\n    def config?(name)\n      @config.key?(name)\n    end\n\n    def bool_config?(name)\n      @config.lookup(name).config_type == 'bool'\n    end\n\n    def path_config?(name)\n      @config.lookup(name).config_type == 'path'\n    end\n\n    def value_config?(name)\n      @config.lookup(name).config_type != 'exec'\n    end\n\n    def add_config(item)\n      @config.add item\n    end\n\n    def add_bool_config(name, default, desc)\n      @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)\n    end\n\n    def add_path_config(name, default, desc)\n      @config.add PathItem.new(name, 'path', default, desc)\n    end\n\n    def set_config_default(name, default)\n      @config.lookup(name).default = default\n    end\n\n    def remove_config(name)\n      @config.remove(name)\n    end\n\n    # For only multipackage\n    def packages\n      raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer\n      @installer.packages\n    end\n\n    # For only multipackage\n    def declare_packages(list)\n      raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer\n      @installer.packages = list\n    end\n  end\n\nend   # class ConfigTable\n\n\n# This module requires: #verbose?, #no_harm?\nmodule FileOperations\n\n  def mkdir_p(dirname, prefix = nil)\n    dirname = prefix + File.expand_path(dirname) if prefix\n    $stderr.puts \"mkdir -p #{dirname}\" if verbose?\n    return if no_harm?\n\n    # Does not check '/', it's too abnormal.\n    dirs = File.expand_path(dirname).split(%r<(?=/)>)\n    if /\\A[a-z]:\\z/i =~ dirs[0]\n      disk = dirs.shift\n      dirs[0] = disk + dirs[0]\n    end\n    dirs.each_index do |idx|\n      path = dirs[0..idx].join('')\n      Dir.mkdir path unless File.dir?(path)\n    end\n  end\n\n  def rm_f(path)\n    $stderr.puts \"rm -f #{path}\" if verbose?\n    return if no_harm?\n    force_remove_file path\n  end\n\n  def rm_rf(path)\n    $stderr.puts \"rm -rf #{path}\" if verbose?\n    return if no_harm?\n    remove_tree path\n  end\n\n  def remove_tree(path)\n    if File.symlink?(path)\n      remove_file path\n    elsif File.dir?(path)\n      remove_tree0 path\n    else\n      force_remove_file path\n    end\n  end\n\n  def remove_tree0(path)\n    Dir.foreach(path) do |ent|\n      next if ent == '.'\n      next if ent == '..'\n      entpath = \"#{path}/#{ent}\"\n      if File.symlink?(entpath)\n        remove_file entpath\n      elsif File.dir?(entpath)\n        remove_tree0 entpath\n      else\n        force_remove_file entpath\n      end\n    end\n    begin\n      Dir.rmdir path\n    rescue Errno::ENOTEMPTY\n      # directory may not be empty\n    end\n  end\n\n  def move_file(src, dest)\n    force_remove_file dest\n    begin\n      File.rename src, dest\n    rescue\n      File.open(dest, 'wb') {|f|\n        f.write File.binread(src)\n      }\n      File.chmod File.stat(src).mode, dest\n      File.unlink src\n    end\n  end\n\n  def force_remove_file(path)\n    begin\n      remove_file path\n    rescue\n    end\n  end\n\n  def remove_file(path)\n    File.chmod 0777, path\n    File.unlink path\n  end\n\n  def install(from, dest, mode, prefix = nil)\n    $stderr.puts \"install #{from} #{dest}\" if verbose?\n    return if no_harm?\n\n    realdest = prefix ? prefix + File.expand_path(dest) : dest\n    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)\n    str = File.binread(from)\n    if diff?(str, realdest)\n      verbose_off {\n        rm_f realdest if File.exist?(realdest)\n      }\n      File.open(realdest, 'wb') {|f|\n        f.write str\n      }\n      File.chmod mode, realdest\n\n      File.open(\"#{objdir_root()}/InstalledFiles\", 'a') {|f|\n        if prefix\n          f.puts realdest.sub(prefix, '')\n        else\n          f.puts realdest\n        end\n      }\n    end\n  end\n\n  def diff?(new_content, path)\n    return true unless File.exist?(path)\n    new_content != File.binread(path)\n  end\n\n  def command(*args)\n    $stderr.puts args.join(' ') if verbose?\n    system(*args) or raise RuntimeError,\n        \"system(#{args.map{|a| a.inspect }.join(' ')}) failed\"\n  end\n\n  def ruby(*args)\n    command config('rubyprog'), *args\n  end\n  \n  def make(task = nil)\n    command(*[config('makeprog'), task].compact)\n  end\n\n  def extdir?(dir)\n    File.exist?(\"#{dir}/MANIFEST\") or File.exist?(\"#{dir}/extconf.rb\")\n  end\n\n  def files_of(dir)\n    Dir.open(dir) {|d|\n      return d.select {|ent| File.file?(\"#{dir}/#{ent}\") }\n    }\n  end\n\n  DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )\n\n  def directories_of(dir)\n    Dir.open(dir) {|d|\n      return d.select {|ent| File.dir?(\"#{dir}/#{ent}\") } - DIR_REJECT\n    }\n  end\n\nend\n\n\n# This module requires: #srcdir_root, #objdir_root, #relpath\nmodule HookScriptAPI\n\n  def get_config(key)\n    @config[key]\n  end\n\n  alias config get_config\n\n  # obsolete: use metaconfig to change configuration\n  def set_config(key, val)\n    @config[key] = val\n  end\n\n  #\n  # srcdir/objdir (works only in the package directory)\n  #\n\n  def curr_srcdir\n    \"#{srcdir_root()}/#{relpath()}\"\n  end\n\n  def curr_objdir\n    \"#{objdir_root()}/#{relpath()}\"\n  end\n\n  def srcfile(path)\n    \"#{curr_srcdir()}/#{path}\"\n  end\n\n  def srcexist?(path)\n    File.exist?(srcfile(path))\n  end\n\n  def srcdirectory?(path)\n    File.dir?(srcfile(path))\n  end\n  \n  def srcfile?(path)\n    File.file?(srcfile(path))\n  end\n\n  def srcentries(path = '.')\n    Dir.open(\"#{curr_srcdir()}/#{path}\") {|d|\n      return d.to_a - %w(. ..)\n    }\n  end\n\n  def srcfiles(path = '.')\n    srcentries(path).select {|fname|\n      File.file?(File.join(curr_srcdir(), path, fname))\n    }\n  end\n\n  def srcdirectories(path = '.')\n    srcentries(path).select {|fname|\n      File.dir?(File.join(curr_srcdir(), path, fname))\n    }\n  end\n\nend\n\n\nclass ToplevelInstaller\n\n  Version   = '3.4.0'\n  Copyright = 'Copyright (c) 2000-2005 Minero Aoki'\n\n  TASKS = [\n    [ 'all',      'do config, setup, then install' ],\n    [ 'config',   'saves your configurations' ],\n    [ 'show',     'shows current configuration' ],\n    [ 'setup',    'compiles ruby extentions and others' ],\n    [ 'install',  'installs files' ],\n    [ 'test',     'run all tests in test/' ],\n    [ 'clean',    \"does `make clean' for each extention\" ],\n    [ 'distclean',\"does `make distclean' for each extention\" ]\n  ]\n\n  def ToplevelInstaller.invoke\n    config = ConfigTable.new(load_rbconfig())\n    config.load_standard_entries\n    config.load_multipackage_entries if multipackage?\n    config.fixup\n    klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)\n    klass.new(File.dirname($0), config).invoke\n  end\n\n  def ToplevelInstaller.multipackage?\n    File.dir?(File.dirname($0) + '/packages')\n  end\n\n  def ToplevelInstaller.load_rbconfig\n    if arg = ARGV.detect {|arg| /\\A--rbconfig=/ =~ arg }\n      ARGV.delete(arg)\n      load File.expand_path(arg.split(/=/, 2)[1])\n      $\".push 'rbconfig.rb'\n    else\n      require 'rbconfig'\n    end\n    ::Config::CONFIG\n  end\n\n  def initialize(ardir_root, config)\n    @ardir = File.expand_path(ardir_root)\n    @config = config\n    # cache\n    @valid_task_re = nil\n  end\n\n  def config(key)\n    @config[key]\n  end\n\n  def inspect\n    \"#<#{self.class} #{__id__()}>\"\n  end\n\n  def invoke\n    run_metaconfigs\n    case task = parsearg_global()\n    when nil, 'all'\n      parsearg_config\n      init_installers\n      exec_config\n      exec_setup\n      exec_install\n    else\n      case task\n      when 'config', 'test'\n        ;\n      when 'clean', 'distclean'\n        @config.load_savefile if File.exist?(@config.savefile)\n      else\n        @config.load_savefile\n      end\n      __send__ \"parsearg_#{task}\"\n      init_installers\n      __send__ \"exec_#{task}\"\n    end\n  end\n  \n  def run_metaconfigs\n    @config.load_script \"#{@ardir}/metaconfig\"\n  end\n\n  def init_installers\n    @installer = Installer.new(@config, @ardir, File.expand_path('.'))\n  end\n\n  #\n  # Hook Script API bases\n  #\n\n  def srcdir_root\n    @ardir\n  end\n\n  def objdir_root\n    '.'\n  end\n\n  def relpath\n    '.'\n  end\n\n  #\n  # Option Parsing\n  #\n\n  def parsearg_global\n    while arg = ARGV.shift\n      case arg\n      when /\\A\\w+\\z/\n        setup_rb_error \"invalid task: #{arg}\" unless valid_task?(arg)\n        return arg\n      when '-q', '--quiet'\n        @config.verbose = false\n      when '--verbose'\n        @config.verbose = true\n      when '--help'\n        print_usage $stdout\n        exit 0\n      when '--version'\n        puts \"#{File.basename($0)} version #{Version}\"\n        exit 0\n      when '--copyright'\n        puts Copyright\n        exit 0\n      else\n        setup_rb_error \"unknown global option '#{arg}'\"\n      end\n    end\n    nil\n  end\n\n  def valid_task?(t)\n    valid_task_re() =~ t\n  end\n\n  def valid_task_re\n    @valid_task_re ||= /\\A(?:#{TASKS.map {|task,desc| task }.join('|')})\\z/\n  end\n\n  def parsearg_no_options\n    unless ARGV.empty?\n      setup_rb_error \"#{task}: unknown options: #{ARGV.join(' ')}\"\n    end\n  end\n\n  alias parsearg_show       parsearg_no_options\n  alias parsearg_setup      parsearg_no_options\n  alias parsearg_test       parsearg_no_options\n  alias parsearg_clean      parsearg_no_options\n  alias parsearg_distclean  parsearg_no_options\n\n  def parsearg_config\n    evalopt = []\n    set = []\n    @config.config_opt = []\n    while i = ARGV.shift\n      if /\\A--?\\z/ =~ i\n        @config.config_opt = ARGV.dup\n        break\n      end\n      name, value = *@config.parse_opt(i)\n      if @config.value_config?(name)\n        @config[name] = value\n      else\n        evalopt.push [name, value]\n      end\n      set.push name\n    end\n    evalopt.each do |name, value|\n      @config.lookup(name).evaluate value, @config\n    end\n    # Check if configuration is valid\n    set.each do |n|\n      @config[n] if @config.value_config?(n)\n    end\n  end\n\n  def parsearg_install\n    @config.no_harm = false\n    @config.install_prefix = ''\n    while a = ARGV.shift\n      case a\n      when '--no-harm'\n        @config.no_harm = true\n      when /\\A--prefix=/\n        path = a.split(/=/, 2)[1]\n        path = File.expand_path(path) unless path[0,1] == '/'\n        @config.install_prefix = path\n      else\n        setup_rb_error \"install: unknown option #{a}\"\n      end\n    end\n  end\n\n  def print_usage(out)\n    out.puts 'Typical Installation Procedure:'\n    out.puts \"  $ ruby #{File.basename $0} config\"\n    out.puts \"  $ ruby #{File.basename $0} setup\"\n    out.puts \"  # ruby #{File.basename $0} install (may require root privilege)\"\n    out.puts\n    out.puts 'Detailed Usage:'\n    out.puts \"  ruby #{File.basename $0} <global option>\"\n    out.puts \"  ruby #{File.basename $0} [<global options>] <task> [<task options>]\"\n\n    fmt = \"  %-24s %s\\n\"\n    out.puts\n    out.puts 'Global options:'\n    out.printf fmt, '-q,--quiet',   'suppress message outputs'\n    out.printf fmt, '   --verbose', 'output messages verbosely'\n    out.printf fmt, '   --help',    'print this message'\n    out.printf fmt, '   --version', 'print version and quit'\n    out.printf fmt, '   --copyright',  'print copyright and quit'\n    out.puts\n    out.puts 'Tasks:'\n    TASKS.each do |name, desc|\n      out.printf fmt, name, desc\n    end\n\n    fmt = \"  %-24s %s [%s]\\n\"\n    out.puts\n    out.puts 'Options for CONFIG or ALL:'\n    @config.each do |item|\n      out.printf fmt, item.help_opt, item.description, item.help_default\n    end\n    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',\"running ruby's\"\n    out.puts\n    out.puts 'Options for INSTALL:'\n    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'\n    out.printf fmt, '--prefix=path',  'install path prefix', ''\n    out.puts\n  end\n\n  #\n  # Task Handlers\n  #\n\n  def exec_config\n    @installer.exec_config\n    @config.save   # must be final\n  end\n\n  def exec_setup\n    @installer.exec_setup\n  end\n\n  def exec_install\n    @installer.exec_install\n  end\n\n  def exec_test\n    @installer.exec_test\n  end\n\n  def exec_show\n    @config.each do |i|\n      printf \"%-20s %s\\n\", i.name, i.value if i.value?\n    end\n  end\n\n  def exec_clean\n    @installer.exec_clean\n  end\n\n  def exec_distclean\n    @installer.exec_distclean\n  end\n\nend   # class ToplevelInstaller\n\n\nclass ToplevelInstallerMulti < ToplevelInstaller\n\n  include FileOperations\n\n  def initialize(ardir_root, config)\n    super\n    @packages = directories_of(\"#{@ardir}/packages\")\n    raise 'no package exists' if @packages.empty?\n    @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))\n  end\n\n  def run_metaconfigs\n    @config.load_script \"#{@ardir}/metaconfig\", self\n    @packages.each do |name|\n      @config.load_script \"#{@ardir}/packages/#{name}/metaconfig\"\n    end\n  end\n\n  attr_reader :packages\n\n  def packages=(list)\n    raise 'package list is empty' if list.empty?\n    list.each do |name|\n      raise \"directory packages/#{name} does not exist\"\\\n              unless File.dir?(\"#{@ardir}/packages/#{name}\")\n    end\n    @packages = list\n  end\n\n  def init_installers\n    @installers = {}\n    @packages.each do |pack|\n      @installers[pack] = Installer.new(@config,\n                                       \"#{@ardir}/packages/#{pack}\",\n                                       \"packages/#{pack}\")\n    end\n    with    = extract_selection(config('with'))\n    without = extract_selection(config('without'))\n    @selected = @installers.keys.select {|name|\n                  (with.empty? or with.include?(name)) \\\n                      and not without.include?(name)\n                }\n  end\n\n  def extract_selection(list)\n    a = list.split(/,/)\n    a.each do |name|\n      setup_rb_error \"no such package: #{name}\"  unless @installers.key?(name)\n    end\n    a\n  end\n\n  def print_usage(f)\n    super\n    f.puts 'Inluded packages:'\n    f.puts '  ' + @packages.sort.join(' ')\n    f.puts\n  end\n\n  #\n  # Task Handlers\n  #\n\n  def exec_config\n    run_hook 'pre-config'\n    each_selected_installers {|inst| inst.exec_config }\n    run_hook 'post-config'\n    @config.save   # must be final\n  end\n\n  def exec_setup\n    run_hook 'pre-setup'\n    each_selected_installers {|inst| inst.exec_setup }\n    run_hook 'post-setup'\n  end\n\n  def exec_install\n    run_hook 'pre-install'\n    each_selected_installers {|inst| inst.exec_install }\n    run_hook 'post-install'\n  end\n\n  def exec_test\n    run_hook 'pre-test'\n    each_selected_installers {|inst| inst.exec_test }\n    run_hook 'post-test'\n  end\n\n  def exec_clean\n    rm_f @config.savefile\n    run_hook 'pre-clean'\n    each_selected_installers {|inst| inst.exec_clean }\n    run_hook 'post-clean'\n  end\n\n  def exec_distclean\n    rm_f @config.savefile\n    run_hook 'pre-distclean'\n    each_selected_installers {|inst| inst.exec_distclean }\n    run_hook 'post-distclean'\n  end\n\n  #\n  # lib\n  #\n\n  def each_selected_installers\n    Dir.mkdir 'packages' unless File.dir?('packages')\n    @selected.each do |pack|\n      $stderr.puts \"Processing the package `#{pack}' ...\" if verbose?\n      Dir.mkdir \"packages/#{pack}\" unless File.dir?(\"packages/#{pack}\")\n      Dir.chdir \"packages/#{pack}\"\n      yield @installers[pack]\n      Dir.chdir '../..'\n    end\n  end\n\n  def run_hook(id)\n    @root_installer.run_hook id\n  end\n\n  # module FileOperations requires this\n  def verbose?\n    @config.verbose?\n  end\n\n  # module FileOperations requires this\n  def no_harm?\n    @config.no_harm?\n  end\n\nend   # class ToplevelInstallerMulti\n\n\nclass Installer\n\n  FILETYPES = %w( bin lib ext data conf man )\n\n  include FileOperations\n  include HookScriptAPI\n\n  def initialize(config, srcroot, objroot)\n    @config = config\n    @srcdir = File.expand_path(srcroot)\n    @objdir = File.expand_path(objroot)\n    @currdir = '.'\n  end\n\n  def inspect\n    \"#<#{self.class} #{File.basename(@srcdir)}>\"\n  end\n\n  #\n  # Hook Script API base methods\n  #\n\n  def srcdir_root\n    @srcdir\n  end\n\n  def objdir_root\n    @objdir\n  end\n\n  def relpath\n    @currdir\n  end\n\n  #\n  # Config Access\n  #\n\n  # module FileOperations requires this\n  def verbose?\n    @config.verbose?\n  end\n\n  # module FileOperations requires this\n  def no_harm?\n    @config.no_harm?\n  end\n\n  def verbose_off\n    begin\n      save, @config.verbose = @config.verbose?, false\n      yield\n    ensure\n      @config.verbose = save\n    end\n  end\n\n  #\n  # TASK config\n  #\n\n  def exec_config\n    exec_task_traverse 'config'\n  end\n\n  def config_dir_bin(rel)\n  end\n\n  def config_dir_lib(rel)\n  end\n\n  def config_dir_man(rel)\n  end\n\n  def config_dir_ext(rel)\n    extconf if extdir?(curr_srcdir())\n  end\n\n  def extconf\n    ruby \"#{curr_srcdir()}/extconf.rb\", *@config.config_opt\n  end\n\n  def config_dir_data(rel)\n  end\n\n  def config_dir_conf(rel)\n  end\n\n  #\n  # TASK setup\n  #\n\n  def exec_setup\n    exec_task_traverse 'setup'\n  end\n\n  def setup_dir_bin(rel)\n    files_of(curr_srcdir()).each do |fname|\n      adjust_shebang \"#{curr_srcdir()}/#{fname}\"\n    end\n  end\n\n  def adjust_shebang(path)\n    return if no_harm?\n    tmpfile = File.basename(path) + '.tmp'\n    begin\n      File.open(path, 'rb') {|r|\n        first = r.gets\n        return unless File.basename(first.sub(/\\A\\#!/, '').split[0].to_s) == 'ruby'\n        $stderr.puts \"adjusting shebang: #{File.basename(path)}\" if verbose?\n        File.open(tmpfile, 'wb') {|w|\n          w.print first.sub(/\\A\\#!\\s*\\S+/, '#! ' + config('rubypath'))\n          w.write r.read\n        }\n      }\n      move_file tmpfile, File.basename(path)\n    ensure\n      File.unlink tmpfile if File.exist?(tmpfile)\n    end\n  end\n\n  def setup_dir_lib(rel)\n  end\n\n  def setup_dir_man(rel)\n  end\n\n  def setup_dir_ext(rel)\n    make if extdir?(curr_srcdir())\n  end\n\n  def setup_dir_data(rel)\n  end\n\n  def setup_dir_conf(rel)\n  end\n\n  #\n  # TASK install\n  #\n\n  def exec_install\n    rm_f 'InstalledFiles'\n    exec_task_traverse 'install'\n  end\n\n  def install_dir_bin(rel)\n    install_files targetfiles(), \"#{config('bindir')}/#{rel}\", 0755\n  end\n\n  def install_dir_lib(rel)\n    install_files rubyscripts(), \"#{config('rbdir')}/#{rel}\", 0644\n  end\n\n  def install_dir_ext(rel)\n    return unless extdir?(curr_srcdir())\n    install_files rubyextentions('.'),\n                  \"#{config('sodir')}/#{File.dirname(rel)}\",\n                  0555\n  end\n\n  def install_dir_data(rel)\n    install_files targetfiles(), \"#{config('datadir')}/#{rel}\", 0644\n  end\n\n  def install_dir_conf(rel)\n    # FIXME: should not remove current config files\n    # (rename previous file to .old/.org)\n    install_files targetfiles(), \"#{config('sysconfdir')}/#{rel}\", 0644\n  end\n\n  def install_dir_man(rel)\n    install_files targetfiles(), \"#{config('mandir')}/#{rel}\", 0644\n  end\n\n  def install_files(list, dest, mode)\n    mkdir_p dest, @config.install_prefix\n    list.each do |fname|\n      install fname, dest, mode, @config.install_prefix\n    end\n  end\n\n  def rubyscripts\n    glob_select(@config.libsrc_pattern, targetfiles())\n  end\n\n  def rubyextentions(dir)\n    ents = glob_select(\"*.#{@config.dllext}\", targetfiles())\n    if ents.empty?\n      setup_rb_error \"no ruby extention exists: 'ruby #{$0} setup' first\"\n    end\n    ents\n  end\n\n  def targetfiles\n    mapdir(existfiles() - hookfiles())\n  end\n\n  def mapdir(ents)\n    ents.map {|ent|\n      if File.exist?(ent)\n      then ent                         # objdir\n      else \"#{curr_srcdir()}/#{ent}\"   # srcdir\n      end\n    }\n  end\n\n  # picked up many entries from cvs-1.11.1/src/ignore.c\n  JUNK_FILES = %w( \n    core RCSLOG tags TAGS .make.state\n    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb\n    *~ *.old *.bak *.BAK *.orig *.rej _$* *$\n\n    *.org *.in .*\n  )\n\n  def existfiles\n    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))\n  end\n\n  def hookfiles\n    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|\n      %w( config setup install clean ).map {|t| sprintf(fmt, t) }\n    }.flatten\n  end\n\n  def glob_select(pat, ents)\n    re = globs2re([pat])\n    ents.select {|ent| re =~ ent }\n  end\n\n  def glob_reject(pats, ents)\n    re = globs2re(pats)\n    ents.reject {|ent| re =~ ent }\n  end\n\n  GLOB2REGEX = {\n    '.' => '\\.',\n    '$' => '\\$',\n    '#' => '\\#',\n    '*' => '.*'\n  }\n\n  def globs2re(pats)\n    /\\A(?:#{\n      pats.map {|pat| pat.gsub(/[\\.\\$\\#\\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')\n    })\\z/\n  end\n\n  #\n  # TASK test\n  #\n\n  TESTDIR = 'test'\n\n  def exec_test\n    unless File.directory?('test')\n      $stderr.puts 'no test in this package' if verbose?\n      return\n    end\n    $stderr.puts 'Running tests...' if verbose?\n    Dir.glob './test/**/test_*.rb', &method(:require)\n  end\n\n  #\n  # TASK clean\n  #\n\n  def exec_clean\n    exec_task_traverse 'clean'\n    rm_f @config.savefile\n    rm_f 'InstalledFiles'\n  end\n\n  def clean_dir_bin(rel)\n  end\n\n  def clean_dir_lib(rel)\n  end\n\n  def clean_dir_ext(rel)\n    return unless extdir?(curr_srcdir())\n    make 'clean' if File.file?('Makefile')\n  end\n\n  def clean_dir_data(rel)\n  end\n\n  def clean_dir_conf(rel)\n  end\n\n  #\n  # TASK distclean\n  #\n\n  def exec_distclean\n    exec_task_traverse 'distclean'\n    rm_f @config.savefile\n    rm_f 'InstalledFiles'\n  end\n\n  def distclean_dir_bin(rel)\n  end\n\n  def distclean_dir_lib(rel)\n  end\n\n  def distclean_dir_ext(rel)\n    return unless extdir?(curr_srcdir())\n    make 'distclean' if File.file?('Makefile')\n  end\n\n  def distclean_dir_data(rel)\n  end\n\n  def distclean_dir_conf(rel)\n  end\n\n  #\n  # lib\n  #\n\n  def exec_task_traverse(task)\n    run_hook \"pre-#{task}\"\n    FILETYPES.each do |type|\n      if config('without-ext') == 'yes' and type == 'ext'\n        $stderr.puts 'skipping ext/* by user option' if verbose?\n        next\n      end\n      traverse task, type, \"#{task}_dir_#{type}\"\n    end\n    run_hook \"post-#{task}\"\n  end\n\n  def traverse(task, rel, mid)\n    dive_into(rel) {\n      run_hook \"pre-#{task}\"\n      __send__ mid, rel.sub(%r[\\A.*?(?:/|\\z)], '')\n      directories_of(curr_srcdir()).each do |d|\n        traverse task, \"#{rel}/#{d}\", mid\n      end\n      run_hook \"post-#{task}\"\n    }\n  end\n\n  def dive_into(rel)\n    return unless File.dir?(\"#{@srcdir}/#{rel}\")\n\n    dir = File.basename(rel)\n    Dir.mkdir dir unless File.dir?(dir)\n    prevdir = Dir.pwd\n    Dir.chdir dir\n    $stderr.puts '---> ' + rel if verbose?\n    @currdir = rel\n    yield\n    Dir.chdir prevdir\n    $stderr.puts '<--- ' + rel if verbose?\n    @currdir = File.dirname(rel)\n  end\n\n  def run_hook(id)\n    path = [ \"#{curr_srcdir()}/#{id}\",\n             \"#{curr_srcdir()}/#{id}.rb\" ].detect {|cand| File.file?(cand) }\n    return unless path\n    begin\n      instance_eval File.read(path), path, 1\n    rescue\n      raise if $DEBUG\n      setup_rb_error \"hook #{path} failed:\\n\" + $!.message\n    end\n  end\n\nend   # class Installer\n\n\nclass SetupError < StandardError; end\n\ndef setup_rb_error(msg)\n  raise SetupError, msg\nend\n\nif $0 == __FILE__\n  begin\n    ToplevelInstaller.invoke\n  rescue SetupError\n    raise if $DEBUG\n    $stderr.puts $!.message\n    $stderr.puts \"Try 'ruby #{$0} --help' for detailed usage.\"\n    exit 1\n  end\nend\n"
  },
  {
    "path": "test/data/accept.txt",
    "content": "# Accept: [Accept: header value from RFC2616,\n#     http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html]\n# Available: [whitespace-separated content types]\n# Expected: [Accept-header like list, containing the available content\n#     types with their q-values]\n\nAccept: */*\nAvailable: text/plain\nExpected: text/plain; q=1.0\n\nAccept: */*\nAvailable: text/plain, text/html\nExpected: text/plain; q=1.0, text/html; q=1.0\n\n# The order matters\nAccept: */*\nAvailable: text/html, text/plain\nExpected: text/html; q=1.0, text/plain; q=1.0\n\nAccept: text/*, */*; q=0.9\nAvailable: text/plain, image/jpeg\nExpected: text/plain; q=1.0, image/jpeg; q=0.9\n\nAccept: text/*, */*; q=0.9\nAvailable: image/jpeg, text/plain\nExpected: text/plain; q=1.0, image/jpeg; q=0.9\n\n# wildcard subtypes still reject differing main types\nAccept: text/*\nAvailable: image/jpeg, text/plain\nExpected: text/plain; q=1.0\n\nAccept: text/html\nAvailable: text/html\nExpected: text/html; q=1.0\n\nAccept: text/html, text/*\nAvailable: text/html\nExpected: text/html; q=1.0\n\nAccept: text/html, text/*\nAvailable: text/plain, text/html\nExpected: text/plain; q=1.0, text/html; q=1.0\n\nAccept: text/html, text/*; q=0.9\nAvailable: text/plain, text/html\nExpected: text/html; q=1.0, text/plain; q=0.9\n\n# If a more specific type has a higher q-value, then the higher value wins\nAccept: text/*; q=0.9, text/html\nAvailable: text/plain, text/html\nExpected: text/html; q=1.0, text/plain; q=0.9\n\nAccept: */*, text/*; q=0.9, text/html; q=0.1\nAvailable: text/plain, text/html, image/monkeys\nExpected: image/monkeys; q=1.0, text/plain; q=0.9, text/html; q=0.1\n\nAccept: text/*, text/html; q=0\nAvailable: text/html\nExpected:\n\nAccept: text/*, text/html; q=0\nAvailable: text/html, text/plain\nExpected: text/plain; q=1.0\n\nAccept: text/html\nAvailable: text/plain\nExpected:\n\nAccept: application/xrds+xml, text/html; q=0.9\nAvailable: application/xrds+xml, text/html\nExpected: application/xrds+xml; q=1.0, text/html; q=0.9\n\nAccept: application/xrds+xml, */*; q=0.9\nAvailable: application/xrds+xml, text/html\nExpected: application/xrds+xml; q=1.0, text/html; q=0.9\n\nAccept: application/xrds+xml, application/xhtml+xml; q=0.9, text/html; q=0.8, text/xml; q=0.7\nAvailable: application/xrds+xml, text/html\nExpected: application/xrds+xml; q=1.0, text/html; q=0.8\n\n# See http://www.rfc-editor.org/rfc/rfc3023.txt, section A.13\nAccept: application/xrds\nAvailable: application/xrds+xml\nExpected:\n\nAccept: application/xrds+xml\nAvailable: application/xrds\nExpected:\n\nAccept: application/xml\nAvailable: application/xrds+xml\nExpected:\n\nAvailable: application/xrds+xml\nAccept: application/xml\nExpected:\n\nAvailable:\nAccept: not_a_content_type\nExpected:\n\nAvailable: text/html\nAccept: not_a_content_type, text/html\nExpected: text/html; q=1.0\n\n#################################################\n# The tests below this line are documentation of how this library\n# works. If the implementation changes, it's acceptable to change the\n# test to reflect that. These are specified so that we can make sure\n# that the current implementation actually works the way that we\n# expect it to given these inputs.\n\nAccept: text/html;level=1\nAvailable: text/html\nExpected: text/html; q=1.0\n\nAccept: text/html; level=1, text/html; level=9; q=0.1\nAvailable: text/html\nExpected: text/html; q=1.0\n\nAccept: text/html; level=9; q=0.1, text/html; level=1\nAvailable: text/html\nExpected: text/html; q=1.0\n"
  },
  {
    "path": "test/data/dh.txt",
    "content": "130706940119084053627151828062879423433929180135817317038378606310097533503449582079984816816837125851552273641820339909167103200910805078308128174143174269944095368580519322913514764528012639683546377014716235962867583443566164615728897857285824741767070432119909660645255499710701356135207437699643611094585 139808169914464096465921128085565621767096724855516655439365028496569658038844954238931647642811548254956660405394116677296461848124300258439895306367561416289126854788101396379292925819850897858045772500578222021901631436550118958972312221974009238050517034542286574826081826542722270952769078386418682059418\n91966407878983240112417790733941098492087186469785726449910011271065622315680646030230288265496017310433513856308693810812043160919214636748486185212617634222158204354206411031403206076739932806412551605172319515223573351072757800448643935018534945933808900467686115619932664888581913179496050117713298715475 88086484332488517006277516020842172054013692832175783214603951240851750819999098631851571207693874357651112736088114133607400684776234181681933311972926752846692615822043533641407510569745606256772455614745111122033229877596984718963046218854103292937700694160593653595134512369959987897086639788909618660591\n94633950701209990078055218830969910271587805983595045023718108184189787131629772007048606080263109446462048743696369276578815611098215686598630889831104860221067872883514840819381234786050098278403321905311637820524177879167250981289318356078312300538871435101338967079907049912435983871847334104247675360099 136836393035803488129856151345450008294260680733328546556640578838845312279198933806383329293483852515700876505956362639881210101974254765087350842271260064592406308509078284840473735904755203614987286456952991025347168970462354352741159076541157478949094536405618626397435745496863324654768971213730622037771\n24685127248019769965088146297942173464487677364928435784091685260262292485380918213538979925891771204729738138857126454465630594391449913947358655368215901119137728648638547728497517587701248406019427282237279437409508871300675355166059811431191200555457304463617727969228965042729205402243355816702436970430 103488011917988946858248200111251786178288940265978921633592888293430082248387786443813155999158786903216094876295371112716734481877806417714913656921169196196571699893360825510307056269738593971532017994987406325068886420548597161498019372380511676314312298122272401348856314619382867707981701472607230523868\n116791045850880292989786005885944774698035781824784400772676299590038746153860847252706167458966356897309533614849402276819438194497464696186624618374179812548893947178936305721131565012344462048549467883494038577857638815386798694225798517783768606048713198211730870155881426709644960689953998714045816205549 25767875422998856261320430397505398614439586659207416236135894343577952114994718158163212134503751463610021489053571733974769536157057815413209619147486931502025658987681202196476489081257777148377685478756033509708349637895740799542063593586769082830323796978935454479273531157121440998804334199442003857410\n75582226959658406842894734694860761896800153014775231713388264961517169436476322183886891849966756849783437334069692683523296295601533803799559985845105706728538458624387103621364117548643541824878550074680443708148686601108223917493525070861593238005735446708555769966855130921562955491250908613793521520082 51100990616369611694975829054222013346248289055987940844427061856603230021472379888102172458517294080775792439385531234808129302064303666640376750139242970123503857186428797403843206765926798353022284672682073397573130625177187185114726049347844460311761033584101482859992951420083621362870301150543916815123\n22852401165908224137274273646590366934616265607879280260563022941455466297431255072303172649495519837876946233272420969249841381161312477263365567831938496555136366981954001163034914812189448922853839616662859772087929140818377228980710884492996109434435597500854043325062122184466315338260530734979159890875 35017410720028595029711778101507729481023945551700945988329114663345341120595162378885287946069695772429641825579528116641336456773227542256911497084242947904528367986325800537695079726856460817606404224094336361853766354225558025931211551975334149258299477750615397616908655079967952372222383056221992235704\n37364490883518159794654045194678325635036705086417851509136183713863262621334636905291385255662750747808690129471989906644041585863034419130023070856805511017402434123099100618568335168939301014148587149578150068910141065808373976114927339040964292334109797421173369274978107389084873550233108940239410902552 40916262212189137562350357241447034318002130016858244002788189310078477605649010031339865625243230798681216437501833540185827501244378529230150467789369234869122179247196276164931090039290879808162629109742198951942358028123056268054775108592325500609335947248599688175189333996086475013450537086042387719925\n42030470670714872936404499074069849778147578537708230270030877866700844337372497704027708080369726758812896818567830863540507961487472657570488625639077418109017434494794778542739932765561706796300920251933107517954265066804108669800167526425723377411855061131982689717887180411017924173629124764378241885274 124652439272864857598747946875599560379786580730218192165733924418687522301721706620565030507816884907589477351553268146177293719586287258662025940181301472851649975563004543250656807255226609296537922304346339513054316391667044301386950180277940536542183725690479451746977789001659540839582630251935163344393\n33176766914206542084736303652243484580303865879984981189372762326078776390896986743451688462101732968104375838228070296418541745483112261133079756514082093269959937647525005374035326747696591842313517634077723301677759648869372517403529488493581781546743147639937580084065663597330159470577639629864369972900 67485835091897238609131069363014775606263390149204621594445803179810038685760826651889895397414961195533694176706808504447269558421955735607423135937153901140512527504198912146656610630396284977496295289999655140295415981288181545277299615922576281262872097567020980675200178329219970170480653040350512964539\n131497983897702298481056962402569646971797912524360547236788650961059980711719600424210346263081838703940277066368168874781981151411096949736205282734026497995296147418292226818536168555712128736975034272678008697869326747592750850184857659420541708058277866000692785617873742438060271311159568468507825422571 5400380840349873337222394910303409203226429752629134721503171858543984393161548520471799318518954232197106728096866840965784563043721652790856860155702760027304915133166173298206604451826182024471262142046935060360564569939062438160049193241369468208458085699995573492688298015026628427440418009025072261296\n83265103005695640943261961853521077357830295830250157593141844209296716788437615940096402365505416686459260302419338241462783388722843946886845478224048360927114533590583464979009731440049610985062455108831881153988321298531365779084012803908832525921630534096740755274371500276660832724874701671184539131864 141285570207910287798371174771658911045525474449663877845558585668334618068814605961306961485855329182957174312715910923324965889174835444049526313968571611940626279733302104955951067959291852710640374412577070764165811275030632465290729619533330733368808295932659463215921521905553936914975786500018720073003\n68435028583616495789148116911096163791710022987677894923742899873596891423986951658100606742052014161171185231735413902875605720814417622409817842932759492013585936536452615480700628719795872201528559780249210820284350401473564919576289210869896327937002173624497942136329576506818749730506884927872345019446 134655528287263100540003157571441260698452262106680191153945271167894435782028803135774578949200580551016388918860856991026082917835209212892423567114480975540305860034439015788120390011692862968771136814777768281366591257663821495720134621172848947971117885754539770645621669309650476331439675400544167728223\n97765390064836080322590528352647421920257073063706996347334558390461274981996865736612531330863478931481491964338380362350271734683183807511097331539820133036984271653285063355715726806139083282458695728902452215405696318402583540317419929113959816258829534543044153959951908676300847164682178008704099351835 92552521881196975294401505656851872247567784546370503402756239533783651371688190302773864319828182042605239246779598629409815474038541272600580320815319709309111399294952620375093803971373108792300726524826209329889463854451846561437729676142864421966497641824498079067929811613947148353921163336822026640804\n145767094672933012300753301037546647564595762930138884463767054235112032706630891961371504668013023047595721138624016493638510710257541241706724342585654715468628355455898091951826598092812212209834746162089753649871544789379424903025374228231365026585872808685759231756517703720396301355299998059523896918448 116669462839999965355861187716880953863237226719689755457884414384663576662696981997535568446560375442532084973721539944428004043491468494548231348032618218312515409944970197902589794303562379864012797605284844016184274353252071642511293089390472576498394410829972525726474727579603392265177009323768966538608\n34172517877854802711907683049441723730724885305592620486269966708379625109832852005775048584124451699198484092407720344962116726808090368739361658889584507734617844212547181476646725256303630128954338675520938806905779837227983648887192531356390902975904503218654196581612781227843742951241442641220856414232 126013077261793777773236390821108423367648447987653714614732477073177878509574051196587476846560696305938891953527959347566502332765820074506907037627115954790645652211088723122982633069089920979477728376746424256704724173255656757918995039125823421607024407307091796807227896314403153380323770001854211384322\n9979624731056222925878866378063961280844793874828281622845276060532093809300121084179730782833657205171434732875093693074415298975346410131191865198158876447591891117577190438695367929923494177555818480377241891190442070100052523008290671797937772993634966511431668500154258765510857129203107386972819651767 76559085024395996164590986654274454741199399364851956129137304209855150918182685643729981600389513229011956888957763987167398150792454613751473654448162776379362213885827651020309844507723069713820393068520302223477225569348080362344052033711960892643036147232270133731530049660264526964146237693063093765111\n18162696663677410793062235946366423954875282212790518677684260521370996677183041664345920941714064628111537529793170736292618705900247450994864220481135611781148410617609559050220262121494712903009168783279356915189941268264177631458029177102542745167475619936272581126346266816618866806564180995726437177435 63244550218824945129624987597134280916829928261688093445040235408899092619821698537312158783367974202557699994650667088974727356690181336666077506063310290098995215324552449858513870629176838494348632073938023916155113126203791709810160925798130199717340478393420816876665127594623142175853115698049952126277\n4817943161362708117912118300716778687157593557807116683477307391846133734701449509121209661982298574607233039490570567781316652698287671086985501523197566560479906850423709894582834963398034434055472063156147829131181965140631257939036683622084290629927807369457311894970308590034407761706800045378158588657 61612160237840981966750225147965256022861527286827877531373888434780789812764688703260066154973576040405676432586962624922734102370509771313805122788566405984830112657060375568510809122230960988304085950306616401218206390412815884549481965750553137717475620505076144744211331973240555181377832337912951699135\n36363324947629373144612372870171042343590861026293829791335153646774927623889458346817049419803031378037141773848560341251355283891019532059644644509836766167835557471311319194033709837770615526356168418160386395260066262292757953919140150454538786106958252854181965875293629955562111756775391296856504912587 86831561031659073326747216166881733513938228972332631084118628692228329095617884068498116676787029033973607066377816508795286358748076949738854520048303930186595481606562375516134920902325649683618195251332651685732712539073110524182134321873838204219194459231650917098791250048469346563303077080880339797744\n26406869969418301728540993821409753036653370247174689204659006239823766914991146853283367848649039747728229875444327879875275718711878211919734397349994000106499628652960403076186651083084423734034070082770589453774926850920776427074440483233447839259180467805375782600203654373428926653730090468535611335253 100139935381469543084506312717977196291289016554846164338908226931204624582010530255955411615528804421371905642197394534614355186795223905217732992497673429554618838376065777445760355552020655667172127543653684405493978325270279321013143828897100500212200358450649158287605846102419527584313353072518101626851\n92613116984760565837109105383781193800503303131143575169488835702472221039082994091847595094556327985517286288659598094631489552181233202387028607421487026032402972597880028640156629614572656967808446397456622178472130864873587747608262139844319805074476178618930354824943672367046477408898479503054125369731 30023391082615178562263328892343821010986429338255434046051061316154579824472412477397496718186615690433045030046315908170615910505869972621853946234911296439134838951047107272129711854649412919542407760508235711897489847951451200722151978578883748353566191421685659370090024401368356823252748749449302536931\n31485815361342085113278193504381994806529237123359718043079410511224607873725611862217941085749929342777366642477711445011074784469367917758629403998067347054115844421430072631339788256386509261291675080191633908849638316409182455648806133048549359800886124554879661473112614246869101243501787363247762961784 114503770698890543429251666713050844656853278831559195214556474458830029271801818536133531843456707474500106283648085144619097572354066554819887152106174400667929098257361286338795493838820850475790977445807435511982704395422526800272723708548541616513134676140304653112325071112865020365664833601046215694089\n76882090884790547431641385530818076533805072109483843307806375918023300052767710853172670987385376253156912268523505310624133905633437815297307463917718596711590885553760690350221265675690787249135345226947453988081566088302642706234126002514517416493192624887800567412565527886687096028028124049522890448168 15056463217273240496622619354104573042767532856243223052125822509781815362480522535564283485059790932505429110157271454207173426525345813426696743168079246510944969446574354255284952839036431873039487144279164893710061580467579842173706653409487110282515691099753380094215805485573768509475850463001549608836\n52345178981230648108672997265819959243255047568833938156267924185186047373470984278294897653277996726416846430969793375429223610099546622112048283560483136389901514170116723365811871938630317974150540909650396429631704968748113009366339718498979597226137532343384889080245796447593572468846438769413505393967 32148494517199936472358017244372701214529606506776255341152991328091526865643069587953759877295255050519124541457805199596762210567333445908166076384465183589342153762720515477404466193879418014196727238972417616122646440870364200208488239778452378059236162633837824948613596114768455832408342040970780086\n41095268619128788015767564971105114602454449306041732792746397800275041704886345704294273937217484580365505320134717320083763349380629342859670693445658118959823430378844830923452105707338162448974869312012791385772125813291388247857971218575518319578818336960572244046567099555399203328678654466958536663208 92166550199033418923713824997841892577149715275633481076285269142670107687867024550593869464613175882141630640739938334001211714884975032600306279287443909448541179109981755796752132502127330056736913454039526413284519137059580845856736918773597087836203497066909257930043736166431682872083389105176299181629\n40049143661018504441607875135884755310012910557581028447435354354754245291878800571089144452035026644953322330676651798951447670184106450649737772686119714700743396359069052813433030118630105307022867200053964644574786137276428546712005171080129190959914708907200288299169344380390093918556722227705114244981 108159089972386282154772900619022507336076619354549601813179459338897131937353741544606392560724999980281424266891537298473163753022749859939445293926707568015958367188089915420630082556748668489756475027008449860889202622698060097015044886961901650857610841562477736791450080980702347705778074391774667412741\n69905259478181995876884927656894491893594530150260951315109404530530357998889589977208787140430938039028941393673520799460431992051993157468616168400324834880926190141581037597526917869362292931957289043707855837933490285814769110495657056206391880865972389421774822461752702336812585852278453803972600333734 71821415380277072313878763768684432371552628204186742842154591000123020597011744840460964835414360968627162765288463383113375595799297552681618876474019263288277398833725479226930770694271622605114061622753165584075733358178384410640349907375170170910499615355511313349300918885560131539570707695789106185664\n26945345439378873515011714350080059082081595419023056538696949766471272811362104837806324694947413603019863785876836706911406330379274553386254346050697348395574746891556054334903838949157798006141473389066020212044825140294048709654273698482867946522782450500680195477050110145664069582549935651920545151500 80313315938584480048642653013876614091607852535582224914294013785054094052454758327935781971746329853786568549510067442145637007308960551652864942042189241081946607011847245280773379099020221884296226818685556430275385068764313042226925852500883894269809033380734632866477789520106865758504064806906234130588\n"
  },
  {
    "path": "test/data/example-xrds.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Sample XRDS file at: NAME -->\n<xrds:XRDS\n    xmlns:xrds=\"xri://$xrds\"\n    xmlns=\"xri://$xrd*($v*2.0)\">\n  <XRD>\n\n    <Service priority=\"0\">\n      <Type>http://example.com/</Type>\n      <URI>http://www.openidenabled.com/</URI>\n    </Service>\n\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/linkparse.txt",
    "content": "Num Tests: 72\n\nOpenID link parsing test cases\nCopyright (C) 2005-2008, JanRain, Inc.\nSee COPYING for license information.\n\nFile format\n-----------\n\nAll text before the first triple-newline (this chunk) should be ignored.\n\nThis file may be interpreted as Latin-1 or UTF-8.\n\nTest cases separated by three line separators (`\\n\\n\\n'). The test\ncases consist of a headers section followed by a data block. These are\nseparated by a double newline. The headers consist of the header name,\nfollowed by a colon, a space, the value, and a newline. There must be\none, and only one, `Name' header for a test case. There may be zero or\nmore link headers. The `Link' header consists of whitespace-separated\nattribute pairs. A link header with an empty string as a value\nindicates an empty but present link tag. The attribute pairs are `='\nseparated and not quoted.\n\nOptional Links and attributes have a trailing `*'. A compilant\nimplementation may produce this as output or may not. A compliant\nimplementation will not produce any output that is absent from this\nfile.\n\n\nName: No link tag at all\n\n<html>\n<head>\n</head>\n</html>\n\n\nName: Link element first\n\n<link>\n\n\nName: Link inside HTML, not head\n\n<html>\n<link>\n\n\nName: Link inside head, not html\n\n<head>\n<link>\n\n\nName: Link inside html, after head\n\n<html>\n<head>\n</head>\n<link>\n\n\nName: Link inside html, before head\n\n<html>\n<link>\n<head>\n\n\nName: Link before html and head\n\n<link>\n<html>\n<head>\n\n\nName: Link after html document with head\n\n<html>\n<head>\n</head>\n</html>\n<link>\n\n\nName: Link inside html inside head, inside another html\n\n<html>\n<head>\n<html>\n<link>\n\n\nName: Link inside html inside head\n\n<head>\n<html>\n<link>\n\n\nName: link inside body inside head inside html\n\n<html>\n<head>\n<body>\n<link>\n\n\nName: Link inside head inside head inside html\n\n<html>\n<head>\n<head>\n<link>\n\n\nName: Link inside script inside head inside html\n\n<html>\n<head>\n<script>\n<link>\n</script>\n\n\nName: Link inside comment inside head inside html\n\n<html>\n<head/>\n<link>\n\n\nName: Link inside of head after short head\n\n<html>\n<head/>\n<head>\n<link>\n\n\nName: Plain vanilla\nLink:\n\n<html>\n<head>\n<link>\n\n\nName: Ignore tags in the <script:... > namespace\nLink*:\n\n<html>\n<head>\n<script:paddypan>\n<link>\n</script:paddypan>\n\n\nName: Short link tag\nLink:\n\n<html>\n<head>\n<link/>\n\n\nName: Spaces in the HTML tag\nLink:\n\n<html >\n<head>\n<link>\n\n\nName: Spaces in the head tag\nLink:\n\n<html>\n<head >\n<link>\n\n\nName: Spaces in the link tag\nLink:\n\n<html>\n<head>\n<link >\n\n\nName: No whitespace\nLink:\n\n<html><head><link>\n\n\nName: Closed head tag\nLink:\n\n<html>\n<head>\n<link>\n</head>\n\n\nName: One good, one bad (after close head)\nLink:\n\n<html>\n<head>\n<link>\n</head>\n<link>\n\n\nName: One good, one bad (after open body)\nLink:\n\n<html>\n<head>\n<link>\n<body>\n<link>\n\n\nName: ill formed (missing close head)\nLink:\n\n<html>\n<head>\n<link>\n</html>\n\n\nName: Ill formed (no close head, link after </html>)\nLink:\n\n<html>\n<head>\n<link>\n</html>\n<link>\n\n\nName: Ignore random tags inside of html\nLink:\n\n<html>\n<delicata>\n<head>\n<title>\n<link>\n\n\nName: case-folding\nLink*:\n\n<HtMl>\n<hEaD>\n<LiNk>\n\n\nName: unexpected tags\nLink:\n\n<butternut>\n<html>\n<summer>\n<head>\n<turban>\n<link>\n\n\nName: un-closed script tags\nLink*:\n\n<html>\n<head>\n<script>\n<link>\n\n\nName: un-closed script tags (no whitespace)\nLink*:\n\n<html><head><script><link>\n\n\nName: un-closed comment\nLink*:\n\n<html>\n<head>\n<!--\n<link>\n\n\nName: un-closed CDATA\nLink*:\n\n<html>\n<head>\n<![CDATA[\n<link>\n\n\nName: cdata-like\nLink*:\n\n<html>\n<head>\n<![ACORN[\n<link>\n]]>\n\n\nName: comment close only\nLink:\n\n<html>\n<head>\n<link>\n-->\n\n\nName: Vanilla, two links\nLink:\nLink:\n\n<html>\n<head>\n<link>\n<link>\n\n\nName: extra tag, two links\nLink:\nLink:\n\n<html>\n<gold nugget>\n<head>\n<link>\n<link>\n\n\nName: case-fold, body ends, two links\nLink:\nLink*:\n\n<html>\n<head>\n<link>\n<LiNk>\n<body>\n<link>\n\n\nName: simple, non-quoted rel\nLink: rel=openid.server\n\n<html><head><link rel=openid.server>\n\n\nName: short tag has rel\nLink: rel=openid.server\n\n<html><head><link rel=openid.server/>\n\n\nName: short tag w/space has rel\nLink: rel=openid.server\n\n<html><head><link rel=openid.server />\n\n\nName: extra non-attribute, has rel\nLink: rel=openid.server hubbard*=hubbard\n\n<html><head><link hubbard rel=openid.server>\n\n\nName: non-attr, has rel, short\nLink: rel=openid.server hubbard*=hubbard\n\n<html><head><link hubbard rel=openid.server/>\n\n\nName: non-attr, has rel, short, space\nLink: rel=openid.server hubbard*=hubbard\n\n<html><head><link hubbard rel=openid.server />\n\n\nName: misplaced slash has rel\nLink: rel=openid.server\n\n<html><head><link / rel=openid.server>\n\n\nName: quoted rel\nLink: rel=openid.server\n\n<html><head><link rel=\"openid.server\">\n\n\nName: single-quoted rel\nLink: rel=openid.server\n\n<html><head><link rel='openid.server'>\n\n\nName: two links w/ rel\nLink: x=y\nLink: a=b\n\n<html><head><link x=y><link a=b>\n\n\nName: non-entity\nLink: x=&y\n\n<html><head><link x=&y>\n\n\nName: quoted non-entity\nLink: x=&y\n\n<html><head><link x=\"&y\">\n\n\nName: quoted entity\nLink: x=&\n\n<html><head><link x=\"&amp;\">\n\n\nName: entity not processed\nLink: x=&#26;\n\n<html><head><link x=\"&#26;\">\n\n\nName: &lt;\nLink: x=<\n\n<html><head><link x=\"&lt;\">\n\n\nName: &gt;\nLink: x=>\n\n<html><head><link x=\"&gt;\">\n\n\nName: &quot;\nLink: x=\"\n\n<html><head><link x=\"&quot;\">\n\n\nName: &amp;&quot;\nLink: x=&\"\n\n<html><head><link x=\"&amp;&quot;\">\n\n\nName: mixed entity and non-entity\nLink: x=&\"&hellip;>\n\n<html><head><link x=\"&amp;&quot;&hellip;&gt;\">\n\n\nName: mixed entity and non-entity (w/normal chars)\nLink: x=x&\"&hellip;>x\n\n<html><head><link x=\"x&amp;&quot;&hellip;&gt;x\">\n\n\nName: broken tags\nLink*: x=y\nLink*: x=y<\n\n<html><head><link x=y<>\n\n\nName: missing close pointy\nLink*: x=y\nLink*: x=y<link z=y\nLink*: z=y\n\n<html><head><link x=y<link z=y />\n\n\nName: missing attribute value\nLink: x=y y*=y\nLink: x=y\n\n<html><head><link x=y y=><link x=y />\n\n\nName: Missing close pointy (no following)\nLink*: x=y\n\n<html><head><link x=y\n\n\nName: Should be quoted\nLink*: x=<\n\n<html><head><link x=\"<\">\n\n\nName: Should be quoted (2)\nLink*: x=>\nLink*: x=x\n\n<html><head><link x=\">\">\n\n\nName: Repeated attribute\nLink: x=y\n\n<html><head><link x=z x=y>\n\n\nName: Repeated attribute (2)\nLink: x=y\n\n<html><head><link x=y x=y>\n\n\nName: Two attributes\nLink: x=y y=z\n\n<html><head><link x=y y=z>\n\n\nName: Well-formed link rel=\"openid.server\"\nLink: rel=openid.server href=http://www.myopenid.com/server\n\n<html>\n  <head>\n    <link rel=\"openid.server\"\n          href=\"http://www.myopenid.com/server\" />\n  </head>\n</html>\n\n\nName: Well-formed link rel=\"openid.server\" and \"openid.delegate\"\nLink: rel=openid.server href=http://www.myopenid.com/server\nLink: rel=openid.delegate href=http://example.myopenid.com/\n\n<html><head><link rel=\"openid.server\"\n                  href=\"http://www.myopenid.com/server\" />\n            <link rel=\"openid.delegate\" href=\"http://example.myopenid.com/\" />\n</head></html>\n\n\nName: from brian's livejournal page\nLink: rel=stylesheet href=http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711 type=text/css\nLink: rel=openid.server href=http://www.livejournal.com/openid/server.bml\n\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n          \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n  <head>\n    <link rel=\"stylesheet\"\n          href=\"http://www.livejournal.com/~serotta/res/319998/stylesheet?1130478711\"\n          type=\"text/css\" />\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <meta name=\"foaf:maker\"\n          content=\"foaf:mbox_sha1sum '12f8abdacb5b1a806711e23249da592c0d316260'\" />\n    <meta name=\"robots\" content=\"noindex, nofollow, noarchive\" />\n    <meta name=\"googlebot\" content=\"nosnippet\" />\n    <link rel=\"openid.server\"\n          href=\"http://www.livejournal.com/openid/server.bml\" />\n    <title>Brian</title>\n  </head>\n\n\nName: non-ascii (Latin-1 or UTF8)\nLink: x=®\n\n<html><head><link x=\"®\">\n\n\n"
  },
  {
    "path": "test/data/n2b64",
    "content": "AA== 0\nAQ== 1\nAg== 2\nAw== 3\nBA== 4\nBQ== 5\nBg== 6\nBw== 7\nCA== 8\nCQ== 9\nCg== 10\nCw== 11\nDA== 12\nDQ== 13\nDg== 14\nDw== 15\nEA== 16\nEQ== 17\nEg== 18\nEw== 19\nFA== 20\nFQ== 21\nFg== 22\nFw== 23\nGA== 24\nGQ== 25\nGg== 26\nGw== 27\nHA== 28\nHQ== 29\nHg== 30\nHw== 31\nIA== 32\nIQ== 33\nIg== 34\nIw== 35\nJA== 36\nJQ== 37\nJg== 38\nJw== 39\nKA== 40\nKQ== 41\nKg== 42\nKw== 43\nLA== 44\nLQ== 45\nLg== 46\nLw== 47\nMA== 48\nMQ== 49\nMg== 50\nMw== 51\nNA== 52\nNQ== 53\nNg== 54\nNw== 55\nOA== 56\nOQ== 57\nOg== 58\nOw== 59\nPA== 60\nPQ== 61\nPg== 62\nPw== 63\nQA== 64\nQQ== 65\nQg== 66\nQw== 67\nRA== 68\nRQ== 69\nRg== 70\nRw== 71\nSA== 72\nSQ== 73\nSg== 74\nSw== 75\nTA== 76\nTQ== 77\nTg== 78\nTw== 79\nUA== 80\nUQ== 81\nUg== 82\nUw== 83\nVA== 84\nVQ== 85\nVg== 86\nVw== 87\nWA== 88\nWQ== 89\nWg== 90\nWw== 91\nXA== 92\nXQ== 93\nXg== 94\nXw== 95\nYA== 96\nYQ== 97\nYg== 98\nYw== 99\nZA== 100\nZQ== 101\nZg== 102\nZw== 103\naA== 104\naQ== 105\nag== 106\naw== 107\nbA== 108\nbQ== 109\nbg== 110\nbw== 111\ncA== 112\ncQ== 113\ncg== 114\ncw== 115\ndA== 116\ndQ== 117\ndg== 118\ndw== 119\neA== 120\neQ== 121\neg== 122\new== 123\nfA== 124\nfQ== 125\nfg== 126\nfw== 127\nAIA= 128\nAIE= 129\nAII= 130\nAIM= 131\nAIQ= 132\nAIU= 133\nAIY= 134\nAIc= 135\nAIg= 136\nAIk= 137\nAIo= 138\nAIs= 139\nAIw= 140\nAI0= 141\nAI4= 142\nAI8= 143\nAJA= 144\nAJE= 145\nAJI= 146\nAJM= 147\nAJQ= 148\nAJU= 149\nAJY= 150\nAJc= 151\nAJg= 152\nAJk= 153\nAJo= 154\nAJs= 155\nAJw= 156\nAJ0= 157\nAJ4= 158\nAJ8= 159\nAKA= 160\nAKE= 161\nAKI= 162\nAKM= 163\nAKQ= 164\nAKU= 165\nAKY= 166\nAKc= 167\nAKg= 168\nAKk= 169\nAKo= 170\nAKs= 171\nAKw= 172\nAK0= 173\nAK4= 174\nAK8= 175\nALA= 176\nALE= 177\nALI= 178\nALM= 179\nALQ= 180\nALU= 181\nALY= 182\nALc= 183\nALg= 184\nALk= 185\nALo= 186\nALs= 187\nALw= 188\nAL0= 189\nAL4= 190\nAL8= 191\nAMA= 192\nAME= 193\nAMI= 194\nAMM= 195\nAMQ= 196\nAMU= 197\nAMY= 198\nAMc= 199\nAMg= 200\nAMk= 201\nAMo= 202\nAMs= 203\nAMw= 204\nAM0= 205\nAM4= 206\nAM8= 207\nANA= 208\nANE= 209\nANI= 210\nANM= 211\nANQ= 212\nANU= 213\nANY= 214\nANc= 215\nANg= 216\nANk= 217\nANo= 218\nANs= 219\nANw= 220\nAN0= 221\nAN4= 222\nAN8= 223\nAOA= 224\nAOE= 225\nAOI= 226\nAOM= 227\nAOQ= 228\nAOU= 229\nAOY= 230\nAOc= 231\nAOg= 232\nAOk= 233\nAOo= 234\nAOs= 235\nAOw= 236\nAO0= 237\nAO4= 238\nAO8= 239\nAPA= 240\nAPE= 241\nAPI= 242\nAPM= 243\nAPQ= 244\nAPU= 245\nAPY= 246\nAPc= 247\nAPg= 248\nAPk= 249\nAPo= 250\nAPs= 251\nAPw= 252\nAP0= 253\nAP4= 254\nAP8= 255\nAQA= 256\nAQE= 257\nAQI= 258\nAQM= 259\nAQQ= 260\nAQU= 261\nAQY= 262\nAQc= 263\nAQg= 264\nAQk= 265\nAQo= 266\nAQs= 267\nAQw= 268\nAQ0= 269\nAQ4= 270\nAQ8= 271\nARA= 272\nARE= 273\nARI= 274\nARM= 275\nARQ= 276\nARU= 277\nARY= 278\nARc= 279\nARg= 280\nARk= 281\nARo= 282\nARs= 283\nARw= 284\nAR0= 285\nAR4= 286\nAR8= 287\nASA= 288\nASE= 289\nASI= 290\nASM= 291\nASQ= 292\nASU= 293\nASY= 294\nASc= 295\nASg= 296\nASk= 297\nASo= 298\nASs= 299\nASw= 300\nAS0= 301\nAS4= 302\nAS8= 303\nATA= 304\nATE= 305\nATI= 306\nATM= 307\nATQ= 308\nATU= 309\nATY= 310\nATc= 311\nATg= 312\nATk= 313\nATo= 314\nATs= 315\nATw= 316\nAT0= 317\nAT4= 318\nAT8= 319\nAUA= 320\nAUE= 321\nAUI= 322\nAUM= 323\nAUQ= 324\nAUU= 325\nAUY= 326\nAUc= 327\nAUg= 328\nAUk= 329\nAUo= 330\nAUs= 331\nAUw= 332\nAU0= 333\nAU4= 334\nAU8= 335\nAVA= 336\nAVE= 337\nAVI= 338\nAVM= 339\nAVQ= 340\nAVU= 341\nAVY= 342\nAVc= 343\nAVg= 344\nAVk= 345\nAVo= 346\nAVs= 347\nAVw= 348\nAV0= 349\nAV4= 350\nAV8= 351\nAWA= 352\nAWE= 353\nAWI= 354\nAWM= 355\nAWQ= 356\nAWU= 357\nAWY= 358\nAWc= 359\nAWg= 360\nAWk= 361\nAWo= 362\nAWs= 363\nAWw= 364\nAW0= 365\nAW4= 366\nAW8= 367\nAXA= 368\nAXE= 369\nAXI= 370\nAXM= 371\nAXQ= 372\nAXU= 373\nAXY= 374\nAXc= 375\nAXg= 376\nAXk= 377\nAXo= 378\nAXs= 379\nAXw= 380\nAX0= 381\nAX4= 382\nAX8= 383\nAYA= 384\nAYE= 385\nAYI= 386\nAYM= 387\nAYQ= 388\nAYU= 389\nAYY= 390\nAYc= 391\nAYg= 392\nAYk= 393\nAYo= 394\nAYs= 395\nAYw= 396\nAY0= 397\nAY4= 398\nAY8= 399\nAZA= 400\nAZE= 401\nAZI= 402\nAZM= 403\nAZQ= 404\nAZU= 405\nAZY= 406\nAZc= 407\nAZg= 408\nAZk= 409\nAZo= 410\nAZs= 411\nAZw= 412\nAZ0= 413\nAZ4= 414\nAZ8= 415\nAaA= 416\nAaE= 417\nAaI= 418\nAaM= 419\nAaQ= 420\nAaU= 421\nAaY= 422\nAac= 423\nAag= 424\nAak= 425\nAao= 426\nAas= 427\nAaw= 428\nAa0= 429\nAa4= 430\nAa8= 431\nAbA= 432\nAbE= 433\nAbI= 434\nAbM= 435\nAbQ= 436\nAbU= 437\nAbY= 438\nAbc= 439\nAbg= 440\nAbk= 441\nAbo= 442\nAbs= 443\nAbw= 444\nAb0= 445\nAb4= 446\nAb8= 447\nAcA= 448\nAcE= 449\nAcI= 450\nAcM= 451\nAcQ= 452\nAcU= 453\nAcY= 454\nAcc= 455\nAcg= 456\nAck= 457\nAco= 458\nAcs= 459\nAcw= 460\nAc0= 461\nAc4= 462\nAc8= 463\nAdA= 464\nAdE= 465\nAdI= 466\nAdM= 467\nAdQ= 468\nAdU= 469\nAdY= 470\nAdc= 471\nAdg= 472\nAdk= 473\nAdo= 474\nAds= 475\nAdw= 476\nAd0= 477\nAd4= 478\nAd8= 479\nAeA= 480\nAeE= 481\nAeI= 482\nAeM= 483\nAeQ= 484\nAeU= 485\nAeY= 486\nAec= 487\nAeg= 488\nAek= 489\nAeo= 490\nAes= 491\nAew= 492\nAe0= 493\nAe4= 494\nAe8= 495\nAfA= 496\nAfE= 497\nAfI= 498\nAfM= 499\nAfQ= 500\nAfU= 501\nAfY= 502\nAfc= 503\nAfg= 504\nAfk= 505\nAfo= 506\nAfs= 507\nAfw= 508\nAf0= 509\nAf4= 510\nAf8= 511\nAgA= 512\nALDs7paJl5xPh6ORH61iDA6pONpV0rTjGiTkLEW2JsVsRKaRiS4AGn2PTR1UZXP0vXAmRXwdSegQgWPUp3Hm3RofRcDh1SykZBLif7ulau1hVO+rhwRyKc7F8F+7LcMf/v+s73eOXUDbbI2r52wfr7skZy/IELhsC8EK6HzhACI3 124241322153253947064453752054205174382289463089695815605736438952932114700118408072544073767229325045596832952652232288773280299665950768731398747700657715829631597019676014848183966683866396215048196276450953653433516126074463193382764063985175903718735372053536664711482497859539116009770850968340298474039\nAOzgU1s6Pd2IkrJlvGND8legXTe50nyDCocI5mwT9rW0YsisY5jaaEOcu51BAq9MmXBPeVX0k/jlXwH4Pn3mCpUAU1rEOsTdcmSJp35siKliDdhTZHHdZNMW+igfXGX5OCsA/BaBcGnE6NnrGWXKyTOoVUGQLEkL2T5yhNUaCT83 166340174936369324883416612727439279977041963320514134445183426741643586944819834936989524033374309932122967866930503619179389342537723598234062828695747850043368572301869699886931403612266216965783079972698791813140295203826980649434652168563255385527187360027803388963151668338040517316899628026707657178935\nAO8hrpw+lDiJ13JahLtCb1RenupQcNd0wlTSck9OLL8wB/x6gAoj0PTLV05eZIbz43N3GUSDmmckjlxdHXiBJ9rsoB0P95l1CWIV+4rXblCqxmOdmlm6VZ13bqbI0x7l0cjeMrkmk+yJ067WqUolqQBlUWMTuJVfkxALJYH5xr/C 167923899524385316022824282304301434707626789716026029252173742527362300338760906999615029022863637963070711762128687835779073122264515776657475985362344360699359591353388569856862973447791264902182048648600267737826849280828116753682917256540180401899752566540869918949003470368970029744573140084219550547906\nQxAn7yrdVs5tlHV+Glbqdaj67c6Ni8am3bBLOL8PV5HbdrLf2xIPmNugo6MfUwFSnT+ZPJ51+VTOsItaNwCFju0Eh1cqyP3JWyLRPE7emKuo6xRhf+5ik0pTg77LEF4JXW6ofDqirpR4alFi0G2d9yImQPphsYJwYGF/nNT8u0Q= 47093316905427544098193936500644355852669366083115552072584429220248776817916430034648347490325490701471113667554329499736495877969341478442613611948220957798780043076906836236556612316544460763366275536846463456405604189392790111985912854476264292503164100482712281088955640964034295834935468665872932715332\nAI9PVzrbJUvmCihwSFans1lBKwudGEZpWWu8pkSK2zVgzGhMvUoGgMp6TG2zsUd1tV8zv7KsVD2t6pXmjT1wPUynufq97GVHI06SGpflDTt30WboYRh3DgYxvso1sOjUXpnDezcaqc2Aiz4nV5MSShkBlyBjA8z2worHDE+uXqw0 100635651531872121827765663065728398779771663753008344681972226973080394359405041113312675686974926993279775427390065833081040771269307007695807025882757371805607979134114890454059957194316765342461291139168706134406917264848659448693866813989352429841300235734400772946895458374870482441457514575059390213172\nFiinVicXOqqRLpxcGTorQpSAGeQ/PfDOuzYK9ViFtmPv6D0cYPfhUH4qXEHOejvmX+0b4lfaX8MWPVZxlqpfXiU9BhG76HJxkLF4ysipukeOvhoHzvcxE5bnhSF1i//bOSifATBLBEZInwqSVg5tHHPuuCkwTL91NqhOulp7Lsk= 15560440884463435471963622630292643727112462888414585143143739400703889930416938984547754943252935620248108237258540176511252143752416771350868493435174026287082706690332705481726295797196444796135827460509780634261726494455068460028424141500629527968240913757449787164107068039175831847071025316475940056777\naYrxyQN/hkBne2ayqo2/iDLF3DZGgk080SOMJfsj9h3Z1OfFZM7TJA+y+/O7niqatosvKrfHrAw+Qs7c6tCZ6NPwYJ4QJLOF9bqH2u2a3fkI954voNUctlUagYUJsZXV8hdhLM6NwUyIZ3ZFkPcpTZp7nKQQ84tr1a8VjDIT5/o= 74114640794666001532816944350975062126079079113921109750255283189037502412929005615388097912507598112836936032143435813588205939470002911374442844578739574773399427907766548612582213272643279263782396527705126350063372192910060171635870872236876399794128383338399728947176692692942605589343038282957050865658\nAMpCUeKUX/vtRslWiUUuXNl1KA9uDAWjMUkTrdsxxRDESI7iZIn3TR9lW+0kV5fzkLF18iYLAwSGBmX1PS/T0UVFmoBPJ9yS7yktNL0lpQ3noyGFn8HHZ6XB3FkH3jegIfGbvwwhnhhFzpHPrXlpO5iU5Y+rexzp2XHWt4yJGuIL 142031143422642739313498629438991149460874309300342349421794421544918823888598660275343727563280565210534243383322796489809683834300630555650331646026843796764549231159336347965502383849513994449309613369541991287590422095953275586374371960367000083487965487661436037637475372929033613295072397262739084075531\nAIMIQVz0JIEKEI+PREu94m3v9XoiU/Q0CpsSuqkwSSje+Wyul5ea9oU5qgtOpdkMUOW91BJo0DW/GMZ8v3C4qyyP29TtjCcAHObJi9hfLSlnTSuzXZnDStooYYKqzfToLToCaAJKCXiXAVW0vWtapLnyqafrf/KgyGZ5u4HfXKY0 92013973253053602863003242446596060337454881568126916916519869242232429836082762281129448384605359749247852792606718908482332975424967542242332487707042773885428473061056052851768940900752317020681189773407893371297668591494665352294885305475871917069040377145530889271334616499701769138948975263435137525300\nANfP+zPBTR27afneyac1KJhOB5Pq3AXB+SoAXJvQI/GkSoNhw5KdfqoIkLcoJi8wClCm424Gm1AdrdGwDFOM/iKTSPkrvMag93+b2EbQGX66/n2X3YRFNkgq/Gtb+2M8oCcAL054Z/iiMD67aU5RWzjqS64ePHsn01bJ7dqLgpMO 151548639867177154896951257541227014781655576169318283047778755573323724856619156348444192550664853912434681577093459933599575436686424046466113215132845213008587152894642577278656978304699131916299275797578171518984206145555369576872231567191579337901913492071976578289189524123204040497290426960375042970382\nAK0kHtacLGu1NFWMADq2rG8hpzM4UEYyPOL+aMJbnwXcUYptRIxb0YFZg35RN/RiZs4lQsiq+kEJKzMMV71TsJq59vMkIZhZoB3t8g9ZqBZuq0JYcTICDwRpNSttJidVpJ6P9sR3s1xPMYKdlSwt6EEc9htOXfZU+yHKYgn98X60 121583812047864398969816595368193171848971298823388059338224714026742264861090347096116404814514279627148994345584790617974476594451626305761040465570524035369799925437276511604752129817947910677564301623631349399504187314174538914591944778074509068973226322566160587813128746039859381466427380402262866230964\nW3sZlWW1Aev3x/DiH9MzwCAZzBj++x9cknLfGAHwgFqkLH6vimEH/r8hi85hzlCOG5CjwhoZ0D/Hsfr26ZJ3X4chG84byrfDnek1V9mm1++v+clJvlYgcuVgn2Opsba2TILTm1MDB+Ujs9brJ2AAKrE9+ep5nvtQVeG9PUGtdlo= 64240043913835461386212515483198059541440539167395859777194837833769712010594411295323900074066077107346806786205590345517755715510695858065925747020336398305793661773798243627926904542715123849691490667964262778804487343218972081260210371192903128886030021862362141928329650003493687310970684093289133340250\nFTQRk9/BIj21gbLwI22fHJWYj+8Ghdcc613hOtJ+/hQmh73HwTXLpaGK9aCptxVbpjW0r/bxaRjmgxu9u1CCZh5yRd7Z46Wk/LIPXGd3ycQzqRMFB7TISFQGJIcFoxRp3Eb5wa2OyrUg7c/D+kb7oFJq9P7mEwIh8TpLzwmu4SU= 14889529068556301710329043521845510156960298822469914567758538023025100741826628180855835334285179977296740667353391766487166458692144569279381035582718738461626140662441222061900764829681913534146898551570916312642104487829660946024590782808750587095559047648957238487820069966851521487428624726655438348581\nAPYXO6uGvs9qWiEAkcWsaCaCrGJJCP2Z1g++XlJ67oZIgEoWITn3T/R2/c4edAfwUUzNHAYZN1h2dSrRoqlrRXrbxFtGOuRCUrXcGLFFcEbTrtm+z5z8xGRfcorx7Cu3FIBPMK5XcGPcbRZdyP1gBkeDMvuBNAo0/To+LP/dhCNM 172810804474418448604443090732221483571611665465870520701624598983692130272337358406272727413570938793741430131635927237320173996217984860203754686741782921346604605228620148450611724714868551781003004492587584071978757421616871762681049508123223983431502852926521520561941051298696758046005573332373854233420\nAIDNxhnDEe1kTJ3XGfTS8zKXeXPRdw5yifm8j8Ibzj/quExy7hFPtKct8hRskPR2qwTlMiW9Ra8Npg2USsqHV0rBoIkX7E3psxq5LBfp/q00l3SEBuLL4K2FGR87bPgU+Duk3RVrNMnColiTcnAR5XkoeWhn/r9MfJMIN9Y0FEh8 90449107125498302548188660544012777357148118984122992664008792590422284061463729084479315745509706793674355738023180454297730948397413371686013210006834869294564190666543874617716180411178090109573192518129248278410216362657350215009192850017507998797754539132540293137589672869131300859207213449571846080636\nAIRWavxYRsGlH0Yr0DudwrpYtbrByf9ZsDawKom7ubiRfepqYzcBlwt4adMMnkYSaXeYtOsD4KBm2ZvLKN3++RkYNmxgkyarORBEg7ERyiThBj7Ksw57pNHCAoHtBEhH7Wp9mHhuZtPvPgCEptmwCu9rYhLt4zZp+Zq8a02dkXvM 92930601962515884925250459851491509622611227724602941760145671636277317511265759558869239180653492283311584982044597979173761619470326096725838197524704577188104121460089235709339932110663536557497112887112782062772810759971739760085128369628777812332518137107605855679096146402427144185104230596200130247628\nAMNJGLcAiJtL5fUfkesWKYJurdYSnvsOZeZcrg7bemkEVjF6S9CcojimUl+ncr/YY5/EXnU0mg84fObtDxWWdJ7z7l0CFcoALTyEatDYKshT0xvdKY3u+LUShxIAyk8EcGnf+KoEaa4Mx3tg2oTBnVegXClOakNTWw8bu2ItagoQ 137134165107366719462230252606689766470445826753581409513106273517221906418464863733870948759313279128624638614534848890858250894834883265387344539280755177217350585564186248554307335197387734431939154077778003706720017441895613190141376534460438929588407764609772857975000507660651583780079804513519571438096\nBmGPZt8XqqI1PuLN4K1/PZMi2rfOYtHEMrcwZdSjKRm5qTkd0Pbb/5zPV07TnM0uLRvIQYTLloEY+GYyn0K5gDTEZpEtQ8ee6Y87zYGDwcf20eqYNxkA7FVV71vqCP/Uw3Oi6B+hMvsWZbvv2vH6MkAeADCrezOtwqVS+irftyc= 4480956865245875120472829476982311611308898564405318773810939350829150182630548948231116574193987272498161864310429976564278532538229396846813874244969927890037756969704618336242255039858182439641759659872128285423988638335967412040624105824571426792562334458751137508116412821914961236269913776304372561703\nAPqFgCIYbJWuRyEGuOStPvcprj2PILQ0JpgwQ2jLKn3DvkWSd83qh7PWGKozGavsjh803K+ZzI7P2wP+Nc0r0El3q4nzaHvKaCtVRyMwbXv9wYLFZICeM6J1l9ljUMts4tbDoPzkIy3ScU7pYxarBWqMkcBU8qL6NN1vEdkeu0fW 175922170410080716883576123079908758276229469783745771772401183721225804343343063277676406040455068452258961299511343441961963941297631097736305638850193978800615558067791016294285848963023036905095022181004058235239390870177623185946205281141386416867569004073524130001309977475780893497185890756991672600534\nAPA/rCcGeH6A+6ZwaBBDM6mB6tTD8mjkrOWEo/pK3MCZ+rrErMBnFp2S19GhlLOfuY8BHS+D834Fdm8+3wKYkWnXZpGb+e3v8ofOQ34G1HvzULOYtrEiC4ISZRt2SSyz2hU+PBXjVnplsHWTRxZDmBxTJdgli4ItAqxGCxj/aJ9m 168708388929747822981923386197903561880341990893945097067702518857172133291360611402092714329372304718329568897960770488377524912057166920574319430820488930520807742026377043178502591886293565177404635365772829346030773275726024973460121300339258054215286249329967181244588558220467488638468686270735376228198\nAKmwrLP108dCGWOWxE/6woJVLRi/Kra/DvdsPkkrZQmWIlUT7IvwM4gU6bUr4f6wpT08cIQls2cGh7dbSEaO0xLa3mmtKhPiAlzSnz0wuifF3JT9U3uXgUfCZuFtE0z7Oi7WTOrpl3k3GA7JFvXnY0lwblIQALVf6oWyNETnajGl 119160465301384937485959146028591622947513292915838943629387700439301197965652871741710280647524383590817798553034250156068573474278225305190573334054718387045488098320076877626430189054572361967283632592181431701411266656256255758079114072932140551282607247364388070762970060420036793573956057551235306893733\nVTe3rCzAL1Sljo3QAXEkAdBy1ZARHZwtrj6ZNRa5ttqd6/l21g4z3iHCeGo4rnE2F8wYTy+NlugjXw86OS+XojW5y6UzTtx0HX5IJ4POqN64aXWmaklGzroBEYWeuFFKcgQN3NOxkuJoDQ6VElP7Epz69kj5CsKJUwL0SjbNrFY= 59841866347633473702601462509811342285929528424012250265905695635971518533504187799047710303717472950129869674786231155102509311322791323986824635569605105662070745033595366004805920086888891275288347907772640070278731650628917037915863439204501060041944275512863990729926528905725569467329169134226609384534\nAIZt1xGhC/HrvpPASsvVIVdsu//tn0noyJmVYh3FdQ6yIh1uce47iCsrV1yvYqx5ZTbC0vnfnbjFcWqH+HtLX/DelgvhEwzqJ8hwQrfE1ShLG4ZjAVo1Z4GCjrDcEUMlwKcunuSJssuxeQuXwTLS92+q6QeBSS7OmfxPX29CLb4B 94399298271083745508290936113986978382457275531684761701599029877008571741877683365769553170771833981099580359640421358853566501815723434822307977440496208486103754978934472597505865596938563438311337045817621762649604204720249750058676095769230214181772215323235427976398686727606000594646472236822594174465\nNIhTPpWXS82VTA0LTd6TfM+HgLgUcmvnMYtLqPpuqCKZwalAycwl0XFYNyVvaY21J94j92ts/lRYgVtHDhk7/9nLXq5js/lsUnG8rWPHJo11JTxvW+df88aX0pw8u+biOWt87vc1MW1dsMTTsJFJAeBx77qU/Cwto95IVqM7vSE= 36889590210230649939994518345793530042252563793069578097360569338647730438860274349862767107939590441616825589851005429465345268710487649366046960918184701290985280638488938340668212498212581853679035928093386035688597446809895381618260692378376844452061580510108168030682664507293277674052032318576713776417\nKXdi4A2Z7tSiiX9YGtDtxUXIfQvPhcc48rUH+Q2SnXL7fLNmr+F4Rf3RiFBRiHKocPfE94pothop5qQJ5X221/DbEKWK6s+ChfQ636jvRxojoLMab3dPtaAPpDJHrfZMxbT4ZaDJT0tpA2e+zZrzBuDs+UUgCpty9nxtdm1gS7A= 29118662951481660380477444121362422614202367719725087486810943918529894738076273660245405874301505615796632229852040910511025841576465052938308369421493312085081188509808322692130449282585522349552501983296872614029139293444558468751646868108213623606366977549477663987815308260383403466635254115908032940976\nAIOTBZQR2EJJRmoWdRNFLG4fceoS3KnRTHRpPdllhHODqdg+QxTOcOvqIzBqgdD0JgO12SuNAjLQOiz0jhd02qkXw9Y1adGuKvL97ARFtNEuJiNzFAj7KpDLy2zk2rPJp4Lp7cjQs0fe8BQYnTzTsNRGm+4ybln/gse1YWu9w8y5 92394618277596007469808288231093678404089765494062813665106014405059399079199990128824492247005602685377185496959522609467906358619318009231448503013528692450191782140091818984176967246749464502089280153086163239846744554575017530385347720563798041108608545014076448155956762636929707905789978331102411214009\nNzfbJRBF4pqEeborJrjoknJgpfK+DZh2k9cE5dcElMPZ2Zn9im7desWGiBSQnu3KbTO4L/t4+m6nFTNcbIJnqbVSMDHdsfG72rG/t89aOuECQw0HMVVgONNNa6i/mw0jZSWnRLD4fa1YgbUlMd8jeqO9XcBDB4mVtDTxyeGa3vU= 38775530011374537813502898274019389132620116890266344603221997943675706375698597061571989090674289834838060050848545748579361837989319487970580969082824601965845786771062335733318139530316825802589479118956745739691326447349403950997231306042638797277408335778415717988679050762936401945487285814799382535925\nY4BVPZ6necuLSwaqYEPeZp0lt9tTGFl/WCJJbwg7XpyvuwYKtzagC1NLzY5ymBfwGFw1yRlQuyGsYd9mBfC99DuVCIeh0JNrhJN1bNfoSzy5UO5+dmTr+dm66VGSRS0tFcViDTfCIleTV+zxo/xuZT+Bjxq7kZue8zGkjp42Kmo= 69872189501616471647606976308259279995249122669120675885925763529037695584466011511740991152346215507625265226811128801733353566555339153627478941716586678793853828514394269931890370517258825006937741437480128878717892485074131232336852490940507703859793477547154689914725314529986438108117871674332626168426\nAKCP9Mto4q/a2xNqM4N7PekbKspwt48OGPre+iqVwPrSP/jWKxg3CvvLNZzN5P+/FiUGIklMMFJ8w76OaHIPqKuwckj1gvCLECJEE+UAZWrNKPmpzd/ootN9/kQhNMuloTFCyhXAUUOXJ7Z0WVLb2u6fn4zroszSMBoWQEKC6lcq 112750701794692134675959811050012620191158543234019977304167102486465198271340022889272244811582365901584420008564301920174477182946432553537794834985703732129975734658113610563794129371053853971031300761815004524681756388784922001759202643614966614186697992611399618828963452661554240362943588548146868410154\nAPOTAFA2waoAODECaGNgCHa8dNN+cjMnD01M+IeQFytzo9RLMzzzg/gpTUFpyLtFMcfbCkDYQMLXwE4crTimdz5sVvjGQ+5fSFQjoDY6Bw7MO6NAcLzlV/sI/1WyNBKaLQbcl2720n16tdUcdckQNnV+cC2J48CVxYM1c7QQlxA0 171043636512232272455501595416608280460445723238023572475354665686544174728784633443479486247342724860289312593374524429736857970220153680852977711594899595712511352458264354251161579203922747468321999465061463474727943140910084880926005209538535217464825087114791420210981711903880998556269523363208766099508\nAMGpxRlB8WVnsGqyyiy3/mzrPymtJW1o1HcDErK11ZwQV5PwTF3c0THwlnxDmcziLWHSWgPQwfRddVDCXMGW9BffJn+XO6aTcWDPmDAh+1DbWJPE1aqApGbHvQ8HONy90dQMZf1ayuwceWCVTuU1wnHdo9F/sIsRbuu7ic2OJDzY 135994898408425255747055209966103741651849229328236418804928584233229830656742052333413774490626915784901255640138520158698845938184666683995579777154437927013722740366497459963753542029774185193376253885864514386760437194444013834088425088260658670140534670789371556026135595577395047002643901630053097946328\nAJAw4uDYdSYkOrjtwJVWLv3pi1+UxWge4RmkWKqVquTsAVcT2tRZ+MFdHM457Hl7fmFIyxvGZQy4c2v1AbHEfPR8ID2sCRQpdcfrxEUZPMDqxfnHHm0ziny6W4X6ggdBzMp/sBWaVNTBL0e61/pELBGYNRGFMzGws7HQkr/sro1D 101254336834199527040756567675327011562230719161388328289463594628690618298993695452746353237675715087353241661592074446889034411683413957950360025295995263477031608845241728493807755308798509893719674568267846671753070163272328014412744008880395248474446310603301447848026040555910147467745595720879397834051\nAM09TdtXgYL4FI5CGNiVjf0T/AN/pZ5zZsBOi1MAUKMURiXnc1x8VKYTqM9Xb86mqNBBqphynIQG6/3e/YbGJgHlsSdrmKbo+P9daMr02I/7Z76/7Osa8+7Ky6lhVCbU3F0tBH4WvopkCQmuJ267afgvDD5kB+9uNr28deMH00cY 144124056591600568767398029380314564902309327093641173350205276895603332085753288682409279238417493662029954512382520307259348748813767324609446500382301421328754981718014234615523158887865271179104711373675849713359713282937065993613915015084108700238420759344034475478243507306107546245540340758766909867800\nAKDhK+/BKGXbrbBh2vM61OP8LN81YwlJKe68KNwUu4tjXlQg7i49Jis7QKPI/YFPUpSNTu5N2iCgeMnCX4+r3NAfivOao9lw4N3nc9bi839SIWdlokhwBHBYmCIgjehUeBAdkU4jKqlE06pIrpRmSvBtn7O4aWTbT+C++ViYAcGF 112973480670453665543892521898882856059335781900313607790238402438320486344365203510769919022496690291280873287383392088872774202832124927485754495093552572232234532821756395965072330282810574669371524103814871172318519695921477775100282448247625395376072233777533359104085023946019406729587713120941266551173\nALxDiSxHjfxvP8ETvpE+SyDPTS7q3o3zCK519WTepygC58KSRfvDnIVIyV3toQKzgqD50kF1Ni5D/wuaSs62y3zg3kELX1g+WuBCc8+x50+kDtbHXa1Me3et/OqVS/QeppkcjK1UZMU29fXze6P/w6aQfvKQkE7koeQtZBKkYc0p 132203344567902304830160099595561253300484092355345272411265169562971473393256361094745618829297250316196312398486598077249124198329075791740755862221465178128527292695331061023291345396067863215552021206609309872689233899464919108147533679134727064586730810633196817136739658243232643507412032417747255282985\nVF0YUTvy8Mfi5o6X06DEvLm87r72mAtTdyyLNr0/GXlk0Xj3L2Oi2bVUMtcXQNRXg/mkdGP88pgdaP/eMzqkUU++vJ7t3UgOC1i3SHegpiBhhZh+aZHH/wjFV8Mz2XZB5z8MpMgN+QwALK1TT2Pyt/feQTsOy0imVanB5+OvCeQ= 59242171319056188000481457618922567543461456096441095927600135114274111606802456239311634638536207588762066940095527920532936960549439269891703098017342732142860571277442598349453761561189719823290643146391349978698217357430495238876700400634593256155537598291759795109752990651995982467695091946768443574756\nezpwBt0N6QhTusiPcKrBvSB6yuk/KShTLUFQHdf5J1u1fgDYrp+aOWuXOFVfOd0bweiG4UxBQNXB2IDFWfYON0fBoaDqNk/41YyqXBSkKbiNWLi1y3zPmwTAiwK0PzYp2EPfk/t/j0HsDbvebu0ygcxb2tPqj4EQ1TXEdD007kU= 86533835313999945727720083706940213467453975054116752898416709637030456504024135513972566184073843025739226187558143854850980654667596935003124034699919861200483994576288766702308068265526535622439762454501169018136389983894783905946543636163866717367545972667876983557989192393479830223914708619684891389509\nU8BT26zT46tTZnkmTNxGUAlXbJhk5cNi4AMSd8fSvZHm55siMFGJ8Jl7mtdzEFR1UFAyEztf2fUhxdtMLe8ei/OJgM0j7myQ9STucEwnsShT7QS/DjBmfvcC42sl1CRpXkb0ZLrEJCPf+crtLKGrG7ExS1oawIAgALBiMQIL6mE= 58812148564290791415180898639607206220554150794356494356250223429674091688305329629529905854147200457536549527135776329004085047145097927266797668252160196098870200925284256433894773392353678965699083286106628662506590268955650280670838340651598082083455821825076016227525614626726458235627297885815646710369\nHfYii3U1SIkBZl09RHaGGA7H3np+qxyNeeCNY07PDl8LwZAaaYk/bHPeBVboan0I2X4o78zCD/gFXFBJ4rxwwUsVjHEioyO2JcpV2/oDOelJBD//78WzBMMSWt7ZKbJV9uYr9ZUM0BUD3fsk1esFCEdnDJdr86U0UMmiig2R+ME= 21039655953870571289679214995029926285040274249531458675115179004718812090027267801012507748013357317597416722235988917212676802092082137617336199787762782958420742299451435320649616271885264333948336627286638368859041172783505464468640994920853000441536629081040963398001710173320125308624362209157720438977\nAICOlee3daFyqTrTdtWjVb5M2rclh9BpIo1CRvKo2bF7NYcjrU0/VvbOnTVXDwdeGMLupbi76f0BrfDxYtkzMXvIZlgoTit4g5ennnklDHFBC5cogaGlri8U28w4/h5oMunZ1O4ezdpRgVJe9nTP/sSEMYiNS5IA7Zshdvm/XccF 90275777798511290102824338787811725003177532250296755103300529948194832904403489332420505850668003332750291879153080212231952155092379375422537931240723308384652734942204313672973885652497290433943089371705605128843469306776615573873479312715317072986990219294942040272550822460408702072075001377245051602693\nL0QUSVIjxvE201b1ztRZyOOxy8vkUz6626TH4tbLwXjjc+AhmrvplaVlavnOgHqve+/L18XNuAYP4BqdxIcWTx+yxBKm4ZS92dRJdcAtccvZpEJtYjdJvI6qbL5Ph6HluaVZwp4dyFyXuZOJGTfYdTb7PUWM0jNT/xsqyjxSQ2U= 33191267986826803728285073844005357792766429917696698533494382218509532051029343127452480789088572904364699220151221680328978554239767633887572649589456766209242252549993823283929686430100804479376247660556781589549613316880150951333982646510273364068770923588389668733632648346075516618646974067295703417701\nAPlD9ECKJuACUmQUsbd2GTOpb2PgQVT08C/5hyNEVdA5bWoICX7epmoCKCybdolk+cfEBP6fSz33j+Vn8MbeiHBLdmF6ETbmcyOjldJ902MDvU8dqAa8IgEZN5Uh5x/xzN+3dqk9o0ji7yi291u90rpfIh85PPpDat2B4l5zs9i5 175040148659257809883308984693597046378367187659749953472629929701758633206586720399909808941145946314755491399962797299295431089674294356220216615950668954164397362123668926410543898553191541662075745481299747832013627018846822876386760538344447600390187421938699064459451308870669878673306013635576901916857\nKB7N0tE+A5vFhyrd/m6Qe1wTihkjqmBn+rinfmMAzRlvtxIBSyDLzQsOQs7L4oTG64ABU+YwcWVijvoeZNamaxGl4hatAH1pRqmC/r8FMvC4vqiFTbFHzQhkjM7uoHD1aKnxyBVgjMj0E0KZjrRxydZjIR2p13FXjLP3UQSFtII= 28173452509830313810392326357601136401754938805266458365469366750775669869895498658593356375710132149836430968810246171974040975430205200958564616924399794768861923079158311829444850822144940112488994119845741191519421434257276977333662656888696213514226866147767570046232093727585815615828360199830275208322\nbxFgV7eXwnbQScl4VzS3RTdcMW+NY6pcGkT1UsqHIeDVyBb8DnH/2/Z+DX3zniR1iW6FPdvhJJeQyPIax1ohILa11R27C1TLxGvTrRBGUycxjEcBIxamHveBsXbECWusYLEakeSDg9x4BTWMz1rTQajkorBoeEjYuW+xBxQtXME= 77994515143740690952370766995249847650881300682406161400195705464876513409097078624084133111941171517535435606295232558665316819077765607639545069239931096306624817379462598756505457054433358548941076472902905065316335603665413114267741896000877284610377452471067725794013283338924419969559537339967562669249\nAOH6E2eBzD76QdTJ6QbR/7OeF7AagUif9pEYx7fMqrIsXCJKKpLV/RHIItCDYP2WO4URCaVueoAJe3M/Shj4o6efvH9pf5Q8MLM0rn5MTHWhThivqYQDwjCp1ZsPgq1VFS+gcnmwgHhj2W7XzJxiNPeRXlxI2vL+XTT/wPBYhqEP 158686346608862569574095184731081143351413141116869402750758091813874232272198082464382169470744476593016502816563462778075467588097653320101723165887488327616477297401486647183409348122990505635004320879840358339260797834264972100385692477324858942142372580281421734058008608134075577990829273447077276721423\nANDDgNXOB/rXwmS4KEjiHj7RCDocVrMv5SU0aw6AJzNTBfseFngqidXx2AJKOEeG7RDDN2gzn4K4qJktF0AIPG2JbELlLUu0MFlpOLxamp586qyp67Cl9OuPq3UZTyQhIsSIE3VQkvxuQkGsaV1owDV3BKIWQbQEqMQI3yT4ELHm 146598844784260148346676185962272439320781765598895126402049215152385925250917998794921584290777625240122575975327405909800121511343265147922400813488099624745229653124857224399973509428158163452130086943873214460600035260925149630502192183407327427517292065083168010281295559088633086659209316582810260124134\nVprr6oBnWuxIzyTZjuxlKSdZhBc0upeNBHVIlXpQEnN1Q+XURKzp4/6Vg/koITftr3SMSgGpE7LkrERMGFgYaqM5XZ1RXYFKT9dRJnz9VRDITVZtdkDrU04bqo2Ur+jvZhvg/oHBDTgQ4nPLJfHO3+GEmUtck+g/wOVozMMgufY= 60816213163057201559480662231646403262735082707152897397414589876256824040344252799972529759737904461369360580708093117244392116003622336721789703580184437841209963565058475060017600871779929808204093448248984201640754565635410002090180110910120481044515630478472999135146756643143415057403006410330361346550\ndo4LGsm0afQLHl9alWF2RVyEKPxLIErsf4pTPgScRE7ZiTSVErbCDeyzd/KHzhBLQs/DhHHcw+OXj541cIRm6jaLVKiT8EwLW/dVG0AkVli83sFh2f56Kk+bCGSKvfGEQcGLY2k7nQ06zoMlYR/xbZCka6Q6kSq4YBDQgigQ1lU= 83252051731120517035090523892596419800592471447735288551342681962005778435125655090199060145942826521644585427683714084736143440310518046334877897672493531918539106001203807757254797471481884534543367685912500572052457610702790097953420236852480969038388056545966568595395722585797418296411673622376893961813\nOL2Qoj4xkqRrQmuuLwrABG3BMMBNGjfBtVBNTdBf7g027Ghkk/z3aK3jKT1EPpdiOdn8zXYBSO1mTRGyK3n7Jo8ICOcnlBOF6cZtDsb9bvSVE26MOD2wzl6irU7vzS+s3vGBkN3AazrxPD4czk3xezA9y13DJVnNzgAgIQHEols= 39844525812817530522650122383059885756573694015271773938493414420875846359054562126060762455794481186614035892021706051863945033061233991184379580556219478200155757966121832613842937722944431875100059046588723473670448006803481527981834627086055642349130254917244469014754132003347635357123155857820000494171\nLjgn+3Hcg5DOf6usRumk7P+ZrdTBRmo968HdZU1mS7LwLW3Hii2KNkwMV7J77zA0P1pnvhMSEEeh1RbCUjLtSIbt3RIcOEoc+aO0eINF8r99l83xF57CBI3MDA3AAbtaYATy/NUXSC2h4W5kdsQuR88139MFi5y8E5njqxHu3UI= 32456338403763561215581247445990611953939298888251578685087656354454727113846722731945605696397627662593375001096230320486703167389461057538581895745078593206660798580358701927596287363374862536765135996838944212622199018632046955402325290145163082309469649329852148345837780541107029165352782710901375425858\nAMt5/u+ZUNm+Xsucr4RQPUu6ExAOq/Jbcjm/Kb2YIAaEQ1czIL82wsu6YmpHcfMaxLjY+EnaaF+eCWQPeGd1av919+QFbQPeh5DT7ZT9klK7BFyVsN0nEDJQ3AMMJqq6lm4sUeVxDVTmMypYnkzRl7jqzyCRY1MHA+o2LyMECdOg 142886089970163885609957244378225169093559131065687633458877059657380607541767850701139140472705242750285722732461954100519608059127637509286558848391554697942686619832870045594188204522385787253648018847569919409782188708374165437385572046835539379151066214153911415525465041951116179326632238059135825466272\nAMvXeHCaa+zk5VdB27KoS8XpjSUngaw7Gwlq6e2RrkEOxBhU2rGWGJ3fhq1HBNRxDf0quqfYTMd1speisaEr3cIyx9BhYwB6A+Nex/Sf9DSixezhcgEz6c5CfwUYP0QTTOiZDqzz+GcjKikjN7DKJTO0WSXMRG8qX8FBbH0rlc9l 143142496664357119491819741364830737485524654099662921673419335301323845847085335210884201567922636945282124120681371777665458057821603161276185071778040317947168899788341482064834489328957963447735297898161379277478278414388733161844053774747425459239004132791029364174047523473372650441001639174571312926565\nAMxoMXHfE2i4khsAkv/lPtLQhbWUjP3kxYmlJkpacpicBB6z/TmG5zjmTC/sqzBvBn3J4UvMzKYFyk9/l7Wnuc480500a3S4HRVtMtirPueV8v/SPktL67eN2zoj1VZA/Rex0aRGjW2CzEKGwEn3G2bZSgdT8hKv7AypF69ppjz6 143539479941314279463880342636704987025205547180882175105616955926182352311179043850344463145750154442573797875223178075233807385237935671604701513551125937539235111702655902037518920150424691586943553275517626347557879039695678271564616114192941679606063184290901862703975921261779714258077775731727612132602\nODvOKg7l9RCn5CePG1FfMitkR5l9+7JK67eU+WeA5p1YXCcKS8GbYAKCtXPD2QfxmQcrNYfAc6Yb/kksaq29oW7MzZuTDzK0HXY5xBc/fJzEuvU51gaI0PR3cuU1qRlLqwmIlyt16gto+2E64BgPgIKJcAjx+TfH/EqNeJ77/W4= 39488587053253042573878502921384752550143716864908041972426777545317969264945056510991363961916339225192727727267483337259701961148978214005913510275048195308792987888118270387288989623193626554910652030960235845935461155296845475356011099372367616732243132816329531758943935324760665826550992788664237161838\nAKkznyQtB+PGvbVroM5nUIzhJUjiNj7q4fC9sSFbmDgvehnwPElVlie6PimH2FKonGV4GSaxZ+osil+9omfkb4rO3pq8fy5KcFSw/gs09X/U2eEEcUt/4oSbjs2NaMIxQftM2CauULiwfkWdkMFTBkHnh7Bbyocc8dtmrBDdoI8a 118817437232756222334188081193205110010964766506378146125932730686679941224328135190204402802650523704343176483564284220367074983943319572348376466341132480772885833789613392397284313483009178508647973749522358005819092779831781339778163122774381387989185969990310049504391258988402795259963134610905036263194\nAJfwWA7XnYbTjlJt+9hO/Q/OubHkUkyMYrN6Jd0cN5MG9Rg8W3i8U6oJxT18p4XozkiOgPlF1lE7hIAW9KRKJKGTue+iw0okLq5UNMu2Ha6l5/wzKi0QzRVTBnQm2zjPlQpgUorBBty5mcbt/B/Y3vOE4I3iVXklVtjQ7zIBHaNK 106695084438708194568048926154027115609888551145480521213711726807296356271397749432698558860759334362315257102647885062353922543502466463770991058956633500180245599467233361812610650830611712448187310827443315947425061886163301613989593906515923245020641415290300558869209909418659128196109640872398602216266\naCXItk5XhuNrbrqJr1Qm04U4y4AzSKDMms11PgVcdf5fCGdizibh6/oZqx5OitM26nRz2vob8F+ZIP0CIyIJU0T1M50dVTbbpwuVNdv/XI6gHekQt0d2g34x1TQJIcsT1VWwGWTPNMtht1hezBAYxwv105AGKnqdLiz04YAdEk0= 73134927546833985031652237686088635686032103401394612286045377544136784429757461671691980910279873140130943470029643791712859175007885735170485461366406852784845528918253441791024065848540598601036357817496637108534035807393364939272891745520961269029038360205258229770737579266643408540634722493263322616397\nAPNeoaWlyNa554OtHP8F7GAY5V9F7LMoF2ssg5wBmsgGFktrRH1C4FdyD0COrzIb0Vcko1/HiTnA9JXlfGKc3gTHEnO0gxBSDjK41L+EIgUfR0EhAD9iftTaCoBM7qZN3R1MYrSz3sevQZNMFOOnRrzwWEXnJaPKAZXvsqPzOIF9 170899982929163229592439208307232242235219591108657660041403142612622997092685093132858257827585941687488772925553142105567685213341947938835403410054637382864108739466539574004149772568683507025358331323655651148107044968424043673850583150424463706583215452211942132017052425497789362680979074312857823248765\nALhwBfBYpOk1pfJcNut0C2fEAd4hhYU03/ZQBqVe/7MgpEDjro7oMvSdba5kjH/VBssmZVqpvuZ5lG+vI9lXLukhwRKJg7m67HG8lZXvjDmjU/PCjxBPNt5r8/DziETYmMa+fhaMTw4hedZcwDe37t1VPIflvM94sBKu6be9yJAn 129516480651398210587505113546142851617282590236388547627336279692965778911450075230961856270046942312918567973875005814982283590898552829322178788678196583244198944578081007477482775130405341039067711963061287597331433268366003672643052056973656674139309732186091974604170508497340243515339072325943686631463\nc9vpoiZvtnj71b8XguD67WayOF57QgOX4V4L++nG2u/RY9VT2+0tJ/C4NIawVa7ScQZAPVLuhV4J50HJX7FZgtY5n+lwMzNo0av7i0IqTS+1BBO8eNJy2wkCbWWBxNybuNnF6OK7eXdPb2Mmwm2OmhN2/j7HAr0cD7rK/Hnif7I= 81358980280155473712258342299472964374474635149963153129588784719499494479288254287754874893180126149146558961101860327826747785201363745989346818037655063262173536227595206355647880155693272153902647256175878517626925488264893732295267833614283963802283320574654949992393798458265266551024756663538388467634\nAPArEXNLzDydcHrieLDReJryWxFzcsN1dxjpJIVGeJp6itsJOrUtnmXVnETtaZhWsmN3/Zh0R7TgJ253f7PZ/Z2xCEdqF0hs2MmnERSywdWZQ0a0McbDUUaDjBNYFht1wvS6djbI1b8RfayrnEZ0miYdzrrP1ntU+5cM1QBAvj6T 168651870043094856205824264282870999215855903395882323164614939540734011037112413507417141209480771157672307388419164831992909066097194364645695794831939514470650008210390333649278806163193463937050083854756730458780288720541495880958909249273048328511615821480782977316719631334570687241232556472064072892051\nRhGyx6xibf0OvY1XjnmX5na3G7emG8PWbvEa1kIjR6pK6K1MrMZnxFefXpHWInFS7ADESNI9LHjZB8VW5QrjRVPMksgdEAlkhY7MyQxaclUlShFl2AfKYBfIIro+vg7mUMzMctD+07BLk+jejRHtPVIxHmNnZrZYds80ve5z3Xw= 49204219353786910100605282012781696579642953908541693903348594981245301165936599174304121350092894937817100350990938057159324959104937469442065996667276651025661016077514839755853073999975805394464570132481314896694678249282338429544941873047382467276103868995474424700207571657816852575364781281563515280764\nAKbFfU3GL6NILVyONPVD/X0tffk5HS//7FBp7n6JKMXu3VXvWnfTl32R0WyVHk2yP0iIyi6SUusSicOH9ncO8KJHmaoMGN9Fn+Zq94FTFqZne5NxHmCtwRAbFNDVGg4FeemGXEe1S5Kk1VcvWqnp+QgY0uwa7RtT8C7/T+1pZlwq 117110890075563714812929271250884717870581483065920538069845585667296154465072587148155060755111295509684258790280104272121160614620669593483929827848744548171793187278583947500205314283462739235860439216105116687015890394925743036369717346234391524403038196640934551590543386844279091801685432977718405127210\nAJ0xZ9dfRc6P4W31bMHBymgOq+38ETEIMvMtr+wB5WTcsquZY1IUB4IVkrHaOo3W2SIr479IfJOOQhmvyRS4iB05yDI88Z/fJfXarkH53gDivECuo+5+JmV7e0S6gCvOuVamwoQjlK3G32bCV2946ry4EyIsVZ6Alk9xk7X5HfGU 110384671994603894282707302829898242894456931176497230904862171369974466400767175784681299142670706023468915238955836087425993929524341269289746060546848852729416925808186253355106621584826213979718185296723694190658548757311188764342751280681935289121682174507629679900374674992438818324999211250580434317716\nfjzmb1D+YBU5Wn1GlwhxjiJS07k+fXxjeNRbOv5SjktzxOXmautO8xZ5ACOlYrTt5G2gzW2PU6sYNfByQ0xoUSyutOuQlD2r+8MnDrxCo6RxT3P0dUSX7q0IVj+oLK4GPbscnKLfe6KqUcYLMgKnDYnc+ztFD+csL6BQnM9WMLk= 88647261832601702291191332432291274285041869480562430895152086741320122435409959711452438332192792226899741738806447713240934608106883094466050154088410020909933636902495700779087737304255058561688767369900548260278700135161077055869478387490726087630962098228537973426295306997128615315548440548541717688505\nYDg99aHkQSh9RjytWknbXzcgLD8MrWUEHF46yQLHYANKXaQYyf3yGM9TYPCDUqWbOapqQe+XfOCoACLyRg7vVDsnOPRDI9ZFUgCQBNG06ZOxzktEhnNJoRC99da8jyodFqqk2f9UD1lVa8tsQdatjUDocwgJaDAOpYEyGnUlbXo= 67567767932654827067250684965667741848878457020992905661955722020937161710030993261011062929936964216357930453809610708591260182295097124272956485574313839759737390934220465669626974544253750900911093325004172643146669082793591441922014060981070503803266774197958528843445580649512373693546027107823355522426\nANdsfO+cNtWsbT/QJHGkYAL2WCHWVPrX6oEz78pO8lUwiigVEow5roLI5Tm7GP7XffjF95z5WDxzpoam+Bfp4za75D6ZEHQmuFnpWQAmNLUHdKUE6UcsWN1rbV1uY+x+Nr5Vni/M7PfQi1yRTTJTYav40tFPb9rY48FsUotivoxd 151275723772668372472508916060743043308364940375633847663054782759325087560768667906829087958412643723335046123025802453213225972572697773468957759328009026531148112732519692142632237595562259864125679649273054426879080697360204352423668940795473103047320116317252295126635024518179060076282921965794883439709\nD2Z8YA0G/vzEVVQ6itLPUC92r9n9FKRpf6lDPWIgpZOOfIkukPp7zzTlo9Ej5IsBrZBbtGz/eYmlHeZ8Y9pQj8HFW24HeKYqjmR0ujbNxI0QgoE+VUwPVg0HhoQsOGmq47zpXpkDwpOAZbMh/t1Bafq6r2zM0qmiwOacJ8KFUas= 10814483230552506566705634583020057064935800294861277580077052473134972003523900930560478187758928889017740705417070994563709463926267126567504805864719383185267204810142444719634360655595490833208838383875687102074846353850310954150927702228780599083427768247170427544730791038729428517279760042619935478187\nXoZpSMHqlOyPYJS7dWSRNDJHCkjbo6+DECzu0FpB9O8bftcxan/06Twbo5d1lEqPlLx3w0XeWtrmCSCaeVcXVtlY3QuPjdKPv8LBnnhslPOVcbGyflaTPXU+ITWE6rwnIF+yWQl3NIwCV4EBtCT+3U//Dt/Ebif9gzfKpKltD6U= 66377743237695515693282032069691369056215169443985727092982918806809030742478033317158686828712146024066618073633406428345129492010236994055590530566431286733776441810601990431112187030942086686719669823512292071202675269428014136307286941704297995292544712278047959299939833088742083527714893795660235870117\nQUbbkyJQ0Nru9c/nPbphM6VxHp5DWlai6407KIDbTGvUReVYI7de1gO/BFphL9GA7gDareYoMuej3/SVp8lEujXywtXzjiI+j2TzR3YYiMBAMhsJO1wU9pxy69Cj5xeFFlrOycjE9sPS9nrqnEEEFNPiK/GDDTHj0KuNbWSCLrI= 45838919357034925862751142472777409057791233610959872523563363744902783251621354580995921495295078179996083468819097423327554678806691589090814275138081407920379810144694354354954459732280968086760894209634364189264517251735804373673532012530665557440070501687207620525228416650281363557992436992284712644274\nF+uI7ARCeAlnPLO1YR7RJj8LyhtE/EJMcY45lsNMff0YeENe8KOITZVxNA55FcxDYpg9sKi1UV3/ASqkqpH8MOxWpBdT2UwSX3oBkp6ETfJKqiag0C4MS8cQVsfcKF39BJ6KUE7X6KUEj11j2YIIRREmLPyZ0LatG7dN7Rmv2iI= 16797235966984072293396362937533957334369977688369659112225970370748312376722010874726300554329794854683394163379447263409228872034356195791733533528404245739693397078461712458035888813157166614479153484688995068722288153129390850561042173295997770817893349738328312152341860704179681230323810266038959856162\nALkEoXznA7BJlBIfA3Avl9kygQcxexEMApwduVRiXeYG0uEXMQU4rgMJBlPqs+ly8LTIcLFaLnJAG2KFQn2GXz2TNa7w4xkegkrslIJEtBWX/lc7VzRtcLbhaXEs0Ci1ValnW9Up7dYOj3Qw9eNo/9M9b1fD9TI+0QXFtp1ge728 129924120553920201168632484268654219915712271781591182777925696006023100660478316445751842982460082888615429513674356810187315558964251402722465707617058251479494744427428152566665405423424700027316505872162698141109433045594670140335040479559124757490095995568556894332243767736124299898808796118800328801724\nKi3FNTEE870E9GaNtbT418CLSmf++s6Di3hzAy8NgiDOFo+uuicJa54V3JNRxOBc99sl/chfZuaBQt14BFOQ0i+9rm2KD82okNABd+SNfXOb0Ow2taZX8CpkVJYDyphFPyHbPIKmzwMShNx9X2z9w4++tJgzBzGcFTPv1nhAlxc= 29618953883711174042338818332957726953262658484143534778541769862244883781157097499904047532839425875312731531093860721544220959674634750905085721866390609141599426547378130082409488797303960018348798930232014390380383063108812922828160584483043190739354817699497573863286563890071313017508437166939160221463\nAJq8tcSnAq6M32ViO4hVGiHY7Tb08cLVyxpl/v0Y5adYblvjrbsFcCmsNDi5PnBOBl5awR7KZdQ1xgq6jIs+SQbccEMvJvGUZW5MgcHrXBj9XVd+8oB0z0eahqXpgYBqLDeHLU6238xR3dJYFf+Xrcrzjg8swx66OmQKkAQVJtdq 108660120968150664552423780971948386965268856900017812123107864829782135741514930439461240950044759098603910762272795612101834680870627850178371693837566833495418727543557712057554231215186486008080050486837716071537742708913279026303380104388546316647349432118287628353129105425052237438199445863950767806314\nAI3mfrgcRwtE3mA12gSoQV1xyIGy/YA4pCCvja4mTjvzQOAfiZL0efadxZH5awohCC1SpZDCFsE9yYp4LugHKu/A8zMcp4k5ena8sTPDkSod1yucjybgmVJ5h17Pru28AzHQ/YUmCnojQv55aV2+AUhxzIfojY+NT2PKRqr+vuf+ 99645829268436288676280252226747461064597487404802430565833102291706103139410465131373666856042539909746769688396958963177805479987372681967013633920910376342526433530508868114301205524789149997372160919406352823342811006288909548557622230243808373083272214426118230701324879006645047374853535922112549545982\nTmXQ+D8XFKSclXwnTIH8d+sb1IV0gfm7GagJahaFL6A9rvYaZ0NTizkG5DQ0RmXyo0wPmLork/296whsdNdUxVAwnGFlWWvMV0ftR1fOvN9KoT0WtVZ4Rmu6Fuc7q1PskAZzIp7MkOAxILO4iX5dNuVC+GLZYIbpTel3Ga8fXuU= 55052751096768041533898435453266875315629605001878362193939750978427494147944918632414581744895066623527980497732722163665712245580312596487741856071020477624754815927936394948233480228964159047139170955663289543349257377302556035170334384320502468579367401821986660515827461352578142560630318492817238744805\nEF6KIBWQiQoHOnBdJs1p+WIcAv9ILt0cnQVo+o/2niOtI0C+eFBSiNgeddhotkQFgHvGUjq8BPYgtLC8A5IFKGzXu4SYj5ziagka0hqfhVs9zVHKNx2NUoMhPDG5R7+giwEGGPOayGHVNbsBf1FBYG91+mwy8hnNbhcHSnvLGk4= 11494909948912248031301686864833544028186348338729984264372557659364976118965740281229664413031002362633393381744365783802034700038490736736266032000546393704814403638058993380993275865674190555703046732456017652317200288968188655019374159412919163798248766655991273308390043613040731449231289437754791500366\nAL7wCh8tkFe07qChFAzRkrnNehvda/Teroj65X1Bmcr14+/zeJlZDObYRYBOm8YYSYNgJekcL3o9lLFE34sCMbSJgm4dGwpEVexiLVi+zc8ndnqBDSAnRqtC+3jbInm/v8l6cUvuzrUNtzXIQ/H4FrmPMiVy0EMerkMtkfw5GBsd 134080980697158076909534078193319899756347955848461100874771253577754225619652121295523443912922220564492468474647193062555347746840044705102003079330399499915801536721237211615317000955332058281901995149084303143543150689010335818219129745452688372571010816270728441637278434982752674030696337642893239393053\nAPunLhlblRi3bbRBwSV8dsw8h5SvT8ncAmXPnca+e1dLzrQZzL7P2OhFope0mW1MCDl2kJPiGTdK3SiYJVsAFeR3r/0z96g3oq+8uS66T6VaJym0QToMsqQF4/fUMaTo9HsukyPyOgjVIU+6TiFd3SxQKIu1/GpQWVQIP2pkHFKM 176716779397275986910036615967409090183531310366246043951791503601618945774743601662530806467045971394247287367421508126613573039423674729894091424105133906122821596079925540513892022311039293333114333317886304014722168786051080135090242879622144693440448171583324154550086458411590240882982297314605229953676\nMM6B5AgdJKe5OLlPzcXwi9WhqQjx5KsnBYxxa3kWdGNTdk/IN6TVd4Ptn8lWkLm78mw3DXP4Ol1sQbIfkHRoKFUN6TaWg5aDCJBDXyHSTZI2FDc1di0Te1SwziYn0sIOe+R+rfuLuHlcT1xaZBgL6+dDLAZaZza36UEjn5i/pTs= 34273208848307582992498656582721015257885595139328466874135636009184357438445251703533153492315835793684794951576799764181908090765379592683793969576893243386892292517067596035059342970830813419330530731370385186653239446376170533147020072285887964430731437765184844167400169982662183791828762458682426369339\nAJK1dx77ZA4F0sYCgRL1LKSTvjGTKBHd4QBeVnE6FKJxIow82puqtsVZ7TBxbECex+LkLQPrEbuQaVr3giUDjg0aJCE0D9ZVXCUS06qulqcCCdWgGFHXDOQzTWDn6TlJCGxtTEMbMxSlUq1q0iKZ19kwMHiT3GydBn8/G7tIYd23 103022457217861194294329435482792508957642944252832971366936865663608381648431732294396977429863681671686490913575377744795372643599438468695483808375208871881849232129651519218503507811863794426234594709451104684234156597418383183271923307418704786548452806494411689822939919114966188329657999811363991575991\nfPZNsqUYBbVGA2FAiglnByxGJOZkVSpj8Y4QNW5wq6o/1e/PRwp0TLYJXIoCJRs82pAj0QDpQbHl5lCZmNxEIQP8o8xI//HCPxPIdgBJmSfm3VGetrOpqEGU0KJJqK4IsjoVpAfPFMUMOpGNz9CSvCHGk1AKrtYvrTJEKmETuig= 87751387019308584846595931543798879607048239290774788042055795835726250309378365187899578817976976035304304847968410200168743967600896348021636654074952051821111673620467434295067182213181329543946368332581250062140819766061014427755090798550122401239987766844126425179573454145697756278292448630509686471208\nEmT6DUd0bxcdprYhAnycQaxm89kltJOlIOGFFRmEK90H3RhzBGr5PRVTJVqemFVpVliO1gy1nPHgqDGVNIE1GXhrhyFJU6m+HJeNcduippRe38xPCiuraRkXao79X7WAiVYUq6RIH+UIRnfTvHBgzTwjrOvKJ5853hYmGaanjh0= 12917015385266582065020051081997430892582163827812227349569911846746592973268746845211126663077128575098045461893559476227689488349263954564361736197688317585888118974603264677576027836032271531903881104937422976121352854003385726888601980526287956222142458858211589791399646989299770657341412683499692330525\nAPtOYyWzdY1A/YU0SGrtjPdMZA5E50Y3hJVXppwuuSk04TjXzcbu2Sqp7sMnKYbToRW4nB5p2UnaLPhTRy0yszOd1auLngW+0ttCybD6nTcVoP65gYOwXGfSEQysqKLb1OfV8kYq5Ba92Efn+CcWWWuS0wEr97W5M/Hccx9bGu0r 176473215292413922394356058789571494026727424839036665031567966488209592078148711908841964690807374236235612412767651029865069639786447019874344449598703213025389428836803984245755885691094364960118900160737925054803955567361126391353868279642836569627177281508980029006921064654964339077608785831304875404587\nVs6bjpYfFA1R/QTeCfhMuZLZ+Zxo6wxq1jFZpi5SBR1LaUwAtOAj38OJC8L7zmxSOj/RGEmJHkulI3E1MH7P7xlWbY468/azfot5fX9BgHrtptV6Q0dkBUg7H91+tcxdbm4/V0HGQGa2rZp+XK1rO+U/d0ki6iNbsCsCR+OeyvI= 60957991334776853645581868230398759578123373154273044785333939425321390401088800849629483265841435899835570419798325123273632247193463641611211088549152950252041797959644227170492417662363676228611376046334386877555777556575818860902071813120592757466883038430756577949025778080997296219236534786815367760626\nGiauT9A+wmwJsFbS2OPIM6ultIbU+kT2NgACn1jFAy+vNBahdfHMCH0jJdCs5TbmKTCeiEf3ITc5TV1OSvIejJ0GRkTf80nY47TAhiP1aehZvMAv59NQHHTDUE1U4TPVYKIyFpm1V1A+JBHKJzuGrB4lvqB2ed7k4m/ZD5lFLMM= 18363925023885496669420377869542744504974590667921570026763131637088916425434675950812384919000566852243714758512996458727914094904422651029609645299422563453163291342992902510788457007623888307499601267675322986672697397389663297565071582648674012080122614260400848960757021864980761735684874056409664531651\nAL/9KOZLtZu4+ZQYQsmOgbST8F4RV4N/Z+l8qsbCFlHdXHqTTkcN0chsccE/3KkVTZsAnAyJqogbAvB/RZqttaK5a8iKlOEoerUS92FVQw/42WhsVaFggR9cHVuvCD6QqclZjSBQKQzUMy0YWPWlycAZDIv96tooA+V+Fk0jbcFs 134819194171226950171930028888667967094069342154233489571728632904658607624703819928943642011918061760802468868660586005724399808048609316802502143143910585363214684061242274402109137825176291816945489430125510625857564490981683683589784133305376252294774711594646923226452625156299996630452243345104727556460\nAK5x2N/4+PKlsW/fNrw76CnE+nS76Rd7Ugo3IKhMTB/IuCc5xG4MQHo5MlWE0oVkZ+Gs4CxUpvD/WCCjHHFlSxKG4mC6ehz3NVLglBt+f1RWfPkF28JPd0UaIOG3um8kG4J3JDN48PXOPP86A0H8ZYbE5+ImmXsGAcwvScUQRInU 122499245103202714319465533564374494931278163571999934877854825659720649344163774228004853964635693562785966889622928722984134944784141208867445419597834322541679973956606275877526560988151196822256754309120410807075405427166696093800381410682490767468563176131997424692783482903880902119461752084196789357012\nALZ12i0hqFhwRAikcoahYzH/BUolhgZ9Jz6adLvvTO4wk6LLOpNC/zCz+LjM7HazZomT1SqeYJ2X+WeGFLADHuWo+Gp/I3S0UEneYHKJxoU7OoOtE0mB0BCncLckHao/LmbpnQpS+Lx5bRsr0yE6oWNea6gbyRm/R0to74MI3/KK 128128022342420083856194424802390993133863171077961467523372211039771843125192435716337829530528063182315478279257832480290950255315151577221042903861075751839976362752440630888566422581799720709574650482021111126414843635330535518992034746102956214991673417580508389225948159518319625680855827280146399752842\nAPXxvLifWgehdwdTRAJP5KrchRzgbUsyMWKcPGm2ZkwGDJjoTl2LIOOGVFiL4CyPBxahkEHf0nMxBN5oNGX/Y4W4PuOAC8gMgHzdLkPWkpnTcyoe5DD+fQsqNuKVw9nvyB15fx8k0d6b056nfFjnnRqgybby7MSllAWSKRYRdxVm 172707950911363219032118650562553641123743396229371815589867086054370029540557395298194067635069298952836929253340374819975848769009260895874615676938511747311585257140973518651959463416682165208985512233703837931718385346209362040743041262031997793519095342415901373534535662377972036003546589624834285049190\nO+9ohtZ9SzGLJoZM8IRQAjhc/GPt2X5G+M22ZidYjx9WgOTrZDXorSyxLuHxay6djsJSgjxYMj8MuanYSn/DzPWBB1Gn4cDmIsfeYuzO+vUJ4l6d0nIvBg9Iqs61/PGFd46YxhnDiVQ9HEznyTjzESnNqc0+/OkQVJcwNHAcZBg= 42087920806448980363073662127262313840530298932643042322138035915324224188032438119079107631420338701086802583985117830416851550991102672642532160807467909040086448764318690465254898516502941122327185894900817634110254371864896139724173087625913998657136384357741816102965779105122269429701537815263708996632\nVJOZmvqrqsIUTQSSJpZPhbQIYN2tsfBhAciWnfAYpwjK9/ts7OP4Qgdp6T/V2EsSRPnfZ0VKdLg1CnEWDhfcODo+/BZcUrJ0AviFAEtdeUhoMSWXtjel9Ln2guHY4s33z2cN70+e8gfjes65lCzrxUIXEF4nKxzKBnScoooQP5k= 59391682001673484862915842850714742391303140646889359425353339320546979084250010101273851580028171449840778038774656177449549941659895629203970455580974953864068394275066532699748911169800076515776388213090834432354601344176559839798153004796057709798368011673585434643656820656931921831615507416411999846297\nFRyJCOgPziO6RDHX1JgYGZRcSAuoQFIZM4niD/B0twK3l+TRpmVigKZAJnZZFtmX+0JQkDwQn3lcBGQIL6mgy+j0hD58U2/Wd6xebuHSzf4OHVGo1cYoqZLplszA+hVCoDVTHi2YAZ+GtfQEggumcNVxqfEZd6D9Nu//hm0t21M= 14824975573460749317081504809641216868382341402512168178334301409725840669112911061147252565570697788806398498723577368905065980113760265945344671897779830912242224090954834750057278285419880820811348943398148063418809729356397202526234113316098584002071850758705282845646489058224513019380757604894853946195\ndUk5LyS7mduFJlvh5o8R73kJIeeTh0Zli/y3XjtIXfCaNRf+wDlD/pX91JEwsQ5Mvj8yq/Uq13QyWhoNwsPpXVcJtJ+02wtIn5darsBDfzcD/LbWhl7zTRUeMjZ72gAWi1djx94SWjrZJS2oWZU92Og1yOyKRG+ua0AhHfYYh6g= 82361050315899968537319599868832189063658136463903643442673674137187842597528653416212822014359684261704550279153006971937114135373937934986951573613797195556144113400128502946618028800530164890707031379614952207482505803377774320259789692177752930767589642007257364960987343146063216186985472686575891023784\nAI6rejwEznR35rIPuIz0CP2aWyhRUR3unJ90YfxyuVYxrqOJQGSDTSf6SGDDw5MqpZXa9pWuwpyrb6smOq4ZtC3Er7lipJfXDjhy+0k1qcfMjmqbATUscwXGpgW+MO71cttccEz6vhbjndi8gvG5M/vfL2l1jA8nXuBd4e254dbz 100186164434910864539376019601151338080943067893748898987236087770762310617199833479771711726248130012472861788210345311298499515751355424063761182369333224929721733015910055321263016834247318907562652286587380604998130368845939290804442878127169587599285040969551065995197981341260363722618429042861484922611\nAJ5vLZX0fSs8dUSBqd5hki48T9cYuR0atxR+qv7cRu9nD1vP8uNVR8dLitg3XH0RARt3ZmOgi/AuggZt6tTxuIBg+9JhBY9WW+BLL5CnYWHC3AKMi7MQBWciLtmBpyF152bDaEcV1PXxtml2KxX0Ba0C+hGVDmJSdi8Kjd4AkfU6 111256341508463539324514225759801553679558662737345522765042612717818066374840372549356543720386819501973783940451033901079765311790026584654529398345993992144903839534037331533660672892487693477412528974248713261092693018326068480417183236210881306241164169849090833681510163753605662526243408192127670285626\nZhXtSzn1GiFfHHnSKUYZiTcEWqlI8owyCKFjCQ+VEvkdk50m8uN7RCQ6ZhI545tN7Uy0WdLstJhgJETBYLHHIoWsJn07mgPxuyO0XsqNroICMQEOO/YWQFk1c0VqZifcohQAwJj7fONzM7hTcA22/7gVigJ3iLq178jZOJsEPQs= 71686982768953132894579286530164112027530221141251507987469672039995314435159469907420372652392376452531392493658576814100773556880394271726970628960571077839124343525055625420896355363707908511865700866168843075071778015504724409171911254647909938237551680861008772396291072284353858575645679153885560978699\nVc8Cw5m5yI+bJ5sUJYm/F2wyZ5x3D4ydyL0uU/3eVF2ZJu55OOlC9pUyyv7WGExClHvWpR9mhMnsqCLyseLfM2Q/YXJ7cjGPKp2xd+fvwHa4hRi1FdOxs96rJnb+HUt9hTwQByXgzpnUfs7AqrqaNf4WSlBNMu0IOOqDdB4iVHU= 60256873326783629723455608618518793848697944184579877638436234491615392142659293975260290798403892159720925893207048153291000664050780029732557737984085196691225472664027706406879051455184548871511448456651238810812870905640934953489289909009741493031472382758586341375517766302753448531830002512912250459253\nQmeUn6cbpE8YrDfMETz/+KVFaK+d4NHHzcdj/MnjcmqQSLpP/XwCW/aeudlN3SfKd6rNo1XZefunZO/ek+PHEIy899WzjiJaajhf2X05fl9WuPEaMES3Yrr+ClogFNQ+9jL8+7L+J8lDuqQzvchT0U0RPay5HSNZw+ZouVCiQ18= 46630904037845609335515965570673490721137364238213103678233212262384415738654627185220187275286458759154841820256007930773120637898228224906635911124921895934056288121005350040349882413280772888907627838315559544636626856478316691755270725623680935763476199888127096014398699432042227882284223578563208692575\nALUBYIShA4w5kRUa6iNF8S33DqaprdOWjVBnO+j9CCGtUh+NNwfpKR8AKf536MtuFFtwaQvRIlkLpaTYXuRxzyU/YG2+UfRQF3pEmXQhcMxJqFzqZ5nWCIWlJ/KtYS4lcC/B7hD2UGAktnIdjVUTSxX60VzA+zxeunV2iBZXQlEs 127106299687401374061881872616647348819431126560557369258073443762502337592227172639640997680536372567116568811258505773087926491911004324918919511363985868314578663758269650473780772688462266790559846182685481907703974916356209771821075179827563487466641669110315430790405454641953880582274165368514679034156\nANyAdMnVCVjmUZGiVdyvGE5mUQpKoJOJINqMAfzVUGvvxXFmGdoAx+xsDRNAP4KoijpXk6E3yPBPBZEWyhiHnyjEkktK/gX6gnb745afS0QIlsjhKCk/W/BHXkzC862Llnc1ZGAIsERnGceEoZHdICfDUh/7nMFp5WuSMzPB7nEO 154841617115465511611746667401422322067517612306328612547616471923266281876818466022676728696273611923942543658633762267658490816264271663863494188027433799849037906883352478212451733963905925106470599843045599411842850386623187980045961158399934160107237440980574028985561404965317132715808604373199725949198\nAJ4nfhDe+HojR2YrprDHW9FVUxsZvoIekwlNL2iKFRFcTB9IcEdh6QnGcaRinev7yEYUsL6saSxUj39uWlqo8udJFdszuuQUmnloIi34L5uj0m1OpLy2dawpFQr8pqyA7go4ugMMj6XCtiVnISUcK8wjHgY3Jed/EKK8k5ce0Jxt 111059703393618496515021583605572584329116596402705082562306930876194742195701060137568030171429700588269665205795898835699633817098262654446852249498668467827435829513531633390969638488553144849154126899372953755511962841193763362947708260103832329116485114451074371844037650417731807385491783373627950406765\nAL+heSTflb2MkRYFTKghfzqlVQ1oE5vcx0eCIsy9NJ2NGFXCRRvoGDVoB8UEsUWIRnaA+MIpwDKGpbOS8kRQrvBvPe/xM/t3jrGkaS6pN064+bCBx8Y/Jq31ZXNG8oUol+y1Eo6fkUKNl4EOetmZWK8VmhVwol5YngDffj4Q8ned 134567692290185631768518572983694048149859804864902017394351513816079806629664302312927579302025923096596995134868068794900003728293470554490807959649153000914807604036531509869958441069678002226922395630284261949256022972967357884468325217602330254290548618134453007903724438628204981673400911693835033278365\nAI272d2sbYIi637kHZC+6lievgcDvT5VKaCnus3fHwm2vfao7oYu31P4st9DlqPWJ635X6QtLkU5HgvVSy66MDj2fcOfwVL09ffkZYnoGNdhMADVgOq62Ro5cCpOdw8Ko0cCyVpVIaSysPuqY7kiClf9GTdyZz/uYHDgwWeNrc4R 99528854246023003959943182132914587584844397870416002887630245681136432049666385367430032197518895755482367603560037194955739661569172773017279832774100155646116233705958563163070414171045438199561777058338188494271322834524386565519620661180246416329082614115142485663975718653564590519408413408765689056785\nAN9S8vPzo4SkyKsk07nfyD0um1riJzRqqWF9KCL+kWMHajurgPACikYzu61tL7l1mNEaIU16Ndz541o+y76DgsTLYszu4KXUOEt1Gu3eHy05Fq18zCDlNesSVjkZjPmuJr2ku+p0cP0TLLMn7/KuVOm4GlEVc6OvBNZuEzRriSYZ 156823459768092337875922818543729136404805918580285507923139232733465414368775678369646914249412830351437211620056021568154043505276475345347569200977945836210758870414054407438380975491139001471954448623922841964684437333066353208837709613982022690623722155151315252634380695513434502419141555410441456920089\nAMc5H8kywLgiT4zz5xgoI90jejsHorbqUGtBeX9wke7zyvEKyWxRKScZwzRbinjDZzN48eg/30qTZOV2Rw97JFg+EA63iZ0vqfF8jErIt3hODniKX8zayCuNmiSb5kiZL0UDU1SNh8ER4m6o5vshBKkmqs0PeozfCGQtR3bZXlx4 139899247405256530335276706333424670310599977544642091674186635734421385499036688803073040921114325725234673132788498809189814711681909865484671959982394306416477300458309408833281654917008031099378445580498219376391819745965887864647387211647794422908411100892195529730435423964537342228510107659017578765432\nAKv+3H/TruTX3wdMWnLzD05em8u/QMl6lCHT4VkK+uZwBXoLeji54Tcs/hZIhj0Bdj0URrRt+7JdGSTy4Sr986AtVFxBJZA3lT+JT4JSrq3oY1Tv+tX/yg8ZodQmbpQyyfaFg3BgeHNmsUoCrdqhj4IwBqEXoOBRIXnzaTuqqSEw 120779384043726135670909127168686589868907326577918074234323699599475436892003731971700278391108690400460261929381703781833059801757700386671579819341589048987186473249926590758009001670959004477454905417357202448886738669226760846888369186457452643053236389556969071303251275912453385963613554945645058007344\nANXIB+HxOyJd3YYsscMpqZpi/eYjZi5q6A0MohU4BiWEJK/E4uIObLJDH5yd4ng+hn7UMhc+R/AxG88hIdOc5NyG/QyFs95ZLUC26F9rkRifu2CBkgqR5EQi2cgwC8jGxQOkC62YND6cAn/ILsKTYaH0iavtO9Tz04vQp9Ypc82H 150122383481070201614242107655752525590609186454390549085509458064289390813495886095936526832230958746095739308601699615024239939948911472291507190108935262129646691795733786714291498653838550751365834947465294261687773081563139416397262227609481906371677917295227469553787085145970923979142676551778927103367\nZQLFoW+dJ7vrHdMlcLRGKY6T6PZKnE2L3NjXymS/55my2CDBLdDf3oXwLlRjVt9KnEiXyQzLhyY2PrFA4k3N/3P5lVDLHero5c36TMshbHgbIKRGN2CGWPEFeQ4j040IwVbQCPJeuF3jL5ikCxWZFXfeEnTL6TqumLfD9yLQfKA= 70932215714423143395949105745758445705072524008235214324766464113352968998429901322485575506330607802260244612268338586532462314021433435523464635419846126736185176246740838082062856583684393425704173881940108783636582561707441482446854068022535943408999200681879161519209676205165680598258447492092651404448\nLzzvPw0FdtM2G/RRiqoajJiIH+Lw3jpL4H+08yOpp1bNITR2Aq0beu2nP0H4o2Z1/FNr2hzuGakkAhVbmmRXc8keoOkeaAQAP/8OYxHpjrqou3WPWaKx+vUCTSqVYYf8gnVKpAAC2cD+3lW+/ZJ538o+c0ovbUKNu1u1j1OBtA0= 33171669664542509840621265032202455391098253465550501094201777336478104142847268103467889435377685359857979277521589539506627375165485879405453566052091202280471235979376217319335800766353336252760793484157724210008639813552207624049019149744883918494762511376489708611103181576211531366514802868659603747853\nAPrGj1lIIlxA57DNh+bTEAFbJK2Y2P3MxLShb4fPx2aY6j88k3umoe07ISQLf9PzNPeml4/0I3w0KNd2x4s9KHbj7NsIT64lhO6eQSEteqZXZGXUYUyNzhrTbAjt+Q9LVKItQhsTkTW2HTQ5RQZfGrkL118b/I18J4P+T8CGZdDz 176100632478477421621142147788721746818712752858710594712903769452749028606541677227413333567013253138397373757811889654342173021761934591400685421771460440213093509170325205622261487145789848227404883040799927313402244625239515162996390018403365063394514244196976794479529075569412676472840544017222373593331\nFvcl/LemWk29I5LCjU1QedTjGlkvFF/kZXNkRJv+vNZ7qgq6pX8WB9yVkk6AoclDYAhCRfKTKuEpR23iafVuHpprPfNXcqBH8n01kq3U27xqIy2hS+D6BRBK67PQaekq31EB0aOcEb/DuNaXakS9+mtTMx6BKt+WoEY+NkzHK6c= 16126868736093163702771491576570380743773057522016869811780571865928979861357811080042796140032050364543242385458140594532945509386155523162799601656485075247603490060565663264947465987286983338572455184901756399862440455644131755848583379822279676555143231305246033911608913609591095831135803702269767527335\nAKW8tvaB8YZ7J5W2lmquBniJzUhRfqFdPZPqvBoMzR4cRh1CMNdSFsYsnsaF3KolNzogdsxFpHAaEMG6zSvpNJAoi4nixCqb5SETXrSLASXvNjI9MvCoE2JCRq7kMbjPL7cem+mBPWZITGUI6KVlJPLxQngHYSFxukqlx7jznwJH 116384596458828069344020651216200368975621068920641012055593076864629080375946542748377736186556382088448816531408136815533164209947323588157210859294774679831647934533061547276394884474877353537242203645373945111105805934070657589374883764420038511061919092743520704686962593876316976299391579463759429567047\nD5N2P4FrqDf7/2Z2BJsqah4SjUtolic/yNqdNzvNEogDKZKAJyGq4zhnHvkYXkEm2ueU/FDPJRqisszG0oULdU6c7p8acirEwsGLVh4RamnFRgmQSK1vbiYB3bR+P+iFX/bZ+TWjN2Y3YMa5UB//I6Zb5kEIjmTpjY2LEPI1e6s= 10937855369372570149476727082965401421189236366492771695094788039313362971972373068736123833330006002198346944149230147444718818161877123407713821100752433128205189334393732633989950841577315682292180735057952587083688644195300641998709155269462601925653013312848413290208844194513502358901613104779186502571\nV/A1ktS0xrcwlI8xrYqvlLCFYrdVp8tEzZaZ9iNNpPH/pzVsA0WbnnUeHbdilkje+4OdoX9C4U2xaOuWOfvqLR0c7GeCkSffCqyf4ZsBmjy/BQL6rCpxMF0gIHXO5O8aJ1h17hy9LTuNzWm4zVh4pNFuHC9L6nAcf92udMiIQzk= 61752386563628388546439207444896778638632243226541303179646524864765343154194512297447627825411023405896612559648434895675553567405277169056807223959390559391191382555701580549902639604424290133917402316755076644943742815711432111554988540913643347167948778404861099845961151998728662878854088239266688156473\nAPoPgEKA0/r1FYmt/Iso6ChYK6dDU62Y+vH5h/LVE00biBYG1f7aL3GdllUTN+XQSHpqlDw8CD+9xojwZIMfgpgjOwLbbe7Aso460zLrg3R8aHBpbVt8iZUgjACwPYr5UyKbFzIAWaXcnYYQ+tCO9aDIuOz+/7eIF62C81zXFJVZ 175598490446477604563905754135475294999639698464908622773037381109011373179895295130424828038708319325919451724985361900259676699137657615076219968061941008972496322083528922054390781811699677037439989404270415929836486610353098273115864435328533577114470407444852521009919911888840405368858409835197558461785\ncL54ymLJhRx3U20Y9aUTIsXy9Ags+XHy4qk3F7uJyO46eiXSL7VrrR9vTQXAbETbu1YiVWfslsPht810eUDUVaVir6yLnXkywn46Ci42FEvVoTEFjO22uYcCh8nqB8H589w/+lVSlNrcILugwfdfCvK1iZzVimOO6l3qzfXToOU= 79171550718114578361958369278761819285111811576818442980166457146638966315793211967882077899426611721874954146020093740153495693185472340728106727284441726113022873005252623222594060645383105757498856463065370975867121188445567981809371870213273555432308279508351518168027875538720367440153667708369625129189\nQdQN4qW2QZq8/fmSaqlRiPSoDbhmF0oYjaY29HcKYGHdlOH0AMJb+RUIq1aszvVtjh7AYay2TNhaZMWQ6Qi3c42SNk3A1MVknT6zqiRCGjNFfxf/matbRLbTFQF832MAId708vrFLF/o2HpekMkc5hcHB6bkUUhEI1NLcMXwGck= 46226230186280253581676626651942823886592433541360244612432763620730826574920825070086312767146345247802570752482654580909236388357139147786783758670999083804670979821212991224400629053427330483809790366665043598754931511997925850227997764381723288657884346974360232490075739442406431704368767588177525348809\ncxHvCK/dyVDvaqCCQyLeaiBGA36mV5el+1lc2eUTkHGUzX5gU0QQCEp+iSXNJhIOON8VFpKOFsziuV0Z+3cegWRw/VnxnjXcBh6IDKdupzOPB+Yl8MA1ti/GrQjLC6ikcNYNjQT0ZThL7KTqEvvZJH68WYmD0IuK26swjNGIGaI= 80804939616399473443737611589382762718815989847332356984276911837267997590368701684135326680567847542004499684038240485603420973682522792156533112356849436451918522884749244246467852622918805139990256619014116276456718693703261686778030658826952213058982142604346352178078750879100976710761147710018148637090\nAIQ3OIZevkYoRGBmsFaXJobSfLeInuKKReVYNjP5VEPoMq0mXTltY6l09/rQ3d1JjsMD1PfA7emhxex+H9t3leBIfCi6Ux34GQEjXWpQc4awuiy9tbR077HaJyecvb8Qy1FTnOHoH5C043QJzrKYT/sFXjgB60piI8Y0R/hwxO4r 92845026347218330987427785323244729176754623818531419911990153715676845614711324345879159989637824921793015074978358052562420379797956750450245721653716740651389924718711940869162230097839047895842495414221110468446944827052871968998907462191349838598297775847512250220907563815783358238473966349820476321323\nLoG6ib5lUh57rdmSkZSWzBoudytFohS4uoU/uly6OaQDOi34GeNVxu/yr6RszJyL9JWkGNgFaBIv/HirH5zA9VQAL/6kpL93a0/GQ/nuHkHy3GWZPF/2+yJ0PfazQ40fWhHZfRxBngWslbguFPjj1XaJ37YzpQAYb/+QcUai9ic= 32658152290878644668906121702816147999633088014476055330179597550087921141413344679134407016170035735846077181424615228657687216737432274043674411132745299610950657139041836412322040866250189120286839287690983293111362228893996267791120043532014262644480689231457941173330523718758287779526551822788227954215\nAKu2jgOQCCfYZ3CLkXEH44aO4TtwMPeK/eq4FtNj9HZ9FxT0LLNJh0ZXPOaPJjgznvIw5C7/hNm7rUs1JeV8I8dj3nbS3EVERQz1gc/ckYB3H1bViWREOD5+TScDusi86YO/z4ar3dauKkg5kT1kKDuU/OP5kNMWvtJjHc4Vd3L3 120581042599355202025471829872601846477331097842315143148145881424071317426176264583672725691485724160094190478865850305422057632110749683552966861219554215519032344086824849470294473808177223497912069335635933312949412445851201918768630656712413082629164792850095444166888072453190903931430551124946191872759\nANLs7OsR7oBM5jSjVADrk+Mx9d0TeieTIkxwWiJ5STKNQmW2EzPOjgbfcLhbYEhzzDFJveXO2dzz6/c8V5oW2yqg7VMx88DzEbpQnQpk/rOQRw9jbI4fxXNJHkNZCeysEVvFfLJb4ecsGA0xJ3Aylny/jP10ahPv2z5K99edGZSU 148116916208650944522110872759145096907599612943009577897396622287067669897712748449324334650112672914917664881091633448764667172850435775162090891556266912697811031318228334453406561952979778127173704706529448647577013482442758465809198730066784986763500579667100246958959793527011919373534159474250508506260\nAL+Er3n1qj+SBsZVtOMJYg4m0CN+DE6gRnC1F7nPvd2XnBe+QE0+LKfcpUDHVNxoydW4BDzNVwnUNbyjXZ+iuddPtO9hchVEI36UiuL0ydeldFpOZ9mtHJaAF6abd0MlHw4vXRf8CbOvXb5N4s76ggijlZBjRtU563sSmBcyq6Zt 134488725667189507159811764480908602790838430340670328479145818969651133017546803581865897303917708192047926432630297993507146075655594931523561067937580218599890162311074002344315818494246433967228889645359283635389151927472221799543158424012020308449895562192866672439712148770104592027035768027605661099629\nAK/04XOBSjjPpuFXTDF82RNWnKqZz9mJQbS2B5bn0ehFnBa6j+B+MazX+AxXTL/d5+hPLT1uexcnSMl3DcGGwKipOXg7Dtuj3pfJXHTrCqXAUYrIXI+8vKVQO55yQPGfzIg9SVgetwW1sDk+a28ZhJ5a9OddqNoi5C+dLce7ZtNb 123560902006294001923570614486104726169564351074482936927091682096999779538353161007361361829586988452098646362280351148131540524964916445100589671458589346440250329883789099771417949746709217272531950438336245613419967556433467843237384555807236658182067742367748737224684334525934210197178231424396818830171\nPzOEGHlihiveoWFAALY+LOfkRJfm0NUF/uR6cSU/tbpGAq4onNpr+iZIzEP5o3JBLOtDC595/NBPI0fzaXl0vQvgJs6KG8iKANjsLKQjIpZBkoKhdbG9MzTVQuAeuDW0w3sn2iMZ/v2dgAzRwfqmQYXJr3I2BbcwWraIJuZXw5A= 44381416070253681813077725822442106641846565789204187691647505370231831464947935035197059366680327425453811558282831465960889061956588244308214943856009686127871667376028831540813257349779756631357122923723235595360268572998278795110672666089470210929411514949652537714634611421849780859192966935514197771152\nAPnuduN01GS9dO2m2uCLs400AR2lX7elOnIPC5U6e17qbukxWYzNhilZlM4kdGXAIeYpzFdSIW/gxRMZe6TXq9krFWRaaPyT2QwRfGHYnazS9F1QNYmW1zXdt+qVp0JGxmh5PyDstbP8Z3x50/E8Mb0gLLPhNAvzY2Jnr9A8Q1Hy 175507868985304663005133968393406051624825489142498103948374797086106732382869120248515993626061853699363294022457032257026588816021007648668265488426495800459085474654859258116280251546902009156490112550154951965894022789029787886785376415437170872937201839249103828294508088966180386198213606090453461193202\nQHEhL4iVzNdUsfG0izTEepwTOvxka8t/9MwuF1Ey6kxsI+ry4g4sJPgR2xMnbtOmvQn2NitAkfvA8JPCiL7a8+gmf+DVRDjKDfpfrtgAVmo+3rH+uJYTrKhAp8R7ggU2xIrvbIrgeUj7ieThPI3Rtap+IdkPCL853JC/+oKtytM= 45252649968839515171157821292772647085425694172492111870169593872127007254353374581972876464918186509502070064028725519394859148593053614163356612260257013360168930649423732336969778875205250872728821432415158634190866775855521719727700464116412886964736859295086745723651735554245035077902615220578218265299\nAPeaekK4mVhEShCfM0mkRebcg1Iq5CgrFIEGOoh1nHzgebr5A9Wrhm9yD1Vd3e+fFD9urDRB4y5MHPJHX1U2NFToC+H8nQkFXL8bfd/9Wl2c7y8m0Mxwi53pLIdzETLbbfeOOtJvuSYYT3n8+/PeMnJ46UD8OfqtnFuS0/bVpFLS 173873040145444066957050580959132871919216036714423404143335635770937773583761934638398867981658394368476005882852706046614562314432695052874974848076542261910660410561876043187368112065303981001507235893831108658530338308496461162623683138693880482650786841100027392293758260448606244283355655751440485602002\nFqC/wgZDPTUoObPFSH5w4QR79zj/O+ZiHGTEnsBMwNZD3Gl/ClRDIsFMDDupNLgwgXsqCQbpwSOHOtAvUuAFwRpzt5B7lwIgtP5ism/AZRno5p+9WVSmUAM3glHsNtvYydz2MkXtnXzSMIR1ZVoLrdwMnckE4pbMzggqz+JZqxw= 15889870005716350976759704672045310928616256175405784574141006779373730686049218680335525720670897894546334915362899913262232170795516176419192840427996647372619000239408311568577050460995518058850793096827271653902583271225799114408537346367483775593212272587811309978019791973449354003275559762102731778844\nAJXNbv2AMWadF5h99ZAUy5gLnVK/hMaakFo0ZedtPNRJobxPmwj+h52G+Czd0U48G0V0wpdeUJC9v/4BhjzhCvNhNsdAT1+vQXDuteYQ1aspsEKLQ6b+NknO88QSbRJw53+KeOY2xe7PKOa4V89XnFFBF7wljRnIYrM8vvcqVQDk 105194875227030598769888785590198577650278341586165110611689226597424766274486797264032300493674927704016605741286512271390703088626381669060095573361828932336327125438452066548897528158329044309005232090053420259033538936293519762277428283316506398965916381374819450858053512398634116052299066189424983605476\nAIDRnUpBHepjBqYAlU4MG/8JxzX1mPxVNHpWvnEVgvqTQx/bisFPpXrYs3jAKIR/lzevYwhH0K/8Vvw4NK9iTMFqgSnU44AZztKsoxUXsEsl1UU56UscY5C7ciKU6vjjWI7nm/uHNOXdE82TQXkk2WX8ferNqZU5DaLFCb+zxb7w 90459642084794142567976043425270153270545560059973413835786695756473295513758287577749768786155290305189883600338986370836806413936196854410098516254596146039255388020628703824195128439558127783534033672712705194483515442668075394018677699876614329419492391568463215822656901183478205197671375262145069825776\nAIdvVNzJqWPgAShvi3GhbhMQft+SLigKGrhoqas2Saz/bA9u9Td6fAxa2LjrAqshW6cnm2aalc3Yv6RW/Y8vg7Ho31NSaRjT4zMUenykcC0/Y88UNxREi85wdnHwGytms6Lq49H8/7EFGJIyL1PLRWPmZn6XFkegscI/HUq/hiKm 95105613103051650721863964216778532448106311156426028879315612217763044797186635476805213120469258258125661666950525364331551671653846368977016286153840829836509696804585927581668281228810410814602664419962214359687545209312836366693384158782798559255789953908588601637765910472073600954502095647132310971046\nDdchOPjXrI6lpV84IdKCisPmdqZan8AARXRLADEhixsfXCYuO+WhNatI/fM1vgv+/TxwwIQjIfG1vOZcB36JUfjHYdItYQ70vUXaVFdpqvoBGyfOTU50Ds/11iGPCF8mWiQwR30/XAXytqDZtaVJVWsgHD3RigBSnSHhnvZAWYg= 9719024770319024562623340689338530708271347986326272393419504304391837979619189392867902307307106771234732135400958362219711925045600118964223238147375808749507928768896918369395426933218443166133187066167663170936604731896932630589251946733237697936733924510107175304126061649311812536190882160340308613512\nI+Z6rdTOt26/v3dtUP1plITb15fjb6aMDvqFS3AD1+nxBqnnk7ISGE9j6dv762EIWQpMzcCG5NCCq35KOHEwRXP28zup6olOMt3CBFgYVcBE2pWOpGiO19G/iFweYZXZPY5HgIkex7HBbb7l6HhomPc2sLL/IRhh2oogyHx2JMM= 25210054612455888156900839678249806510561198051210010474517915819801056434402727631042894881559517808906460418029149538469607239850657781476308872923928122553395468026744382526167194202058040459679991391557937527079948356545086684521068912222036707113005006607012596093923970784177288565193670152033981048003\nALbBoyelCs4UkfnPjMT3S67ujhBHBEE0uxLx6kSGZq2IOMU/QdWYPFElRgYC/y++334FSEycjS6NAJJo2ITpZCO5AjNJ93J3WYgbDLiwu1VzKHX6ItfFNEk45km+QTi07+pDKcKNd1k0mxqpLd/PuZd5hRpPDDoKBb6i+mrCb2yF 128335905497646745013379107761994003743181143126608677203818152878840562628631384684712779135591095534911406031545494164782375276574093777950840330452805743803067864740000758175436633463846967335728314347497013853264454015790847388463800323796888198433722196292529074568758149650782323407298620158495364705413\nANwlxEkeqmqYTxw1ZwMi1v2wo4ntPaEYZYoTLTJQfa+kuIksnHW9va243HAiOixd+rviVdm1dEwzESBbX0wiJNtRBpP+bnRxy4xOBjNoOB0c/tfka5JVwu5eeskyHx4V3inLviUaj86Yck42n5NaJFMfBvhzVftZ/YF9WBITI8g6 154592850289860621115358362871905683265658659789986179554827712019629689749439795961607030363152337159590319622241556795951071651584979664762468782303706550885785493534656062553770262954861884613383561063525714923031691298088562054236178003658891902606245782350998076658704876516153027797371814038658244397114\n"
  },
  {
    "path": "test/data/test1-discover.txt",
    "content": "equiv\nStatus: 200 OK\nContent-Type: text/html\n\n<html>\n<head>\n<meta http-equiv=\"YADIS_HEADER\" content=\"URL_BASE/xrds\">\n<title>Joe Schmoe's Homepage</title>\n</head>\n<body>\n<h1>Joe Schmoe's Homepage</h1>\n<p>Blah blah blah blah blah blah blah</p>\n</body>\n</html>\n\f\nheader\nStatus: 200 OK\nContent-Type: text/html\nYADIS_HEADER: URL_BASE/xrds\n\n<html>\n<head>\n<title>Joe Schmoe's Homepage</title>\n</head>\n<body>\n<h1>Joe Schmoe's Homepage</h1>\n<p>Blah blah blah blah blah blah blah</p>\n</body>\n\f\nxrds\nStatus: 200 OK\nContent-Type: application/xrds+xml\n\n<XRDS Content>\n\f\nxrds_ctparam\nStatus: 200 OK\nContent-Type: application/xrds+xml; charset=UTF8\n\n<XRDS Content>\n\f\nxrds_ctcase\nStatus: 200 OK\nContent-Type: appliCATION/XRDS+xml\n\n<XRDS Content>\n\f\nxrds_html\nStatus: 200 OK\nContent-Type: text/html\n\n<XRDS Content>\n\f\nredir_equiv\nStatus: 302 Found\nContent-Type: text/plain\nLocation: URL_BASE/equiv\n\nYou are presently being redirected.\n\f\nredir_header\nStatus: 302 Found\nContent-Type: text/plain\nLocation: URL_BASE/header\n\nYou are presently being redirected.\n\f\nredir_xrds\nStatus: 302 Found\nContent-Type: application/xrds+xml\nLocation: URL_BASE/xrds\n\n<XRDS Content>\n\f\nredir_xrds_html\nStatus: 302 Found\nContent-Type: text/plain\nLocation: URL_BASE/xrds_html\n\nYou are presently being redirected.\n\f\nredir_redir_equiv\nStatus: 302 Found\nContent-Type: text/plain\nLocation: URL_BASE/redir_equiv\n\nYou are presently being redirected.\n\f\nlowercase_header\nStatus: 200 OK\nContent-Type: text/html\nx-xrds-location: URL_BASE/xrds\n\n<html>\n<head>\n<title>Joe Schmoe's Homepage</title>\n</head>\n<body>\n<h1>Joe Schmoe's Homepage</h1>\n<p>Blah blah blah blah blah blah blah</p>\n</body>\n\f\n404_server_response\nStatus: 404 Not Found\n\nEEk!\n\f\n500_server_response\nStatus: 500 Server error\n\nEEk!\n\f\n201_server_response\nStatus: 201 Created\n\nEEk!\n\f\n404_with_header\nStatus: 404 Not Found\nYADIS_HEADER: URL_BASE/xrds\n\nEEk!\n\f\n404_with_meta\nStatus: 404 Not Found\nContent-Type: text/html\n\n<html>\n<head>\n<meta http-equiv=\"YADIS_HEADER\" content=\"URL_BASE/xrds\">\n<title>Joe Schmoe's Homepage</title>\n</head>\n<body>\n<h1>Joe Schmoe's Homepage</h1>\n<p>Blah blah blah blah blah blah blah</p>\n</body>\n</html>\n"
  },
  {
    "path": "test/data/test1-parsehtml.txt",
    "content": "found\n<!-- minimal well-formed success case -->\n<html><head><meta http-equiv=\"X-XRDS-Location\" content=\"found\"></head></html>\n\f\nfound\n<!-- minimal well-formed success case, xhtml closing, whitespace -->\n<html><head><meta http-equiv=\"X-XRDS-Location\" content=\"found\" /></head></html>\n\f\nfound\n<!-- minimal well-formed success case, xhtml closing, no whitespace -->\n<html><head><meta http-equiv=\"X-XRDS-Location\" content=\"found\"/></head></html>\n\f\nfound\n<!-- minimal success case -->\n<html><head><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nfound\n<!-- ignore bogus top-level tags -->\n</porky><html><head><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nfound\n<!-- Case folding for header name -->\n<html><head><meta http-equiv=\"x-xrds-location\" content=\"found\">\n\f\nfound\n<!-- missing <html> tag -->\n<head><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nfound\n<!-- javascript in head -->\n<html><head><script type=\"text/javascript\">document.write(\"<body>\");</script><META http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- no close script tag in head -->\n<html><head><script type=\"text/javascript\">document.write(\"<body>\");<META http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nfound\n<!-- case folding for tag names -->\n<html><head><META http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nfound\n<!-- Stop after first one found -->\n<html><head>\n<meta http-equiv=\"x-xrds-location\" content=\"found\">\n<meta http-equiv=\"x-xrds-location\" content=\"not-found\">\n\f\n&\n<!-- standard entity -->\n<head><meta http-equiv=\"X-XRDS-Location\" content=\"&amp;\">\n\f\nfound\n<!-- hex entity -->\n<html>\n  <head>\n    <meta http-equiv=\"X-XRDS-Location\" content=\"&#x66;ound\">\n  </head>\n</html>\n\f\nfound\n<!-- decimal entity -->\n<html>\n  <head>\n    <meta http-equiv=\"X-XRDS-Location\" content=\"&#102;ound\">\n  </head>\n</html>\n\f\n/\n<!-- hex entity -->\n<html>\n  <head>\n    <meta http-equiv=\"X-XRDS-Location\" content=\"&#x2f;\">\n  </head>\n</html>\n\f\n\n<!-- empty string -->\n<html><head><meta http-equiv=\"X-XRDS-Location\" content=\"\">\n\f\nEOF\n<!-- No markup, except this comment -->\n\f\nNone\n<!-- No meta, just standard HTML -->\n<html>\n  <head>\n    <title>A boring document</title>\n  </head>\n  <body>\n    <h1>A boring document</h1>\n    <p>There's really nothing interesting about this</p>\n  </body>\n</html>\n\f\nEOF\n<!-- No <html> or <head> -->\n<meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nEOF\n<!-- No <head> tag -->\n<html><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- No <html> or <head> and a <body> -->\n<body><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- <head> and <html> reversed -->\n<head><html><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- <meta> is inside of <body> -->\n<html><head><body><meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- <meta> is inside comment -->\n<html>\n  <head>\n    <!--<meta http-equiv=\"X-XRDS-Location\" content=\"found\">-->\n  </head>\n</html>\n\f\nNone\n<!-- <meta> is inside of <body> -->\n<html>\n  <head>\n    <title>Someone's blog</title>\n  </head>\n  <body>\n    <h1>My blog</h1>\n    <p>This is my blog</p>\n    <h2>Comments</h2>\n    <p><meta http-equiv=\"X-XRDS-Location\" content=\"found\"></p>\n  </body>\n</html>\n\f\nNone\n<!-- short head tag -->\n<html><head/>\n<meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- <body> comes first -->\n<body><html><head>\n<meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!-- </body> comes first -->\n</body><html><head>\n<meta http-equiv=\"X-XRDS-Location\" content=\"found\">\n\f\nNone\n<!bad processing instruction!>\n"
  },
  {
    "path": "test/data/test_discover/malformed_meta_tag.html",
    "content": "<html>\n<head>\n<title />\n\n<link rel=\"openid.server\"\n\thref=\"http://www.myopenid.com/server\" />\n<link rel=\"openid.delegate\"\n\thref=\"http://user.myopenid.com/\" />\n<link rel=\"openid2.local_id\"\n\thref=\"http://user.myopenid.com/\" />\n<link rel=\"openid2.provider\"\n\thref=\"http://www.myopenid.com/server\" />\n<meta http-equiv=\"X-XRDS-Location\"\n\thttp://www.myopenid.com/xrds?username=user.myopenid.com\" />\n\n</head>\n<body>\n</body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/openid.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <title>Identity Page for Smoker</title>\n    <link rel=\"openid.server\" href=\"http://www.myopenid.com/server\" />\n    <link rel=\"openid.delegate\" href=\"http://smoker.myopenid.com/\" />\n  </head>\n  <body>\n    <p>foo</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/openid2.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <title>Identity Page for Smoker</title>\n    <link rel=\"openid2.provider\" href=\"http://www.myopenid.com/server\" />\n    <link rel=\"openid2.local_id\" href=\"http://smoker.myopenid.com/\" />\n  </head>\n  <body>\n    <p>foo</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/openid2_xrds.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           >\n  <XRD>\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/signon</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <LocalID>http://smoker.myopenid.com/</LocalID>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/openid2_xrds_no_local_id.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           >\n  <XRD>\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/signon</Type>\n      <URI>http://www.myopenid.com/server</URI>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/openid_1_and_2.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <title>Identity Page for Smoker</title>\n    <link rel=\"openid2.provider openid.server\" href=\"http://www.myopenid.com/server\" />\n    <link rel=\"openid2.local_id openid.delegate\" href=\"http://smoker.myopenid.com/\" />\n  </head>\n  <body>\n    <p>foo</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/openid_1_and_2_xrds.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/signon</Type>\n      <Type>http://openid.net/signon/1.1</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <LocalID>http://smoker.myopenid.com/</LocalID>\n      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/openid_1_and_2_xrds_bad_delegate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/signon</Type>\n      <Type>http://openid.net/signon/1.0</Type>\n      <Type>http://openid.net/signon/1.1</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <LocalID>http://smoker.myopenid.com/</LocalID>\n      <openid:Delegate>http://localid.mismatch.invalid/</openid:Delegate>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/openid_and_yadis.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <title>Identity Page for Smoker</title>\n    <meta http-equiv=\"X-XRDS-Location\" content=\"http://someuser.unittest/xrds\" />\n    <link rel=\"openid.server\" href=\"http://www.myopenid.com/server\" />\n    <link rel=\"openid.delegate\" href=\"http://smoker.myopenid.com/\" />\n  </head>\n  <body>\n    <p>foo</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/openid_no_delegate.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <title>Identity Page for Smoker</title>\n    <link rel=\"openid.server\" href=\"http://www.myopenid.com/server\" />\n  </head>\n  <body>\n    <p>foo</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/openid_utf8.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <title>Identity Page for Smoker</title>\n    <link rel=\"openid.server\" href=\"http://www.myopenid.com/server\" />\n    <link rel=\"openid.delegate\" href=\"http://smoker.myopenid.com/\" />\n  </head>\n  <body>\n    <p>こんにちは</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/data/test_discover/yadis_0entries.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n    <Service >\n      <Type>http://is-not-openid.unittest/</Type>\n      <URI>http://noffing.unittest./</URI>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_2_bad_local_id.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/signon</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <LocalID>http://smoker.myopenid.com/</LocalID>\n      <LocalID>http://localid.mismatch.invalid/</LocalID>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_2entries_delegate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n    <CanonicalID>=!1000</CanonicalID>\n\n    <Service priority=\"10\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>\n    </Service>\n\n    <Service priority=\"20\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://www.livejournal.com/openid/server.bml</URI>\n      <openid:Delegate>http://frank.livejournal.com/</openid:Delegate>\n    </Service>\n\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_2entries_idp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n    <CanonicalID>=!1000</CanonicalID>\n\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/signon</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <openid:LocalID>http://smoker.myopenid.com/</openid:LocalID>\n    </Service>\n\n    <Service priority=\"20\">\n      <Type>http://specs.openid.net/auth/2.0/server</Type>\n      <URI>http://www.livejournal.com/openid/server.bml</URI>\n    </Service>\n\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_another_delegate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n\n    <Service priority=\"10\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://vroom.unittest/server</URI>\n      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_idp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n    <Service priority=\"10\">\n      <Type>http://specs.openid.net/auth/2.0/server</Type>\n      <URI>http://www.myopenid.com/server</URI>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_idp_delegate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\"\n           >\n  <XRD>\n    <Service>\n      <Type>http://specs.openid.net/auth/2.0/server</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_discover/yadis_no_delegate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           >\n  <XRD>\n    <Service priority=\"10\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://www.myopenid.com/server</URI>\n    </Service>\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/=j3h.2007.11.14.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://=j3h\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*j3h</Query>\n  <Status code=\"100\"/>\n  <Expires>2007-11-15T01:35:07.000Z</Expires>\n  <ProviderID>xri://=</ProviderID>\n  <LocalID priority=\"10\">!378C.2F61.25D6.F7EB</LocalID>\n  <CanonicalID priority=\"10\">=!378C.2F61.25D6.F7EB</CanonicalID>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"2\">http://2idi.com/openid/</URI>\n   <URI append=\"qxri\" priority=\"1\">https://2idi.com/openid/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">xri://+i-service*(+contact)*($v*1.0)</Type>\n   <Type match=\"default\"/>\n   <ProviderID/>\n   <Path select=\"true\">(+contact)</Path>\n   <Path match=\"null\"/>\n   <URI append=\"qxri\" priority=\"1\">http://2idi.com/contact/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/README",
    "content": "delegated-20060809.xrds    - results from proxy.xri.net, determined by \n                             Drummond and Kevin to be incorrect.\ndelegated-20060809-r1.xrds - Drummond's 1st correction\ndelegated-20060809-r2.xrds - Drummond's 2nd correction\n\nspoofs: keturn's (=!E4)'s attempts to log in with Drummond's i-number (=!D2)\nspoof1.xrds\nspoof2.xrds\nspoof3.xrds - attempt to steal @!C0!D2 by having \"at least one\" CanonicalID\n    match the $res service ProviderID.\n\nref.xrds - resolving @ootao*test.ref, which refers to a neustar XRI.\n"
  },
  {
    "path": "test/data/test_xrds/delegated-20060809-r1.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://@ootao*test1\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*ootao</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-09T22:07:13.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!5BAD.2AA.3C72.AF46</LocalID>\n  <CanonicalID priority=\"10\">@!5BAD.2AA.3C72.AF46</CanonicalID>\n  <Service priority=\"10\">\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <ProviderID>xri://!!1003</ProviderID>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/@ootao/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*test1</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://!!1003</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA01</LocalID>\n  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/delegated-20060809-r2.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://@ootao*test1\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*ootao</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-09T22:07:13.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!5BAD.2AA.3C72.AF46</LocalID>\n  <CanonicalID priority=\"10\">@!5BAD.2AA.3C72.AF46</CanonicalID>\n  <Service priority=\"10\">\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/@ootao/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*test1</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA01</LocalID>\n  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/delegated-20060809.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://@ootao*test1\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*ootao</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-09T22:07:13.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!5BAD.2AA.3C72.AF46</LocalID>\n  <CanonicalID priority=\"10\">@!5BAD.2AA.3C72.AF46</CanonicalID>\n  <Service priority=\"10\">\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <ProviderID/>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/@ootao/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*test1</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://!!1003</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA01</LocalID>\n  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/no-xrd.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS\n    xmlns:xrds=\"xri://$xrds\"\n    xmlns:openid=\"http://openid.net/xmlns/1.0\"\n    xmlns:typekey=\"http://typekey.com/xmlns/1.0\"\n    xmlns=\"xri://$xrd*($v*2.0)\">\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/not-xrds.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<x></x>\n"
  },
  {
    "path": "test/data/test_xrds/prefixsometimes.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://@ootao*test1\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*ootao</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-09T22:07:13.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!5BAD.2AA.3C72.AF46</LocalID>\n  <CanonicalID priority=\"10\">@!5BAD.2AA.3C72.AF46</CanonicalID>\n  <Service priority=\"10\">\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/@ootao/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*test1</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA01</LocalID>\n  <CanonicalID>xri://@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/ref.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://@ootao*test.ref\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*ootao</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-15T18:56:09.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!5BAD.2AA.3C72.AF46</LocalID>\n  <CanonicalID priority=\"10\">@!5BAD.2AA.3C72.AF46</CanonicalID>\n  <Service priority=\"10\">\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <ProviderID/>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/@ootao/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*test.ref</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://!!1003</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA03</LocalID>\n  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA03</CanonicalID>\n  <Ref>@!BAE.A650.823B.2475</Ref>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRDS ref=\"xri://@!BAE.A650.823B.2475\" xmlns=\"xri://$xrds\">\n  <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n   <Query>!BAE.A650.823B.2475</Query>\n   <Status code=\"100\"/>\n   <Expires>2006-08-15T18:56:10.000Z</Expires>\n   <ProviderID>xri://@</ProviderID>\n   <LocalID priority=\"10\">!BAE.A650.823B.2475</LocalID>\n   <CanonicalID priority=\"10\">@!BAE.A650.823B.2475</CanonicalID>\n   <Service priority=\"10\">\n    <Type select=\"true\">(+wdnc)</Type>\n    <ProviderID/>\n    <Path select=\"true\">(+wdnc)</Path>\n    <URI append=\"none\" priority=\"10\">http://www.tcpacompliance.us</URI>\n   </Service>\n   <Service priority=\"10\">\n    <Type match=\"content\" select=\"true\">xri://$res*auth*($v*2.0)</Type>\n    <ProviderID/>\n    <MediaType match=\"content\" select=\"false\">application/xrds+xml;trust=none</MediaType>\n    <URI priority=\"10\">http://dev.dready.org/cgi-bin/xri</URI>\n   </Service>\n   <Service priority=\"10\">\n    <Type match=\"content\" select=\"true\">(+i-name)</Type>\n    <ProviderID/>\n    <Path match=\"content\" select=\"true\">(+i-name)</Path>\n    <URI append=\"none\" priority=\"10\">http://www.inames.net</URI>\n   </Service>\n   <Service priority=\"10\">\n    <Type select=\"true\">xri://+i-service*(+contact)*($v*1.0)</Type>\n    <Type match=\"default\" select=\"false\"/>\n    <ProviderID>xri://!!1001</ProviderID>\n    <Path select=\"true\">(+contact)</Path>\n    <Path match=\"null\" select=\"false\"/>\n    <MediaType select=\"false\">text/html</MediaType>\n    <MediaType match=\"default\" select=\"false\"/>\n    <URI append=\"none\" priority=\"10\">http://www.neustar.biz</URI>\n   </Service>\n  </XRD>\n </XRDS>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>!BAE.A650.823B.2475</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-15T18:56:10.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!BAE.A650.823B.2475</LocalID>\n  <CanonicalID priority=\"10\">@!BAE.A650.823B.2475</CanonicalID>\n  <Service priority=\"10\">\n   <Type select=\"true\">(+wdnc)</Type>\n   <ProviderID/>\n   <Path select=\"true\">(+wdnc)</Path>\n   <URI append=\"none\" priority=\"10\">http://www.tcpacompliance.us</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type match=\"content\" select=\"true\">xri://$res*auth*($v*2.0)</Type>\n   <ProviderID/>\n   <MediaType match=\"content\" select=\"false\">application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://dev.dready.org/cgi-bin/xri</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type match=\"content\" select=\"true\">(+i-name)</Type>\n   <ProviderID/>\n   <Path match=\"content\" select=\"true\">(+i-name)</Path>\n   <URI append=\"none\" priority=\"10\">http://www.inames.net</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">xri://+i-service*(+contact)*($v*1.0)</Type>\n   <Type match=\"default\" select=\"false\"/>\n   <ProviderID>xri://!!1001</ProviderID>\n   <Path select=\"true\">(+contact)</Path>\n   <Path match=\"null\" select=\"false\"/>\n   <MediaType select=\"false\">text/html</MediaType>\n   <MediaType match=\"default\" select=\"false\"/>\n   <URI append=\"none\" priority=\"10\">http://www.neustar.biz</URI>\n  </Service>\n </XRD>\n</XRDS>"
  },
  {
    "path": "test/data/test_xrds/sometimesprefix.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://@ootao*test1\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*ootao</Query>\n  <Status code=\"100\"/>\n  <Expires>2006-08-09T22:07:13.000Z</Expires>\n  <ProviderID>xri://@</ProviderID>\n  <LocalID priority=\"10\">!5BAD.2AA.3C72.AF46</LocalID>\n  <CanonicalID priority=\"10\">xri://@!5BAD.2AA.3C72.AF46</CanonicalID>\n  <Service priority=\"10\">\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/@ootao/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*test1</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://@!5BAD.2AA.3C72.AF46</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA01</LocalID>\n  <CanonicalID>@!5BAD.2AA.3C72.AF46!0000.0000.3B9A.CA01</CanonicalID>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID/>\n   <URI append=\"qxri\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/spoof1.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://=keturn*isDrummond\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n <Query>*keturn</Query>\n <ProviderID>xri://=</ProviderID>\n <LocalID>!E4</LocalID>\n <CanonicalID>=!E4</CanonicalID>\n\n <Service>\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <URI>http://keturn.example.com/resolve/</URI>\n   <ProviderID>=!E4</ProviderID>\n </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*isDrummond</Query>\n  <ProviderID>=!E4</ProviderID>\n  <LocalID>!D2</LocalID>\n  <CanonicalID>=!D2</CanonicalID>\n  <Service>\n    <Type>http://openid.net/signon/1.0</Type>\n    <URI>http://keturn.example.com/openid</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/spoof2.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://=keturn*isDrummond\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n <Query>*keturn</Query>\n <ProviderID>xri://=</ProviderID>\n <LocalID>!E4</LocalID>\n <CanonicalID>=!E4</CanonicalID>\n\n <Service>\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <URI>http://keturn.example.com/resolve/</URI>\n   <ProviderID>xri://=</ProviderID>\n </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*isDrummond</Query>\n  <ProviderID>xri://=</ProviderID>\n  <LocalID>!D2</LocalID>\n  <CanonicalID>=!D2</CanonicalID>\n  <Service>\n    <Type>http://openid.net/signon/1.0</Type>\n    <URI>http://keturn.example.com/openid</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/spoof3.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://=keturn*isDrummond\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n <Query>*keturn</Query>\n <ProviderID>xri://@</ProviderID>\n <LocalID>@E4</LocalID>\n <CanonicalID>@!E4</CanonicalID>\n\n <Service>\n   <Type>xri://$res*auth*($v*2.0)</Type>\n   <URI>http://keturn.example.com/resolve/</URI>\n   <ProviderID>@!E4</ProviderID>\n </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*is</Query>\n  <ProviderID>@!E4</ProviderID>\n  <LocalID>!D2</LocalID>\n  <CanonicalID>=!C0</CanonicalID>\n  <CanonicalID>=!E4!01</CanonicalID>\n  <Service>\n    <Type>xri://$res*auth*($v*2.0)</Type>\n    <URI>http://keturn.example.com/resolve/</URI>\n    <ProviderID>@!C0</ProviderID>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*drummond</Query>\n  <ProviderID>@!C0</ProviderID>\n  <LocalID>!D2</LocalID>\n  <CanonicalID>@!C0!D2</CanonicalID>\n  <Service>\n    <Type>http://openid.net/signon/1.0</Type>\n    <URI>http://keturn.example.com/openid</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/status222.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://=x\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*x</Query>\n  <Status code=\"222\">The subsegment does not exist</Status>\n  <Expires>2006-08-18T00:02:35.000Z</Expires>\n  <ProviderID>xri://=</ProviderID>\n </XRD>\n</XRDS>"
  },
  {
    "path": "test/data/test_xrds/subsegments.xrds",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XRDS ref=\"xri://=nishitani*masaki\" xmlns=\"xri://$xrds\">\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*nishitani</Query>\n  <Status code=\"100\"/>\n  <Expires>2007-12-25T11:33:39.000Z</Expires>\n  <ProviderID>xri://=</ProviderID>\n  <LocalID priority=\"10\">!E117.EF2F.454B.C707</LocalID>\n  <CanonicalID priority=\"10\">=!E117.EF2F.454B.C707</CanonicalID>\n  <Service priority=\"10\">\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID>xri://!!1003!103</ProviderID>\n   <URI append=\"none\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n  <Service priority=\"10\">\n   <Type select=\"true\">xri://$res*auth*($v*2.0)</Type>\n   <ProviderID>xri://!!1003!103</ProviderID>\n   <MediaType>application/xrds+xml;trust=none</MediaType>\n   <URI priority=\"10\">http://resolve.ezibroker.net/resolve/=nishitani/</URI>\n  </Service>\n  <Service priority=\"1\">\n   <Type match=\"content\" select=\"true\">xri://+i-service*(+forwarding)*($v*1.0)</Type>\n   <Type match=\"null\" select=\"false\"/>\n   <ProviderID>xri://!!1003!103</ProviderID>\n   <Path match=\"content\">(+index)</Path>\n   <Path match=\"default\"/>\n   <URI append=\"qxri\" priority=\"1\">http://linksafe-forward.ezibroker.net/forwarding/</URI>\n  </Service>\n </XRD>\n <XRD xmlns=\"xri://$xrd*($v*2.0)\">\n  <Query>*masaki</Query>\n  <Status code=\"100\">SUCCESS</Status>\n  <ProviderID>xri://!!1003</ProviderID>\n  <LocalID>!0000.0000.3B9A.CA01</LocalID>\n  <CanonicalID>=!E117.EF2F.454B.C707!0000.0000.3B9A.CA01</CanonicalID>\n  <Service>\n   <Type select=\"true\">http://openid.net/signon/1.0</Type>\n   <ProviderID>xri://!!1003!103</ProviderID>\n   <URI append=\"none\" priority=\"1\">https://linksafe.ezibroker.net/server/</URI>\n  </Service>\n  <Service>\n   <Type select=\"true\">xri://+i-service*(+contact)*($v*1.0)</Type>\n   <Type match=\"null\"/>\n   <ProviderID>xri://!!1003!103</ProviderID>\n   <Path select=\"true\">(+contact)</Path>\n   <Path match=\"null\"/>\n   <URI append=\"authority\" priority=\"1\">http://linksafe-contact.ezibroker.net/contact/</URI>\n  </Service>\n  <Service priority=\"1\">\n   <Type match=\"content\" select=\"true\">xri://+i-service*(+forwarding)*($v*1.0)</Type>\n   <Type match=\"null\" select=\"false\"/>\n   <ProviderID>xri://!!1003!103</ProviderID>\n   <Path match=\"content\">(+index)</Path>\n   <Path match=\"default\"/>\n   <URI append=\"qxri\" priority=\"1\">http://linksafe-forward.ezibroker.net/forwarding/</URI>\n  </Service>\n </XRD>\n</XRDS>\n"
  },
  {
    "path": "test/data/test_xrds/valid-populated-xrds.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS\n    xmlns:xrds=\"xri://$xrds\"\n    xmlns:openid=\"http://openid.net/xmlns/1.0\"\n    xmlns:typekey=\"http://typekey.com/xmlns/1.0\"\n    xmlns=\"xri://$xrd*($v*2.0)\">\n  <XRD>\n\n    <Service priority=\"0\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://www.myopenid.com/server</URI>\n      <openid:Delegate>http://josh.myopenid.com/</openid:Delegate>\n    </Service>\n\n    <Service priority=\"20\">\n      <Type>http://lid.netmesh.org/sso/2.0b5</Type>\n      <Type>http://lid.netmesh.org/2.0b5</Type>\n      <URI>http://mylid.net/josh</URI>\n    </Service>\n\n    <Service priority=\"10\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://www.livejournal.com/openid/server.bml</URI>\n      <openid:Delegate>http://www.livejournal.com/users/nedthealpaca/</openid:Delegate>\n    </Service>\n\n    <Service priority=\"15\">\n      <Type>http://typekey.com/services/1.0</Type>\n      <typekey:MemberName>joshhoyt</typekey:MemberName>\n    </Service>\n\n    <Service priority=\"5\">\n      <Type>http://openid.net/signon/1.0</Type>\n      <URI>http://www.schtuff.com/openid</URI>\n      <openid:Delegate>http://users.schtuff.com/josh</openid:Delegate>\n    </Service>\n\n  </XRD>\n</xrds:XRDS>\n"
  },
  {
    "path": "test/data/trustroot.txt",
    "content": "========================================\nTrust root parsing checking\n========================================\n\n----------------------------------------\n23: Does not parse\n----------------------------------------\nbaz.org\n*.foo.com\nhttp://*.schtuff.*/\nftp://foo.com\nftp://*.foo.com\nhttp://*.foo.com:80:90/\nfoo.*.com\nhttp://foo.*.com\nhttp://www.*\nhttp://*foo.com/\nhttp://foo.com/invalid#fragment\nhttp://..it/\nhttp://.it/\nhttp://*:8081/\nhttp://*:80\nhttp://localhost:1900foo/\nhttp://foo.com\\/\nhttp://π.pi.com/\nhttp://lambda.com/Λ\n\n\n\n5\n\n----------------------------------------\n14: Insane\n----------------------------------------\nhttp:///\nhttp://*/\nhttps://*/\nhttp://*.com\nhttp://*.com/\nhttps://*.com/\nhttp://*.com.au/\nhttp://*.co.uk/\nhttp://*.foo.notatld/\nhttps://*.foo.notatld/\nhttp://*.museum/\nhttps://*.museum/\nhttp://www.schtuffcom/\nhttp://it/\n\n----------------------------------------\n18: Sane\n----------------------------------------\nhttp://*.schtuff.com./\nhttp://*.schtuff.com/\nhttp://*.foo.schtuff.com/\nhttp://*.schtuff.com\nhttp://www.schtuff.com/\nhttp://www.schtuff.com./\nhttp://www.schutff.com\nhttp://*.this.that.schtuff.com/\nhttp://*.foo.com/path\nhttp://*.foo.com/path?action=foo2\nhttp://x.foo.com/path?action=foo2\nhttp://x.foo.com/path?action=%3D\nhttp://localhost:8081/\nhttp://localhost:8082/?action=openid\nhttps://foo.com/\nhttp://kink.fm/should/be/sane\nhttp://beta.lingu.no/\nhttp://goathack.livejournal.org:8020/openid/login.bml\n\n========================================\nreturn_to matching\n========================================\n\n----------------------------------------\n46: matches\n----------------------------------------\nhttp://*/                             http://cnn.com/\nhttp://*/                             http://livejournal.com/\nhttp://*/                             http://met.museum/\nhttp://localhost:8081/x?action=openid http://localhost:8081/x?action=openid\nhttp://*.foo.com                      http://b.foo.com\nhttp://*.foo.com                      http://b.foo.com/\nhttp://*.foo.com/                     http://b.foo.com\nhttp://b.foo.com                      http://b.foo.com\nhttp://b.foo.com                      http://b.foo.com/\nhttp://b.foo.com/                     http://b.foo.com\nhttp://*.b.foo.com                    http://b.foo.com\nhttp://*.b.foo.com                    http://b.foo.com/\nhttp://*.b.foo.com/                   http://b.foo.com\nhttp://*.b.foo.com                    http://x.b.foo.com\nhttp://*.b.foo.com                    http://w.x.b.foo.com\nhttp://*.bar.co.uk                    http://www.bar.co.uk\nhttp://*.uoregon.edu                  http://x.cs.uoregon.edu\nhttp://x.com/abc                      http://x.com/abc\nhttp://x.com/abc                      http://x.com/abc/def\nhttp://10.0.0.1/abc                   http://10.0.0.1/abc\nhttp://*.x.com                        http://x.com/gallery\nhttp://*.x.com                        http://foo.x.com/gallery\nhttp://foo.x.com                      http://foo.x.com/gallery/xxx\nhttp://*.x.com/gallery                http://foo.x.com/gallery\nhttp://localhost:8082/?action=openid  http://localhost:8082/?action=openid\nhttp://goathack.livejournal.org:8020/ http://goathack.livejournal.org:8020/openid/login.bml\nhttps://foo.com                       https://foo.com\nhttp://Foo.com                        http://foo.com\nhttp://foo.com                        http://Foo.com\nhttp://foo.com:80/                    http://foo.com/\nhttp://foo.com/?x=y                   http://foo.com/?x=y&a=b\nhttp://foo.com/x                      http://foo.com/x?y\nhttp://mylid.net/j3h.                 http://mylid.net/j3h.?x=y\nhttp://j3h.us                         http://j3h.us?ride=unicycle\nhttps://www.filmclans.com:443/mattmartin/FilmClans https://www.filmclans.com/mattmartin/FilmClans/Logon.aspx?nonce=BVjqSOee\nhttp://foo.com:80                     http://foo.com\nhttp://foo.com                        http://foo.com:80\nhttp://foo.com                        http://foo.com/\nhttp://foo.com/                       http://foo.com\nhttp://foo.com/                       http://foo.com:80\nhttp://foo.com:80/                    http://foo.com:80/stuff\nhttp://foo.com:80/                    http://foo.com/stuff\nhttp://foo.com/path                   http://foo.com/path/extra\nhttp://foo.com/path2                  http://foo.com/path2?extra=query\nhttp://foo.com/path2                  http://foo.com/path2/?extra=query\nhttp://foo.com/                       HTTP://foo.com/\n\n----------------------------------------\n25: does not match\n----------------------------------------\nhttp://*/                             ftp://foo.com/\nhttp://*/                             xxx\nhttp://foo.com/                       http://oo.com/\nhttp://*.x.com/abc                    http://foo.x.com\nhttp://*.x.com/abc                    http://*.x.com\nhttp://*.com/                         http://*.com/\nhttp://x.com/abc                      http://x.com/\nhttp://x.com/abc                      http://x.com/a\nhttp://x.com/abc                      http://x.com/ab\nhttp://x.com/abc                      http://x.com/abcd\nhttp://*.cs.uoregon.edu               http://x.uoregon.edu\nhttp://*.foo.com                      http://bar.com\nhttp://*.foo.com                      http://www.bar.com\nhttp://*.bar.co.uk                    http://xxx.co.uk\nhttps://foo.com                       http://foo.com\nhttp://foo.com                        https://foo.com\nhttp://foo.com:81                     http://foo.com:80\nhttp://foo.com/?a=b                   http://foo.com/?x=y\nhttp://foo.com/?a=b                   http://foo.com/?x=y&a=b\nhttp://foo.com/?a=b                   http://foo.com/\nhttp://*.oo.com/                      http://foo.com/\nhttp://foo.com/*                      http://foo.com/anything\nhttp://foo.com                        http://foo.com:443\nhttps://foo.com                       https://foo.com:80\nhttp://foo.com/path/xev               http://foo.com/path?extra=more\n"
  },
  {
    "path": "test/data/urinorm.txt",
    "content": "Already normal form\nhttp://example.com/\nhttp://example.com/\n\nAdd a trailing slash\nhttp://example.com\nhttp://example.com/\n\nRemove an empty port segment\nhttp://example.com:/\nhttp://example.com/\n\nRemove a default port segment\nhttp://example.com:80/\nhttp://example.com/\n\nCapitalization in host names\nhttp://wWw.exaMPLE.COm/\nhttp://www.example.com/\n\nCapitalization in scheme names\nhtTP://example.com/\nhttp://example.com/\n\nCapitalization in percent-escaped reserved characters\nhttp://example.com/foo%2cbar\nhttp://example.com/foo%2Cbar\n\nUnescape percent-encoded unreserved characters\nhttp://example.com/foo%2Dbar%2dbaz\nhttp://example.com/foo-bar-baz\n\nremove_dot_segments example 1\nhttp://example.com/a/b/c/./../../g\nhttp://example.com/a/g\n\nremove_dot_segments example 2\nhttp://example.com/mid/content=5/../6\nhttp://example.com/mid/6\n\nremove_dot_segments: single-dot\nhttp://example.com/a/./b\nhttp://example.com/a/b\n\nremove_dot_segments: double-dot\nhttp://example.com/a/../b\nhttp://example.com/b\n\nremove_dot_segments: leading double-dot\nhttp://example.com/../b\nhttp://example.com/b\n\nremove_dot_segments: trailing single-dot\nhttp://example.com/a/.\nhttp://example.com/a/\n\nremove_dot_segments: trailing double-dot\nhttp://example.com/a/..\nhttp://example.com/\n\nremove_dot_segments: trailing single-dot-slash\nhttp://example.com/a/./\nhttp://example.com/a/\n\nremove_dot_segments: trailing double-dot-slash\nhttp://example.com/a/../\nhttp://example.com/\n\nTest of all kinds of syntax-based normalization\nhTTPS://a/./b/../b/%63/%7bfoo%7d\nhttps://a/b/c/%7Bfoo%7D\n\nUnsupported scheme\nftp://example.com/\nfail\n\nNon-absolute URI\nhttp:/foo\nfail"
  },
  {
    "path": "test/discoverdata.rb",
    "content": "\nrequire 'uri'\nrequire 'openid/yadis/constants'\nrequire 'openid/yadis/discovery'\nrequire 'openid/util'\n\nmodule OpenID\n\n  module DiscoverData\n\n    include TestDataMixin\n    include Util\n\n    TESTLIST = [\n                # success,  input_name,          id_name,            result_name\n                [true,  \"equiv\",             \"equiv\",            \"xrds\"],\n                [true,  \"header\",            \"header\",           \"xrds\"],\n                [true,  \"lowercase_header\",  \"lowercase_header\", \"xrds\"],\n                [true,  \"xrds\",              \"xrds\",             \"xrds\"],\n                [true,  \"xrds_ctparam\",      \"xrds_ctparam\",     \"xrds_ctparam\"],\n                [true,  \"xrds_ctcase\",       \"xrds_ctcase\",      \"xrds_ctcase\"],\n                [false, \"xrds_html\",         \"xrds_html\",        \"xrds_html\"],\n                [true,  \"redir_equiv\",       \"equiv\",            \"xrds\"],\n                [true,  \"redir_header\",      \"header\",           \"xrds\"],\n                [true,  \"redir_xrds\",        \"xrds\",             \"xrds\"],\n                [false, \"redir_xrds_html\",   \"xrds_html\",        \"xrds_html\"],\n                [true,  \"redir_redir_equiv\", \"equiv\",            \"xrds\"],\n                [false, \"404_server_response\", nil,             nil],\n                [false, \"404_with_header\",     nil,             nil],\n                [false, \"404_with_meta\",       nil,             nil],\n                [false, \"201_server_response\", nil,             nil],\n                [false, \"500_server_response\", nil,             nil],\n             ]\n\n    @@example_xrds_file = 'example-xrds.xml'\n    @@default_test_file = 'test1-discover.txt'\n    @@discover_tests = {}\n\n    def readTests(filename)\n      data = read_data_file(filename, false)\n      tests = {}\n      data.split(\"\\f\\n\", -1).each { |case_|\n        name, content = case_.split(\"\\n\", 2)\n        tests[name] = content\n      }\n\n      return tests\n    end\n\n    def getData(filename, name)\n      if !@@discover_tests.member?(filename)\n        @@discover_tests[filename] = readTests(filename)\n      end\n\n      file_tests = @@discover_tests[filename]\n      return file_tests[name]\n    end\n\n    def fillTemplate(test_name, template, base_url, example_xrds)\n      mapping = [\n                 ['URL_BASE/', base_url],\n                 ['<XRDS Content>', example_xrds],\n                 ['YADIS_HEADER', Yadis::YADIS_HEADER_NAME],\n                 ['NAME', test_name],\n                ]\n\n      mapping.each { |k, v|\n        template = template.gsub(/#{k}/, v)\n      }\n\n      return template\n    end\n\n    def generateSample(test_name, base_url,\n                       example_xrds=nil,\n                       filename=@@default_test_file)\n      if example_xrds.nil?\n        example_xrds = read_data_file(@@example_xrds_file, false)\n      end\n\n      begin\n        template = getData(filename, test_name)\n      rescue Errno::ENOENT\n        raise ArgumentError(filename)\n      end\n\n      return fillTemplate(test_name, template, base_url, example_xrds)\n    end\n\n    def generateResult(base_url, input_name, id_name, result_name, success)\n      uri = URI::parse(base_url)\n\n      input_url = (uri + input_name).to_s\n\n      # If the name is None then we expect the protocol to fail, which\n      # we represent by None\n      if id_name.nil?\n        Util.assert(result_name.nil?)\n        return input_url, DiscoveryFailure\n      end\n\n      result = generateSample(result_name, base_url)\n      headers, content = result.split(\"\\n\\n\", 2)\n      header_lines = headers.split(\"\\n\")\n\n      ctype = nil\n      header_lines.each { |header_line|\n        if header_line.start_with?('Content-Type:')\n          _, ctype = header_line.split(':', 2)\n          ctype = ctype.strip()\n          break\n        else\n          ctype = nil\n        end\n      }\n\n      id_url = (uri + id_name).to_s\n      result = Yadis::DiscoveryResult.new(input_url)\n      result.normalized_uri = id_url\n\n      if success\n        result.xrds_uri = (uri + result_name).to_s\n      end\n\n      result.content_type = ctype\n      result.response_text = content\n      return [input_url, result]\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_accept.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'openid/yadis/accept'\nrequire 'openid/util'\n\nmodule OpenID\n\n  class AcceptTest < Minitest::Test\n    include TestDataMixin\n\n    def getTestData()\n      # Read the test data off of disk\n      #\n      # () -> [(int, str)]\n      lines = read_data_file('accept.txt')\n      line_no = 1\n      return lines.collect { |line|\n        pair = [line_no, line]\n        line_no += 1\n        pair\n      }\n    end\n\n    def chunk(lines)\n      # Return groups of lines separated by whitespace or comments\n      #\n      # [(int, str)] -> [[(int, str)]]\n      chunks = []\n      chunk = []\n      lines.each { |lineno, line|\n        stripped = line.strip()\n        if (stripped == '') or stripped.start_with?('#')\n          if chunk.length > 0\n            chunks << chunk\n            chunk = []\n          end\n        else\n          chunk << [lineno, stripped]\n        end\n      }\n\n      if chunk.length > 0\n        chunks << chunk\n      end\n\n      return chunks\n    end\n\n    def parseLines(chunk)\n      # Take the given chunk of lines and turn it into a test data\n      # dictionary\n      #\n      # [(int, str)] -> {str:(int, str)}\n      items = {}\n      chunk.each { |lineno, line|\n        header, data = line.split(':', 2)\n        header = header.downcase\n        items[header] = [lineno, data.strip]\n      }\n      return items\n    end\n\n    def parseAvailable(available_text)\n      # Parse an Available: line's data\n      #\n      # str -> [str]\n      return available_text.split(',', -1).collect { |s| s.strip }\n    end\n\n    def parseExpected(expected_text)\n      # Parse an Expected: line's data\n      #\n      # str -> [(str, float)]\n      expected = []\n      if expected_text != ''\n        expected_text.split(',', -1).each { |chunk|\n          chunk = chunk.strip\n          mtype, qstuff = chunk.split(';', -1)\n          mtype = mtype.strip\n          Util.assert(!mtype.index('/').nil?)\n          qstuff = qstuff.strip\n          q, qstr = qstuff.split('=', -1)\n          Util.assert(q == 'q')\n          qval = qstr.to_f\n          expected << [mtype, qval]\n        }\n      end\n\n      return expected\n    end\n\n    def test_accept_headers\n      lines = getTestData()\n      chunks = chunk(lines)\n      data_sets = chunks.collect { |chunk| parseLines(chunk) }\n      data_sets.each { |data|\n        lnos = []\n        lno, header = data['accept']\n        lnos << lno\n        lno, avail_data = data['available']\n        lnos << lno\n        begin\n          available = parseAvailable(avail_data)\n        rescue\n          print 'On line', lno\n          raise\n        end\n\n        lno, exp_data = data['expected']\n        lnos << lno\n        begin\n          expected = parseExpected(exp_data)\n        rescue\n          print 'On line', lno\n          raise\n        end\n\n        sprintf('MatchAcceptTest for lines %s', lnos)\n\n        # Test:\n        accepted = Yadis.parse_accept_header(header)\n        actual = Yadis.match_types(accepted, available)\n        assert_equal(expected, actual)\n\n        assert_equal(Yadis.get_acceptable(header, available),\n                     expected.collect { |mtype, _| mtype })\n      }\n    end\n\n    def test_generate_accept_header\n      # TODO: move this into a test case file and write parsing code\n      # for it.\n\n      # Form: [input_array, expected_header_string]\n      [\n       # Empty input list\n       [[], \"\"],\n       # Content type name only; no q value\n       [[\"test\"], \"test\"],\n       # q = 1.0 should be omitted from the header\n       [[[\"test\", 1.0]], \"test\"],\n       # Test conversion of float to string\n       [[\"test\", [\"with_q\", 0.8]], \"with_q; q=0.8, test\"],\n       # Allow string q values, too\n       [[\"test\", [\"with_q_str\", \"0.7\"]], \"with_q_str; q=0.7, test\"],\n       # Test q values out of bounds\n       [[[\"test\", -1.0]], nil],\n       [[[\"test\", 1.1]], nil],\n       # Test sorting of types by q value\n       [[[\"middle\", 0.5], [\"min\", 0.1], \"max\"],\n        \"min; q=0.1, middle; q=0.5, max\"],\n\n      ].each { |input, expected_header|\n\n        if expected_header.nil?\n          assert_raises(ArgumentError) {\n            Yadis.generate_accept_header(*input)\n          }\n        else\n          assert_equal(expected_header, Yadis.generate_accept_header(*input),\n                       [input, expected_header].inspect)\n        end\n      }\n    end\n\n  end\n\nend\n"
  },
  {
    "path": "test/test_association.rb",
    "content": "require \"minitest/autorun\"\nrequire \"openid/association\"\nrequire \"openid/protocolerror\"\n\nmodule OpenID\n  class AssociationTestCase < Minitest::Test\n    def setup\n      # Use this funny way of getting a time so that it does not have\n      # fractional seconds, and so can be serialized exactly using our\n      # standard code.\n      issued = Time.at(Time.now.to_i)\n      lifetime = 600\n\n      @assoc = Association.new('handle', 'secret', issued,\n                               lifetime, 'HMAC-SHA1')\n    end\n\n    def test_round_trip\n      assoc2 = Association.deserialize(@assoc.serialize())\n      [:handle, :secret, :lifetime, :assoc_type].each do |attr|\n        assert_equal(@assoc.send(attr), assoc2.send(attr))\n      end\n    end\n\n    def test_deserialize_failure\n      field_list = Util.kv_to_seq(@assoc.serialize)\n      kv = Util.seq_to_kv(field_list + [['monkeys', 'funny']])\n      assert_raises(ProtocolError) {\n        Association.deserialize(kv)\n      }\n\n      bad_version_list = field_list.dup\n      bad_version_list[0] = ['version', 'moon']\n      bad_version_kv = Util.seq_to_kv(bad_version_list)\n      assert_raises(ProtocolError) {\n        Association.deserialize(bad_version_kv)\n      }\n    end\n\n    def test_serialization_identity\n      assoc2 = Association.deserialize(@assoc.serialize)\n      assert_equal(@assoc, assoc2)\n    end\n\n    def test_expires_in\n      # Allow one second of slop\n      assert(@assoc.expires_in.between?(599,600))\n      assert(@assoc.expires_in(Time.now.to_i).between?(599,600))\n      assert_equal(0,@assoc.expires_in(Time.now.to_i + 10000),\"negative expires_in\")\n    end\n\n    def test_from_expires_in\n      start_time = Time.now\n      expires_in = @assoc.expires_in\n      assoc = Association.from_expires_in(expires_in,\n                                          @assoc.handle,\n                                          @assoc.secret,\n                                          @assoc.assoc_type)\n\n      # Allow one second of slop here for code execution time\n      assert_in_delta(1, assoc.expires_in, @assoc.expires_in)\n      [:handle, :secret, :assoc_type].each do |attr|\n        assert_equal(@assoc.send(attr), assoc.send(attr))\n      end\n\n      # Make sure the issued time is near the start\n      assert(assoc.issued >= start_time)\n      assert_in_delta(1, assoc.issued.to_f, start_time.to_f)\n    end\n\n    def test_sign_sha1\n      pairs = [['key1', 'value1'],\n               ['key2', 'value2']]\n\n      [['HMAC-SHA256', \"\\xfd\\xaa\\xfe;\\xac\\xfc*\\x988\\xad\\x05d6-\\xeaVy\\xd5\\xa5Z.<\\xa9\\xed\\x18\\x82\\\\$\\x95x\\x1c&\"],\n       ['HMAC-SHA1', \"\\xe0\\x1bv\\x04\\xf1G\\xc0\\xbb\\x7f\\x9a\\x8b\\xe9\\xbc\\xee}\\\\\\xe5\\xbb7*\"],\n      ].each do |assoc_type, expected|\n        assoc = Association.from_expires_in(3600, \"handle\", 'very_secret', assoc_type)\n        sig = assoc.sign(pairs)\n        assert_equal(expected.force_encoding(\"UTF-8\"), sig.force_encoding(\"UTF-8\"))\n\n        m = Message.new(OPENID2_NS)\n        pairs.each { |k, v|\n          m.set_arg(OPENID_NS, k, v)\n        }\n        m.set_arg(BARE_NS, \"not_an_openid_arg\", \"bogus\")\n\n        signed_m = assoc.sign_message(m)\n        assert(signed_m.has_key?(OPENID_NS, 'sig'))\n        assert_equal(signed_m.get_arg(OPENID_NS, 'signed'),\n                     'assoc_handle,key1,key2,ns,signed')\n      end\n    end\n\n    def test_sign_message_with_sig\n      assoc = Association.from_expires_in(3600, \"handle\", \"very_secret\",\n                                          \"HMAC-SHA1\")\n      m = Message.new(OPENID2_NS)\n      m.set_arg(OPENID_NS, 'sig', 'noise')\n      assert_raises(ArgumentError) {\n        assoc.sign_message(m)\n      }\n    end\n\n    def test_sign_message_with_signed\n      assoc = Association.from_expires_in(3600, \"handle\", \"very_secret\",\n                                          \"HMAC-SHA1\")\n      m = Message.new(OPENID2_NS)\n      m.set_arg(OPENID_NS, 'signed', 'fields')\n      assert_raises(ArgumentError) {\n        assoc.sign_message(m)\n      }\n    end\n\n    def test_sign_different_assoc_handle\n      assoc = Association.from_expires_in(3600, \"handle\", \"very_secret\",\n                                          \"HMAC-SHA1\")\n      m = Message.new(OPENID2_NS)\n      m.set_arg(OPENID_NS, 'assoc_handle', 'different')\n      assert_raises(ArgumentError) {\n        assoc.sign_message(m)\n      }\n    end\n\n    def test_sign_bad_assoc_type\n      @assoc.instance_eval { @assoc_type = 'Cookies' }\n      assert_raises(ProtocolError) {\n        @assoc.sign([])\n      }\n    end\n\n    def test_make_pairs\n      msg = Message.new(OPENID2_NS)\n      msg.update_args(OPENID2_NS, {\n                        'mode' => 'id_res',\n                        'identifier' => '=example',\n                        'signed' => 'identifier,mode',\n                        'sig' => 'cephalopod',\n                      })\n      msg.update_args(BARE_NS, {'xey' => 'value'})\n      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',\n                                          \"HMAC-SHA1\")\n      pairs = assoc.make_pairs(msg)\n      assert_equal([['identifier', '=example'],\n                    ['mode', 'id_res']], pairs)\n    end\n\n    def test_check_message_signature_no_signed\n      m = Message.new(OPENID2_NS)\n      m.update_args(OPENID2_NS, {'mode' => 'id_res',\n                      'identifier' => '=example',\n                      'sig' => 'coyote',\n                    })\n      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',\n                                          \"HMAC-SHA1\")\n      assert_raises(ProtocolError) {\n        assoc.check_message_signature(m)\n      }\n    end\n\n    def test_check_message_signature_no_sig\n      m = Message.new(OPENID2_NS)\n      m.update_args(OPENID2_NS, {'mode' => 'id_res',\n                      'identifier' => '=example',\n                      'signed' => 'mode',\n                    })\n      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',\n                                          \"HMAC-SHA1\")\n      assert_raises(ProtocolError) {\n        assoc.check_message_signature(m)\n      }\n    end\n\n    def test_check_message_signature_bad_sig\n      m = Message.new(OPENID2_NS)\n      m.update_args(OPENID2_NS, {'mode' => 'id_res',\n                      'identifier' => '=example',\n                      'signed' => 'mode',\n                      'sig' => Util.to_base64('coyote'),\n                    })\n      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',\n                                          \"HMAC-SHA1\")\n      assert(!assoc.check_message_signature(m))\n    end\n\n    def test_check_message_signature_good_sig\n      m = Message.new(OPENID2_NS)\n      m.update_args(OPENID2_NS, {'mode' => 'id_res',\n                      'identifier' => '=example',\n                      'signed' => 'mode',\n                      'sig' => Util.to_base64('coyote'),\n                    })\n      assoc = Association.from_expires_in(3600, '{sha1}', 'very_secret',\n                                          \"HMAC-SHA1\")\n      class << assoc\n        # Override sign, because it's already tested elsewhere\n        def sign(pairs)\n          \"coyote\"\n        end\n      end\n\n      assert(assoc.check_message_signature(m))\n    end\n  end\n\n  class AssociationNegotiatorTestCase < Minitest::Test\n    def assert_equal_under(item1, item2)\n      val1 = yield(item1)\n      val2 = yield(item2)\n      assert_equal(val1, val2)\n    end\n\n    def test_copy\n      neg = AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1']])\n      neg2 = neg.copy\n      assert_equal_under(neg, neg2) {|n| n.instance_eval{@allowed_types} }\n      assert(neg.object_id != neg2.object_id)\n    end\n\n    def test_add_allowed\n      neg = AssociationNegotiator.new([])\n      assert(!neg.allowed?('HMAC-SHA1', 'DH-SHA1'))\n      assert(!neg.allowed?('HMAC-SHA1', 'no-encryption'))\n      assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))\n      assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))\n      neg.add_allowed_type('HMAC-SHA1')\n      assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))\n      assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))\n      assert(!neg.allowed?('HMAC-SHA256', 'DH-SHA256'))\n      assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))\n      neg.add_allowed_type('HMAC-SHA256', 'DH-SHA256')\n      assert(neg.allowed?('HMAC-SHA1', 'DH-SHA1'))\n      assert(neg.allowed?('HMAC-SHA1', 'no-encryption'))\n      assert(neg.allowed?('HMAC-SHA256', 'DH-SHA256'))\n      assert(!neg.allowed?('HMAC-SHA256', 'no-encryption'))\n      assert_equal(neg.get_allowed_type, ['HMAC-SHA1', 'DH-SHA1'])\n    end\n\n    def test_bad_assoc_type\n      assert_raises(ProtocolError) {\n        AssociationNegotiator.new([['OMG', 'Ponies']])\n      }\n    end\n\n    def test_bad_session_type\n      assert_raises(ProtocolError) {\n        AssociationNegotiator.new([['HMAC-SHA1', 'OMG-Ponies']])\n      }\n    end\n\n    def test_default_negotiator\n      assert_equal(DefaultNegotiator.get_allowed_type,\n                   ['HMAC-SHA1', 'DH-SHA1'])\n      assert(DefaultNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))\n    end\n\n    def test_encrypted_negotiator\n      assert_equal(EncryptedNegotiator.get_allowed_type,\n                   ['HMAC-SHA1', 'DH-SHA1'])\n      assert(!EncryptedNegotiator.allowed?('HMAC-SHA256', 'no-encryption'))\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_associationmanager.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"openid/consumer/associationmanager\"\nrequire \"openid/association\"\nrequire \"openid/dh\"\nrequire \"openid/util\"\nrequire \"openid/cryptutil\"\nrequire \"openid/message\"\nrequire \"openid/protocolerror\"\nrequire \"openid/store/memory\"\nrequire \"util\"\nrequire \"time\"\n\nmodule OpenID\n  class DHAssocSessionTest < Minitest::Test\n    def test_sha1_get_request\n      # Initialized without an explicit DH gets defaults\n      sess = Consumer::DiffieHellmanSHA1Session.new\n      assert_equal(['dh_consumer_public'], sess.get_request.keys)\n      Util::from_base64(sess.get_request['dh_consumer_public'])\n    end\n\n    def test_sha1_get_request_custom_dh\n      dh = DiffieHellman.new(1299721, 2)\n      sess = Consumer::DiffieHellmanSHA1Session.new(dh)\n      req = sess.get_request\n      assert_equal(['dh_consumer_public', 'dh_modulus', 'dh_gen'].sort,\n                   req.keys.sort)\n      assert_equal(dh.modulus, CryptUtil.base64_to_num(req['dh_modulus']))\n      assert_equal(dh.generator, CryptUtil.base64_to_num(req['dh_gen']))\n      Util::from_base64(req['dh_consumer_public'])\n    end\n  end\n\n  module TestDiffieHellmanResponseParametersMixin\n    def setup\n      session_cls = self.class.session_cls\n\n      # Pre-compute DH with small prime so tests run quickly.\n      @server_dh = DiffieHellman.new(100389557, 2)\n      @consumer_dh = DiffieHellman.new(100389557, 2)\n\n      # base64(btwoc(g ^ xb mod p))\n      @dh_server_public = CryptUtil.num_to_base64(@server_dh.public)\n\n      @secret = CryptUtil.random_string(session_cls.secret_size)\n\n      enc_mac_key_unencoded =\n        @server_dh.xor_secret(session_cls.hashfunc,\n                              @consumer_dh.public,\n                              @secret)\n\n      @enc_mac_key = Util.to_base64(enc_mac_key_unencoded)\n\n      @consumer_session = session_cls.new(@consumer_dh)\n\n      @msg = Message.new(self.class.message_namespace)\n    end\n\n    def test_extract_secret\n      @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)\n      @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)\n\n      extracted = @consumer_session.extract_secret(@msg)\n      assert_equal(extracted, @secret)\n    end\n\n    def test_absent_serve_public\n      @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)\n\n      assert_raises(Message::KeyNotFound) {\n        @consumer_session.extract_secret(@msg)\n      }\n    end\n\n    def test_absent_mac_key\n      @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)\n\n      assert_raises(Message::KeyNotFound) {\n        @consumer_session.extract_secret(@msg)\n      }\n    end\n\n    def test_invalid_base64_public\n      @msg.set_arg(OPENID_NS, 'dh_server_public', 'n o t b a s e 6 4.')\n      @msg.set_arg(OPENID_NS, 'enc_mac_key', @enc_mac_key)\n\n      assert_raises(ArgumentError) {\n        @consumer_session.extract_secret(@msg)\n      }\n    end\n\n    def test_invalid_base64_mac_key\n      @msg.set_arg(OPENID_NS, 'dh_server_public', @dh_server_public)\n      @msg.set_arg(OPENID_NS, 'enc_mac_key', 'n o t base 64')\n\n      assert_raises(ArgumentError) {\n        @consumer_session.extract_secret(@msg)\n      }\n    end\n  end\n\n  class TestConsumerOpenID1DHSHA1 < Minitest::Test\n    include TestDiffieHellmanResponseParametersMixin\n    class << self\n      attr_reader :session_cls, :message_namespace\n    end\n\n    @session_cls = Consumer::DiffieHellmanSHA1Session\n    @message_namespace = OPENID1_NS\n  end\n\n  class TestConsumerOpenID2DHSHA1 < Minitest::Test\n    include TestDiffieHellmanResponseParametersMixin\n    class << self\n      attr_reader :session_cls, :message_namespace\n    end\n\n    @session_cls = Consumer::DiffieHellmanSHA1Session\n    @message_namespace = OPENID2_NS\n  end\n\n  class TestConsumerOpenID2DHSHA256 < Minitest::Test\n    include TestDiffieHellmanResponseParametersMixin\n    class << self\n      attr_reader :session_cls, :message_namespace\n    end\n\n    @session_cls = Consumer::DiffieHellmanSHA256Session\n    @message_namespace = OPENID2_NS\n  end\n\n  class TestConsumerNoEncryptionSession < Minitest::Test\n    def setup\n      @sess = Consumer::NoEncryptionSession.new\n    end\n\n    def test_empty_request\n      assert_equal(@sess.get_request, {})\n    end\n\n    def test_get_secret\n      secret = 'shhh!' * 4\n      mac_key = Util.to_base64(secret)\n      msg = Message.from_openid_args({'mac_key' => mac_key})\n      assert_equal(secret, @sess.extract_secret(msg))\n    end\n  end\n\n  class TestCreateAssociationRequest < Minitest::Test\n    def setup\n      @server_url = 'http://invalid/'\n      @assoc_manager = Consumer::AssociationManager.new(nil, @server_url)\n      class << @assoc_manager\n        def compatibility_mode=(val)\n            @compatibility_mode = val\n        end\n      end\n      @assoc_type = 'HMAC-SHA1'\n    end\n\n    def test_no_encryption_sends_type\n      session_type = 'no-encryption'\n      session, args = @assoc_manager.send(:create_associate_request,\n                                          @assoc_type,\n                                          session_type)\n\n      assert(session.is_a?(Consumer::NoEncryptionSession))\n      expected = Message.from_openid_args(\n            {'ns' => OPENID2_NS,\n             'session_type' => session_type,\n             'mode' => 'associate',\n             'assoc_type' => @assoc_type,\n             })\n\n      assert_equal(expected, args)\n    end\n\n    def test_no_encryption_compatibility\n      @assoc_manager.compatibility_mode = true\n      session_type = 'no-encryption'\n      session, args = @assoc_manager.send(:create_associate_request,\n                                          @assoc_type,\n                                          session_type)\n\n      assert(session.is_a?(Consumer::NoEncryptionSession))\n      assert_equal(Message.from_openid_args({'mode' => 'associate',\n                                              'assoc_type' => @assoc_type,\n                                            }), args)\n    end\n\n    def test_dh_sha1_compatibility\n      @assoc_manager.compatibility_mode = true\n      session_type = 'DH-SHA1'\n      session, args = @assoc_manager.send(:create_associate_request,\n                                          @assoc_type,\n                                          session_type)\n\n\n      assert(session.is_a?(Consumer::DiffieHellmanSHA1Session))\n\n      # This is a random base-64 value, so just check that it's\n      # present.\n      refute_nil(args.get_arg(OPENID1_NS, 'dh_consumer_public'))\n      args.del_arg(OPENID1_NS, 'dh_consumer_public')\n\n      # OK, session_type is set here and not for no-encryption\n      # compatibility\n      expected = Message.from_openid_args({'mode' => 'associate',\n                                            'session_type' => 'DH-SHA1',\n                                            'assoc_type' => @assoc_type,\n                                          })\n      assert_equal(expected, args)\n    end\n  end\n\n  class TestAssociationManagerExpiresIn < Minitest::Test\n    def expires_in_msg(val)\n      msg = Message.from_openid_args({'expires_in' => val})\n      Consumer::AssociationManager.extract_expires_in(msg)\n    end\n\n    def test_parse_fail\n      ['',\n       '-2',\n       ' 1',\n       ' ',\n       '0x00',\n       'foosball',\n       '1\\n',\n       '100,000,000,000',\n      ].each do |x|\n        assert_raises(ProtocolError) {expires_in_msg(x)}\n      end\n    end\n\n    def test_parse\n      ['0',\n       '1',\n       '1000',\n       '9999999',\n       '01',\n      ].each do |n|\n        assert_equal(n.to_i, expires_in_msg(n))\n      end\n    end\n  end\n\n  class TestAssociationManagerCreateSession < Minitest::Test\n    def test_invalid\n      assert_raises(ArgumentError) {\n        Consumer::AssociationManager.create_session('monkeys')\n      }\n    end\n\n    def test_sha256\n      sess = Consumer::AssociationManager.create_session('DH-SHA256')\n      assert(sess.is_a?(Consumer::DiffieHellmanSHA256Session))\n    end\n  end\n\n  module NegotiationTestMixin\n    include TestUtil\n    def mk_message(args)\n      args['ns'] = @openid_ns\n      Message.from_openid_args(args)\n    end\n\n    def call_negotiate(responses, negotiator=nil)\n      store = nil\n      compat = self.class::Compat\n      assoc_manager = Consumer::AssociationManager.new(store, @server_url,\n                                                       compat, negotiator)\n      class << assoc_manager\n        attr_accessor :responses\n\n        def request_association(assoc_type, session_type)\n          m = @responses.shift\n          if m.is_a?(Message)\n            raise ServerError.from_message(m)\n          else\n            return m\n          end\n        end\n      end\n      assoc_manager.responses = responses\n      assoc_manager.negotiate_association\n    end\n  end\n\n  # Test the session type negotiation behavior of an OpenID 2\n  # consumer.\n  class TestOpenID2SessionNegotiation < Minitest::Test\n    include NegotiationTestMixin\n\n    Compat = false\n\n    def setup\n      @server_url = 'http://invalid/'\n      @openid_ns = OPENID2_NS\n    end\n\n    # Test the case where the response to an associate request is a\n    # server error or is otherwise undecipherable.\n    def test_bad_response\n      assert_log_matches('Server error when requesting an association') {\n        assert_nil(call_negotiate([mk_message({})]))\n      }\n    end\n\n    # Test the case where the association type (assoc_type) returned\n    # in an unsupported-type response is absent.\n    def test_empty_assoc_type\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'session_type' => 'new-session-type',\n                       })\n\n      assert_log_matches('Unsupported association type',\n                         \"Server #{@server_url} responded with unsupported \"\\\n                         \"association session but did not supply a fallback.\"\n                         ) {\n        assert_nil(call_negotiate([msg]))\n      }\n\n    end\n\n    # Test the case where the session type (session_type) returned\n    # in an unsupported-type response is absent.\n    def test_empty_session_type\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'new-assoc-type',\n                       })\n\n      assert_log_matches('Unsupported association type',\n                         \"Server #{@server_url} responded with unsupported \"\\\n                         \"association session but did not supply a fallback.\"\n                         ) {\n        assert_nil(call_negotiate([msg]))\n      }\n    end\n\n    # Test the case where an unsupported-type response specifies a\n    # preferred (assoc_type, session_type) combination that is not\n    # allowed by the consumer's SessionNegotiator.\n    def test_not_allowed\n      negotiator = AssociationNegotiator.new([])\n      negotiator.instance_eval{\n        @allowed_types = [['assoc_bogus', 'session_bogus']]\n      }\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'not-allowed',\n                         'session_type' => 'not-allowed',\n                       })\n\n      assert_log_matches('Unsupported association type',\n                         'Server sent unsupported session/association type:') {\n        assert_nil(call_negotiate([msg], negotiator))\n      }\n    end\n\n    # Test the case where an unsupported-type response triggers a\n    # retry to get an association with the new preferred type.\n    def test_unsupported_with_retry\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'HMAC-SHA1',\n                         'session_type' => 'DH-SHA1',\n                       })\n\n      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')\n\n      assert_log_matches('Unsupported association type') {\n        assert_equal(assoc, call_negotiate([msg, assoc]))\n      }\n    end\n\n    # Test the case where an unsupported-typ response triggers a\n    # retry, but the retry fails and nil is returned instead.\n    def test_unsupported_with_retry_and_fail\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'HMAC-SHA1',\n                         'session_type' => 'DH-SHA1',\n                       })\n\n      assert_log_matches('Unsupported association type',\n                         \"Server #{@server_url} refused\") {\n        assert_nil(call_negotiate([msg, msg]))\n      }\n    end\n\n    # Test the valid case, wherein an association is returned on the\n    # first attempt to get one.\n    def test_valid\n      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')\n\n      assert_log_matches() {\n        assert_equal(call_negotiate([assoc]), assoc)\n      }\n    end\n  end\n\n\n  # Tests for the OpenID 1 consumer association session behavior.  See\n  # the docs for TestOpenID2SessionNegotiation.  Notice that this\n  # class is not a subclass of the OpenID 2 tests.  Instead, it uses\n  # many of the same inputs but inspects the log messages logged with\n  # oidutil.log.  See the calls to self.failUnlessLogMatches.  Some of\n  # these tests pass openid2-style messages to the openid 1\n  # association processing logic to be sure it ignores the extra data.\n  class TestOpenID1SessionNegotiation < Minitest::Test\n    include NegotiationTestMixin\n\n    Compat = true\n\n    def setup\n      @server_url = 'http://invalid/'\n      @openid_ns = OPENID1_NS\n    end\n\n    def test_bad_response\n      assert_log_matches('Server error when requesting an association') {\n        response = call_negotiate([mk_message({})])\n        assert_nil(response)\n      }\n    end\n\n    def test_empty_assoc_type\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'session_type' => 'new-session-type',\n                       })\n\n      assert_log_matches('Server error when requesting an association') {\n        response = call_negotiate([msg])\n        assert_nil(response)\n      }\n    end\n\n    def test_empty_session_type\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'new-assoc-type',\n                       })\n\n      assert_log_matches('Server error when requesting an association') {\n        response = call_negotiate([msg])\n        assert_nil(response)\n      }\n    end\n\n    def test_not_allowed\n      negotiator = AssociationNegotiator.new([])\n      negotiator.instance_eval{\n        @allowed_types = [['assoc_bogus', 'session_bogus']]\n      }\n\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'not-allowed',\n                         'session_type' => 'not-allowed',\n                       })\n\n      assert_log_matches('Server error when requesting an association') {\n        response = call_negotiate([msg])\n        assert_nil(response)\n      }\n    end\n\n    def test_unsupported_with_retry\n      msg = mk_message({'error' => 'Unsupported type',\n                         'error_code' => 'unsupported-type',\n                         'assoc_type' => 'HMAC-SHA1',\n                         'session_type' => 'DH-SHA1',\n                       })\n\n      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')\n\n\n      assert_log_matches('Server error when requesting an association') {\n        response = call_negotiate([msg, assoc])\n        assert_nil(response)\n      }\n    end\n\n    def test_valid\n      assoc = Association.new('handle', 'secret', Time.now, 10000, 'HMAC-SHA1')\n      assert_log_matches() {\n        response = call_negotiate([assoc])\n        assert_equal(assoc, response)\n      }\n    end\n  end\n\n\n  class TestExtractAssociation < Minitest::Test\n    include ProtocolErrorMixin\n\n    # An OpenID associate response (without the namespace)\n    DEFAULTS = {\n      'expires_in' => '1000',\n      'assoc_handle' => 'a handle',\n      'assoc_type' => 'a type',\n      'session_type' => 'a session type',\n    }\n\n    def setup\n      @assoc_manager = Consumer::AssociationManager.new(nil, nil)\n    end\n\n    # Make tests that ensure that an association response that is\n    # missing required fields will raise an Message::KeyNotFound.\n    #\n    # According to 'Association Session Response' subsection 'Common\n    # Response Parameters', the following fields are required for\n    # OpenID 2.0:\n    #\n    #  * ns\n    #  * session_type\n    #  * assoc_handle\n    #  * assoc_type\n    #  * expires_in\n    #\n    # In OpenID 1, everything except 'session_type' and 'ns' are\n    # required.\n    MISSING_FIELD_SETS = ([[\"no_fields\", []]] +\n                          (DEFAULTS.keys.map do |f|\n                             fields = DEFAULTS.keys\n                             fields.delete(f)\n                             [\"missing_#{f}\", fields]\n                           end)\n                          )\n\n    [OPENID1_NS, OPENID2_NS].each do |ns|\n      MISSING_FIELD_SETS.each do |name, fields|\n        # OpenID 1 is allowed to be missing session_type\n        if ns != OPENID1_NS and name != 'missing_session_type'\n          test = lambda do\n            msg = Message.new(ns)\n            fields.each do |field|\n              msg.set_arg(ns, field, DEFAULTS[field])\n            end\n            assert_raises(Message::KeyNotFound) do\n              @assoc_manager.send(:extract_association, msg, nil)\n            end\n          end\n          define_method(\"test_#{name}\", test)\n        end\n      end\n    end\n\n    # assert that extracting a response that contains the given\n    # response session type when the request was made for the given\n    # request session type will raise a ProtocolError indicating\n    # session type mismatch\n    def assert_session_mismatch(req_type, resp_type, ns)\n      # Create an association session that has \"req_type\" as its\n      # session_type and no allowed_assoc_types\n      assoc_session_class = Class.new do\n        @session_type = req_type\n        def self.session_type\n          @session_type\n        end\n        def self.allowed_assoc_types\n          []\n        end\n      end\n      assoc_session = assoc_session_class.new\n\n      # Build an OpenID 1 or 2 association response message that has\n      # the specified association session type\n      msg = Message.new(ns)\n      msg.update_args(ns, DEFAULTS)\n      msg.set_arg(ns, 'session_type', resp_type)\n\n      # The request type and response type have been chosen to produce\n      # a session type mismatch.\n      assert_protocol_error('Session type mismatch') {\n        @assoc_manager.send(:extract_association, msg, assoc_session)\n      }\n    end\n\n    [['no-encryption', '', OPENID2_NS],\n     ['DH-SHA1', 'no-encryption', OPENID2_NS],\n     ['DH-SHA256', 'no-encryption', OPENID2_NS],\n     ['no-encryption', 'DH-SHA1', OPENID2_NS],\n     ['DH-SHA1', 'DH-SHA256', OPENID1_NS],\n     ['DH-SHA256', 'DH-SHA1', OPENID1_NS],\n     ['no-encryption', 'DH-SHA1', OPENID1_NS],\n    ].each do |req_type, resp_type, ns|\n      test = lambda { assert_session_mismatch(req_type, resp_type, ns) }\n      name = \"test_mismatch_req_#{req_type}_resp_#{resp_type}_#{ns}\"\n      define_method(name, test)\n    end\n\n    def test_openid1_no_encryption_fallback\n      # A DH-SHA1 session\n      assoc_session = Consumer::DiffieHellmanSHA1Session.new\n\n      # An OpenID 1 no-encryption association response\n      msg = Message.from_openid_args({\n                                       'expires_in' => '1000',\n                                       'assoc_handle' => 'a handle',\n                                       'assoc_type' => 'HMAC-SHA1',\n                                       'mac_key' => 'X' * 20,\n                                     })\n\n      # Should succeed\n      assoc = @assoc_manager.send(:extract_association, msg, assoc_session)\n      assert_equal('a handle', assoc.handle)\n      assert_equal('HMAC-SHA1', assoc.assoc_type)\n      assert(assoc.expires_in.between?(999, 1000))\n      assert('X' * 20, assoc.secret)\n    end\n  end\n\n  class GetOpenIDSessionTypeTest < Minitest::Test\n    include TestUtil\n\n    SERVER_URL = 'http://invalid/'\n\n    def do_test(expected_session_type, session_type_value)\n      # Create a Message with just 'session_type' in it, since\n      # that's all this function will use. 'session_type' may be\n      # absent if it's set to None.\n      args = {}\n      if !session_type_value.nil?\n        args['session_type'] = session_type_value\n      end\n      message = Message.from_openid_args(args)\n      assert(message.is_openid1)\n\n      assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)\n      actual_session_type = assoc_manager.send(:get_openid1_session_type,\n                                               message)\n      error_message = (\"Returned session type parameter #{session_type_value}\"\\\n                       \"was expected to yield session type \"\\\n                       \"#{expected_session_type}, but yielded \"\\\n                       \"#{actual_session_type}\")\n      assert_equal(expected_session_type, actual_session_type, error_message)\n    end\n\n\n    [['nil', 'no-encryption', nil],\n     ['empty', 'no-encryption', ''],\n     ['dh_sha1', 'DH-SHA1', 'DH-SHA1'],\n     ['dh_sha256', 'DH-SHA256', 'DH-SHA256'],\n    ].each {|name, expected, input|\n      # Define a test method that will check what session type will be\n      # used if the OpenID 1 response to an associate call sets the\n      # 'session_type' field to `session_type_value`\n      test = lambda {assert_log_matches() { do_test(expected, input) } }\n      define_method(\"test_#{name}\", &test)\n    }\n\n    # This one's different because it expects log messages\n    def test_explicit_no_encryption\n      assert_log_matches(\"WARNING: #{SERVER_URL} sent 'no-encryption'\"){\n        do_test('no-encryption', 'no-encryption')\n      }\n    end\n  end\n\n  class ExtractAssociationTest < Minitest::Test\n    include ProtocolErrorMixin\n\n    SERVER_URL = 'http://invalid/'\n\n    def setup\n      @session_type = 'testing-session'\n\n      # This must something that works for Association::from_expires_in\n      @assoc_type = 'HMAC-SHA1'\n\n      @assoc_handle = 'testing-assoc-handle'\n\n      # These arguments should all be valid\n      @assoc_response =\n        Message.from_openid_args({\n                                   'expires_in' => '1000',\n                                   'assoc_handle' => @assoc_handle,\n                                   'assoc_type' => @assoc_type,\n                                   'session_type' => @session_type,\n                                   'ns' => OPENID2_NS,\n                                 })\n      assoc_session_cls = Class.new do\n        class << self\n          attr_accessor :allowed_assoc_types, :session_type\n        end\n\n        attr_reader :extract_secret_called, :secret\n        def initialize\n          @extract_secret_called = false\n          @secret = 'shhhhh!'\n        end\n\n        def extract_secret(_)\n          @extract_secret_called = true\n          @secret\n        end\n      end\n      @assoc_session = assoc_session_cls.new\n      @assoc_session.class.allowed_assoc_types = [@assoc_type]\n      @assoc_session.class.session_type = @session_type\n\n      @assoc_manager = Consumer::AssociationManager.new(nil, SERVER_URL)\n    end\n\n    def call_extract\n      @assoc_manager.send(:extract_association,\n                          @assoc_response, @assoc_session)\n    end\n\n    # Handle a full successful association response\n    def test_works_with_good_fields\n      assoc = call_extract\n      assert(@assoc_session.extract_secret_called)\n      assert_equal(@assoc_session.secret, assoc.secret)\n      assert_equal(1000, assoc.lifetime)\n      assert_equal(@assoc_handle, assoc.handle)\n      assert_equal(@assoc_type, assoc.assoc_type)\n    end\n\n    def test_bad_assoc_type\n      # Make sure that the assoc type in the response is not valid\n      # for the given session.\n      @assoc_session.class.allowed_assoc_types = []\n      assert_protocol_error('Unsupported assoc_type for sess') {call_extract}\n    end\n\n    def test_bad_expires_in\n      # Invalid value for expires_in should cause failure\n      @assoc_response.set_arg(OPENID_NS, 'expires_in', 'forever')\n      assert_protocol_error('Invalid expires_in') {call_extract}\n    end\n  end\n\n  class TestExtractAssociationDiffieHellman < Minitest::Test\n    include ProtocolErrorMixin\n\n    SECRET = 'x' * 20\n\n    def setup\n      @assoc_manager = Consumer::AssociationManager.new(nil, nil)\n    end\n\n    def setup_dh\n      sess, _ = @assoc_manager.send(:create_associate_request,\n                                          'HMAC-SHA1', 'DH-SHA1')\n\n      server_dh = DiffieHellman.new\n      cons_dh = sess.instance_variable_get('@dh')\n\n      enc_mac_key = server_dh.xor_secret(CryptUtil.method(:sha1),\n                                         cons_dh.public, SECRET)\n\n      server_resp = {\n        'dh_server_public' => CryptUtil.num_to_base64(server_dh.public),\n        'enc_mac_key' => Util.to_base64(enc_mac_key),\n        'assoc_type' => 'HMAC-SHA1',\n        'assoc_handle' => 'handle',\n        'expires_in' => '1000',\n        'session_type' => 'DH-SHA1',\n      }\n      if @assoc_manager.instance_variable_get(:@compatibility_mode)\n        server_resp['ns'] = OPENID2_NS\n      end\n      return [sess, Message.from_openid_args(server_resp)]\n    end\n\n    def test_success\n      sess, server_resp = setup_dh\n      ret = @assoc_manager.send(:extract_association, server_resp, sess)\n      assert(!ret.nil?)\n      assert_equal(ret.assoc_type, 'HMAC-SHA1')\n      assert_equal(ret.secret, SECRET)\n      assert_equal(ret.handle, 'handle')\n      assert_equal(ret.lifetime, 1000)\n    end\n\n    def test_openid2success\n      # Use openid 1 type in endpoint so _setUpDH checks\n      # compatibility mode state properly\n      @assoc_manager.instance_variable_set('@compatibility_mode', true)\n      test_success()\n    end\n\n    def test_bad_dh_values\n      sess, server_resp = setup_dh\n      server_resp.set_arg(OPENID_NS, 'enc_mac_key', '\\x00\\x00\\x00')\n      assert_protocol_error('Malformed response for') {\n        @assoc_manager.send(:extract_association, server_resp, sess)\n      }\n    end\n  end\n\n  class TestAssocManagerGetAssociation < Minitest::Test\n    include FetcherMixin\n    include TestUtil\n\n    attr_reader :negotiate_association\n\n    def setup\n      @server_url = 'http://invalid/'\n      @store = Store::Memory.new\n      @assoc_manager = Consumer::AssociationManager.new(@store, @server_url)\n      @assoc_manager.extend(Const)\n      @assoc = Association.new('handle', 'secret', Time.now, 10000,\n                               'HMAC-SHA1')\n    end\n\n    def set_negotiate_response(assoc)\n      @assoc_manager.const(:negotiate_association, assoc)\n    end\n\n    def test_not_in_store_no_response\n      set_negotiate_response(nil)\n      assert_nil(@assoc_manager.get_association)\n    end\n\n    def test_not_in_store_negotiate_assoc\n      # Not stored beforehand:\n      stored_assoc = @store.get_association(@server_url, @assoc.handle)\n      assert_nil(stored_assoc)\n\n      # Returned from associate call:\n      set_negotiate_response(@assoc)\n      assert_equal(@assoc, @assoc_manager.get_association)\n\n      # It should have been stored:\n      stored_assoc = @store.get_association(@server_url, @assoc.handle)\n      assert_equal(@assoc, stored_assoc)\n    end\n\n    def test_in_store_no_response\n      set_negotiate_response(nil)\n      @store.store_association(@server_url, @assoc)\n      assert_equal(@assoc, @assoc_manager.get_association)\n    end\n\n    def test_request_assoc_with_status_error\n      fetcher_class = Class.new do\n        define_method(:fetch) do |*args|\n          MockResponse.new(500, '')\n        end\n      end\n      with_fetcher(fetcher_class.new) do\n        assert_log_matches('Got HTTP status error when requesting') {\n          result = @assoc_manager.send(:request_association, 'HMAC-SHA1',\n                                       'no-encryption')\n          assert(result.nil?)\n        }\n      end\n    end\n  end\n\n  class TestAssocManagerRequestAssociation < Minitest::Test\n    include FetcherMixin\n    include TestUtil\n\n    def setup\n      @assoc_manager = Consumer::AssociationManager.new(nil, 'http://invalid/')\n      @assoc_type = 'HMAC-SHA1'\n      @session_type = 'no-encryption'\n      @message = Message.new(OPENID2_NS)\n      @message.update_args(OPENID_NS, {\n                             'assoc_type' => @assoc_type,\n                             'session_type' => @session_type,\n                             'assoc_handle' => 'kaboodle',\n                             'expires_in' => '1000',\n                             'mac_key' => 'X' * 20,\n                           })\n    end\n\n    def make_request\n      kv = @message.to_kvform\n      fetcher_class = Class.new do\n        define_method(:fetch) do |*args|\n          MockResponse.new(200, kv)\n        end\n      end\n      with_fetcher(fetcher_class.new) do\n        @assoc_manager.send(:request_association, @assoc_type, @session_type)\n      end\n    end\n\n    # The association we get is from valid processing of our result,\n    # and that no errors are raised\n    def test_success\n      assert_equal('kaboodle', make_request.handle)\n    end\n\n    # A missing parameter gets translated into a log message and\n    # causes the method to return nil\n    def test_missing_fields\n      @message.del_arg(OPENID_NS, 'assoc_type')\n      assert_log_matches('Missing required par') {\n        assert_nil(make_request)\n      }\n    end\n\n    # A bad value results in a log message and causes the method to\n    # return nil\n    def test_protocol_error\n      @message.set_arg(OPENID_NS, 'expires_in', 'goats')\n      assert_log_matches('Protocol error processing') {\n        assert_nil(make_request)\n      }\n    end\n  end\n\nend\n"
  },
  {
    "path": "test/test_ax.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/extensions/ax'\nrequire 'openid/message'\nrequire 'openid/consumer/responses'\nrequire 'openid/consumer/discovery'\nrequire 'openid/consumer/checkid_request'\n\nmodule OpenID\n  module AX\n    class BogusAXMessage < AXMessage\n      @mode = 'bogus'\n\n      def get_extension_args\n        new_args\n      end\n\n      def do_check_mode(args)\n        check_mode(args)\n      end\n\n      def do_check_mode_new_args\n        check_mode(new_args)\n      end\n    end\n\n    class AXMessageTest < Minitest::Test\n      def setup\n        @bax = BogusAXMessage.new\n      end\n\n      def test_check_mode\n        assert_raises(Error) { @bax.do_check_mode({'mode' => 'fetch_request'})}\n        @bax.do_check_mode({'mode' => @bax.mode})\n      end\n\n      def test_check_mode_new_args\n        @bax.do_check_mode_new_args\n      end\n    end\n\n    class AttrInfoTest < Minitest::Test\n      def test_construct\n        assert_raises(ArgumentError) { AttrInfo.new }\n        type_uri = 'uri geller'\n        ainfo = AttrInfo.new(type_uri)\n\n        assert_equal(type_uri, ainfo.type_uri)\n        assert_equal(1, ainfo.count)\n        assert_equal(false, ainfo.required)\n        assert_nil(ainfo.ns_alias)\n      end\n    end\n\n    class ToTypeURIsTest < Minitest::Test\n      def setup\n        @aliases = NamespaceMap.new\n      end\n\n      def test_empty\n        [nil, ''].each{|empty|\n          uris = AX.to_type_uris(@aliases, empty)\n          assert_equal([], uris)\n        }\n      end\n\n      def test_undefined\n        assert_raises(IndexError) {\n          AX.to_type_uris(@aliases, 'http://janrain.com/')\n        }\n      end\n\n      def test_one\n        uri = 'http://janrain.com/'\n        name = 'openid_hackers'\n        @aliases.add_alias(uri, name)\n        uris = AX::to_type_uris(@aliases, name)\n        assert_equal([uri], uris)\n      end\n\n      def test_two\n        uri1 = 'http://janrain.com/'\n        name1 = 'openid_hackers'\n        @aliases.add_alias(uri1, name1)\n\n        uri2 = 'http://jyte.com/'\n        name2 = 'openid_hack'\n        @aliases.add_alias(uri2, name2)\n\n        uris = AX.to_type_uris(@aliases, [name1, name2].join(','))\n        assert_equal([uri1, uri2], uris)\n      end\n    end\n\n    class ParseAXValuesTest < Minitest::Test\n      def ax_values(ax_args, expected_args)\n        msg = KeyValueMessage.new\n        msg.parse_extension_args(ax_args)\n        assert_equal(expected_args, msg.data)\n      end\n\n      def ax_error(ax_args, error)\n        msg = KeyValueMessage.new\n        assert_raises(error) {\n          msg.parse_extension_args(ax_args)\n        }\n      end\n\n      def test_empty_is_valid\n        ax_values({}, {})\n      end\n\n      def test_missing_value_for_alias_explodes\n        ax_error({'type.foo'=>'urn:foo'}, IndexError)\n      end\n\n      def test_count_present_but_not_value\n        ax_error({'type.foo'=>'urn:foo', 'count.foo' => '1'}, IndexError)\n      end\n\n      def test_invalid_count_value\n        msg = FetchRequest.new\n        assert_raises(Error) {\n          msg.parse_extension_args({'type.foo'=>'urn:foo',\n                                     'count.foo' => 'bogus'})\n        }\n      end\n\n      def test_request_unlimited_values\n        msg = FetchRequest.new\n        args = {'mode' => 'fetch_request',\n          'required' => 'foo',\n          'type.foo' => 'urn:foo',\n          'count.foo' => UNLIMITED_VALUES\n        }\n        msg.parse_extension_args(args)\n        foo = msg.attributes[0]\n        assert_equal(UNLIMITED_VALUES, foo.count)\n        assert(foo.wants_unlimited_values?)\n      end\n\n      def test_long_alias\n        # spec says we must support at least 32 character-long aliases\n        name = 'x' * MINIMUM_SUPPORTED_ALIAS_LENGTH\n\n        msg = KeyValueMessage.new\n        args = {\n          \"type.#{name}\" => 'urn:foo',\n          \"count.#{name}\" => '1',\n          \"value.#{name}.1\" => 'first',\n        }\n        msg.parse_extension_args(args)\n        assert_equal(['first'],msg['urn:foo'])\n      end\n\n      def test_invalid_alias\n        types = [\n                 KeyValueMessage,\n                 FetchRequest\n                ]\n        inputs = [\n                  {'type.a.b'=>'urn:foo',\n                    'count.a.b'=>'1'},\n                  {'type.a,b'=>'urn:foo',\n                    'count.a,b'=>'1'},\n                 ]\n        types.each{|t|\n          inputs.each{|input|\n            msg = t.new\n            assert_raises(Error) {msg.parse_extension_args(input)}\n          }\n        }\n      end\n\n      def test_count_present_and_is_zero\n        ax_values(\n                  {'type.foo'=>'urn:foo',\n                    'count.foo'=>'0',\n                  },\n                  {'urn:foo'=>[]}\n                  )\n      end\n\n      def test_singleton_empty\n        ax_values(\n                  {'type.foo'=>'urn:foo',\n                    'value.foo'=>'',\n                  },\n                  {'urn:foo'=>[]}\n                  )\n      end\n\n      def test_double_alias\n        ax_error(\n                 {'type.foo'=>'urn:foo',\n                   'value.foo'=>'',\n                   'type.bar'=>'urn:foo',\n                   'value.bar'=>'',\n                 },\n                 IndexError\n                 )\n      end\n\n      def test_double_singleton\n        ax_values(\n                  {'type.foo'=>'urn:foo',\n                    'value.foo'=>'',\n                    'type.bar'=>'urn:bar',\n                    'value.bar'=>'',\n                  },\n                  {'urn:foo'=>[],'urn:bar'=>[]}\n                  )\n      end\n\n      def singleton_value\n        ax_values(\n                  {'type.foo'=>'urn:foo',\n                    'value.foo'=>'something',\n                  },\n                  {'urn:foo'=>['something']}\n                  )\n      end\n    end\n\n    class FetchRequestTest < Minitest::Test\n      def setup\n        @msg = FetchRequest.new\n        @type_a = 'http://janrain.example.com/a'\n        @name_a = 'a'\n      end\n\n      def test_mode\n        assert_equal('fetch_request', @msg.mode)\n      end\n\n      def test_construct\n        assert_equal({}, @msg.requested_attributes)\n        assert_nil(@msg.update_url)\n\n        msg = FetchRequest.new('hailstorm')\n        assert_equal({}, msg.requested_attributes)\n        assert_equal('hailstorm', msg.update_url)\n      end\n\n      def test_add\n        uri = 'mud://puddle'\n\n        assert(! @msg.member?(uri))\n        a = AttrInfo.new(uri)\n        @msg.add(a)\n        assert(@msg.member?(uri))\n      end\n\n      def test_add_twice\n        uri = 'its://raining'\n        a = AttrInfo.new(uri)\n        @msg.add(a)\n        assert_raises(IndexError) {@msg.add(a)}\n      end\n\n      def do_extension_args(expected_args)\n        expected_args['mode'] = @msg.mode\n        assert_equal(expected_args, @msg.get_extension_args)\n      end\n\n      def test_get_extension_args_empty\n        do_extension_args({})\n      end\n\n      def test_get_extension_args_no_alias\n        a = AttrInfo.new('foo://bar')\n        @msg.add(a)\n        ax_args = @msg.get_extension_args\n        ax_args.each{|k,v|\n          if v == a.type_uri and k.index('type.') == 0\n            @name = k[5..-1]\n            break\n          end\n        }\n        do_extension_args({'type.'+@name => a.type_uri,\n                            'if_available' => @name})\n      end\n\n      def test_get_extension_args_alias_if_available\n        a = AttrInfo.new('type://of.transportation',\n                         'transport')\n        @msg.add(a)\n        do_extension_args({'type.'+a.ns_alias => a.type_uri,\n                            'if_available' => a.ns_alias})\n      end\n\n      def test_get_extension_args_alias_req\n        a = AttrInfo.new('type://of.transportation',\n                         'transport',\n                         true)\n        @msg.add(a)\n        do_extension_args({'type.'+a.ns_alias => a.type_uri,\n                            'required' => a.ns_alias})\n      end\n\n      def test_get_required_attrs_empty\n        assert_equal([], @msg.get_required_attrs)\n      end\n\n      def test_parse_extension_args_extra_type\n        args = {\n          'mode' => 'fetch_request',\n          'type.' + @name_a => @type_a\n        }\n        assert_raises(Error) {@msg.parse_extension_args(args)}\n      end\n\n      def test_parse_extension_args\n        args = {\n          'mode' => 'fetch_request',\n          'type.' + @name_a => @type_a,\n          'if_available' => @name_a\n        }\n        @msg.parse_extension_args(args)\n        assert(@msg.member?(@type_a) )\n        assert_equal([@type_a], @msg.requested_types)\n        ai = @msg.requested_attributes[@type_a]\n        assert(ai.is_a?(AttrInfo))\n        assert(!ai.required)\n        assert_equal(@type_a, ai.type_uri)\n        assert_equal(@name_a, ai.ns_alias)\n        assert_equal([ai], @msg.attributes)\n      end\n\n      def test_extension_args_idempotent\n        args = {\n          'mode' => 'fetch_request',\n          'type.' + @name_a => @type_a,\n          'if_available' => @name_a\n        }\n        @msg.parse_extension_args(args)\n        assert_equal(args, @msg.get_extension_args)\n        assert(!@msg.requested_attributes[@type_a].required)\n      end\n\n      def test_extension_args_idempotent_count_required\n        args = {\n          'mode' => 'fetch_request',\n          'type.' + @name_a => @type_a,\n          'count.' + @name_a => '2',\n          'required' => @name_a\n        }\n        @msg.parse_extension_args(args)\n        assert_equal(args, @msg.get_extension_args)\n        assert(@msg.requested_attributes[@type_a].required)\n      end\n\n      def test_extension_args_count1\n        args = {\n          'mode' => 'fetch_request',\n          'type.' + @name_a => @type_a,\n          'count.' + @name_a => '1',\n          'if_available' => @name_a\n        }\n        norm_args = {\n          'mode' => 'fetch_request',\n          'type.' + @name_a => @type_a,\n          'if_available' => @name_a\n        }\n        @msg.parse_extension_args(args)\n        assert_equal(norm_args, @msg.get_extension_args)\n      end\n\n      def test_from_openid_request_no_ax\n        message = Message.new\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = message\n        ax_req = FetchRequest.from_openid_request(openid_req)\n        assert(ax_req.nil?)\n      end\n\n      def test_from_openid_request_wrong_ax_mode\n        uri = 'http://under.the.sea/'\n        name = 'ext0'\n        value = 'snarfblat'\n\n        message = OpenID::Message.from_openid_args({\n                                               'mode' => 'id_res',\n                                               'ns' => OPENID2_NS,\n                                               'ns.ax' => AXMessage::NS_URI,\n                                               'ax.update_url' => 'http://example.com/realm/update_path',\n                                               'ax.mode' => 'store_request',\n                                               'ax.type.' + name => uri,\n                                               'ax.count.' + name => '1',\n                                               'ax.value.' + name + '.1' => value\n                                             })\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = message\n        ax_req = FetchRequest.from_openid_request(openid_req)\n        assert(ax_req.nil?)\n      end\n\n      def test_openid_update_url_verification_error\n        openid_req_msg = Message.from_openid_args({\n                                                    'mode' => 'checkid_setup',\n                                                    'ns' => OPENID2_NS,\n                                                    'realm' => 'http://example.com/realm',\n                                                    'ns.ax' => AXMessage::NS_URI,\n                                                    'ax.update_url' => 'http://different.site/path',\n                                                    'ax.mode' => 'fetch_request',\n                                                  })\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = openid_req_msg\n        assert_raises(Error) {\n          FetchRequest.from_openid_request(openid_req)\n        }\n      end\n\n      def test_openid_no_realm\n        openid_req_msg = Message.from_openid_args({\n                                                    'mode' => 'checkid_setup',\n                                                    'ns' => OPENID2_NS,\n                                                    'ns.ax' => AXMessage::NS_URI,\n                                                    'ax.update_url' => 'http://different.site/path',\n                                                    'ax.mode' => 'fetch_request',\n                                                  })\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = openid_req_msg\n        assert_raises(Error) {\n          FetchRequest.from_openid_request(openid_req)\n        }\n      end\n\n      def test_openid_update_url_verification_success\n        openid_req_msg = Message.from_openid_args({\n                                                    'mode' => 'checkid_setup',\n                                                    'ns' => OPENID2_NS,\n                                                    'realm' => 'http://example.com/realm',\n                                                    'ns.ax' => AXMessage::NS_URI,\n                                                    'ax.update_url' => 'http://example.com/realm/update_path',\n                                                    'ax.mode' => 'fetch_request',\n                                                  })\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = openid_req_msg\n        fr = FetchRequest.from_openid_request(openid_req)\n        assert(fr.is_a?(FetchRequest))\n      end\n\n      def test_openid_update_url_verification_success_return_to\n        openid_req_msg = Message.from_openid_args({\n                                                    'mode' => 'checkid_setup',\n                                                    'ns' => OPENID2_NS,\n                                                    'return_to' => 'http://example.com/realm',\n                                                    'ns.ax' => AXMessage::NS_URI,\n                                                    'ax.update_url' => 'http://example.com/realm/update_path',\n                                                    'ax.mode' => 'fetch_request',\n                                                  })\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = openid_req_msg\n        fr = FetchRequest.from_openid_request(openid_req)\n        assert(fr.is_a?(FetchRequest))\n      end\n\n      def test_add_extension\n        openid_req_msg = Message.from_openid_args({\n                                                    'mode' => 'checkid_setup',\n                                                    'ns' => OPENID2_NS,\n                                                    'return_to' => 'http://example.com/realm',\n                                                  })\n\n        e = OpenID::OpenIDServiceEndpoint.new\n        openid_req = Consumer::CheckIDRequest.new(nil, e)\n        openid_req.message = openid_req_msg\n\n        fr = FetchRequest.new\n        fr.add(AttrInfo.new(\"urn:bogus\"))\n\n        openid_req.add_extension(fr)\n\n        expected = {\n          'mode' => 'fetch_request',\n          'if_available' => 'ext0',\n          'type.ext0' => 'urn:bogus',\n        }\n\n        expected.each { |k,v|\n          assert(openid_req.message.get_arg(AXMessage::NS_URI, k) == v)\n        }\n      end\n    end\n\n    class FetchResponseTest < Minitest::Test\n      def setup\n        @msg = FetchResponse.new\n        @value_a = 'commodity'\n        @value_a1 = 'value2'\n        @type_a = 'http://blood.transfusion/'\n        @name_a = 'george'\n        @request_update_url = 'http://some.url.that.is.awesome/'\n      end\n\n      def test_construct\n        assert_nil(@msg.update_url)\n        assert_equal({}, @msg.data)\n      end\n\n      def test_get_extension_args_empty\n        eargs = {\n          'mode' => 'fetch_response'\n        }\n        assert_equal(eargs, @msg.get_extension_args)\n      end\n\n      def test_get_extension_args_empty_request\n        eargs = {\n          'mode' => 'fetch_response'\n        }\n        req = FetchRequest.new\n        assert_equal(eargs, @msg.get_extension_args(req))\n      end\n\n      def test_get_extension_args_empty_request_some\n        uri = 'http://not.found/'\n        name = 'ext0'\n        eargs = {\n          'mode' => 'fetch_response',\n          'type.' + name => uri,\n          'count.' + name => '0'\n        }\n        req = FetchRequest.new\n        req.add(AttrInfo.new(uri))\n        assert_equal(eargs, @msg.get_extension_args(req))\n      end\n\n      def test_update_url_in_response\n        uri = 'http://not.found/'\n        name = 'ext0'\n        eargs = {\n          'mode' => 'fetch_response',\n          'update_url' => @request_update_url,\n          'type.' + name => uri,\n          'count.' + name => '0'\n        }\n        req = FetchRequest.new(@request_update_url)\n        req.add(AttrInfo.new(uri))\n        assert_equal(eargs, @msg.get_extension_args(req))\n      end\n\n      def test_get_extension_args_single_value_response\n        # Single values do NOT have a count, and\n        # do not use the array extension\n        eargs = {\n          'mode' => 'fetch_response',\n          'type.' + @name_a => @type_a,\n          'value.' + @name_a  => @value_a\n        }\n        req = FetchRequest.new\n        req.add(AttrInfo.new(@type_a, @name_a))\n        @msg.add_value(@type_a, @value_a)\n        assert_equal(eargs, @msg.get_extension_args(req))\n      end\n\n      def test_get_extension_args_array_value_response\n        # Multiple array values add the count, and array index\n        # to each value\n        eargs = {\n          'mode' => 'fetch_response',\n          'type.' + @name_a => @type_a,\n          'value.' + @name_a + \".1\" => @value_a,\n          'value.' + @name_a + \".2\" => @value_a1,\n          'count.' + @name_a => '2'\n        }\n        req = FetchRequest.new\n        # Specify that this URI should have a count of 2\n        req.add(AttrInfo.new(@type_a, @name_a, true, 2))\n        # Push both values onto the array\n        @msg.add_value(@type_a, @value_a)\n        @msg.add_value(@type_a, @value_a1)\n        assert_equal(eargs, @msg.get_extension_args(req))\n      end\n\n      def test_get_extension_args_some_not_request\n        req = FetchRequest.new\n        @msg.add_value(@type_a, @value_a)\n        assert_raises(IndexError) {@msg.get_extension_args(req)}\n      end\n\n      def test_get_single_success\n        @msg.add_value(@type_a, @value_a)\n        assert_equal(@value_a, @msg.get_single(@type_a))\n      end\n\n      def test_get_single_none\n        assert_nil(@msg.get_single(@type_a))\n      end\n\n      def test_get_single_extra\n        @msg.set_values(@type_a, ['x', 'y'])\n        assert_raises(Error) { @msg.get_single(@type_a) }\n      end\n\n      def test_from_unsigned_success_response\n        uri = 'http://under.the.sea/'\n        name = 'ext0'\n        value = 'snarfblat'\n\n        m = OpenID::Message.from_openid_args({\n                                               'mode' => 'id_res',\n                                               'ns' => OPENID2_NS,\n                                               'ns.ax' => AXMessage::NS_URI,\n                                               'ax.update_url' => 'http://example.com/realm/update_path',\n                                               'ax.mode' => 'fetch_response',\n                                               'ax.type.' + name => uri,\n                                               'ax.count.' + name => '1',\n                                               'ax.value.' + name + '.1' => value\n                                             })\n\n        e = OpenID::OpenIDServiceEndpoint.new()\n        resp = OpenID::Consumer::SuccessResponse.new(e, m, [])\n\n        ax_resp = FetchResponse.from_success_response(resp, false)\n\n        values = ax_resp[uri]\n        assert_equal(values, [value])\n      end\n\n      def test_from_signed_success_response\n        uri = 'http://under.the.sea/'\n        name = 'ext0'\n        value = 'snarfblat'\n        oid_fields = {\n          'mode' => 'id_res',\n          'ns' => OPENID2_NS,\n          'ns.ax' => AXMessage::NS_URI,\n          'ax.update_url' => 'http://example.com/realm/update_path',\n          'ax.mode' => 'fetch_response',\n          'ax.type.' + name => uri,\n          'ax.count.' + name => '1',\n          'ax.value.' + name + '.1' => value\n        }\n        signed_fields = oid_fields.keys.map{|f| \"openid.#{f}\"}\n\n        m = OpenID::Message.from_openid_args(oid_fields)\n        e = OpenID::OpenIDServiceEndpoint.new()\n        resp = OpenID::Consumer::SuccessResponse.new(e, m, signed_fields)\n\n        ax_resp = FetchResponse.from_success_response(resp, true)\n\n        values = ax_resp[uri]\n        assert_equal(values, [value])\n      end\n\n      def test_from_signed_success_response_with_unsigned_attributes\n        uri = 'http://under.the.sea/'\n        name = 'ext0'\n        value = 'snarfblat'\n\n        m = OpenID::Message.from_openid_args({\n                                               'mode' => 'id_res',\n                                               'ns' => OPENID2_NS,\n                                               'ns.ax' => AXMessage::NS_URI,\n                                               'ax.update_url' => 'http://example.com/realm/update_path',\n                                               'ax.mode' => 'fetch_response',\n                                               'ax.type.' + name => uri,\n                                               'ax.count.' + name => '1',\n                                               'ax.value.' + name + '.1' => value\n                                             })\n\n        e = OpenID::OpenIDServiceEndpoint.new()\n        resp = OpenID::Consumer::SuccessResponse.new(e, m, [])\n\n        assert_nil FetchResponse.from_success_response(resp, true)\n      end\n\n      def test_from_empty_success_response\n        e = OpenID::OpenIDServiceEndpoint.new()\n        m = OpenID::Message.from_openid_args({'mode' => 'id_res'})\n        resp = OpenID::Consumer::SuccessResponse.new(e, m, [])\n        ax_resp = FetchResponse.from_success_response(resp)\n        assert(ax_resp.nil?)\n      end\n    end\n\n    class StoreRequestTest < Minitest::Test\n      def setup\n        @msg = StoreRequest.new\n        @type_a = 'http://oranges.are.for/'\n        @name_a = 'juggling'\n      end\n\n      def test_construct\n        assert_equal({}, @msg.data)\n      end\n\n      def test_get_extension_args_empty\n        eargs = {\n          'mode' => 'store_request'\n        }\n        assert_equal(eargs, @msg.get_extension_args)\n      end\n\n      def test_from_openid_request_wrong_ax_mode\n        uri = 'http://under.the.sea/'\n        name = 'ext0'\n        value = 'snarfblat'\n\n        message = OpenID::Message.from_openid_args({\n                                               'mode' => 'id_res',\n                                               'ns' => OPENID2_NS,\n                                               'ns.ax' => AXMessage::NS_URI,\n                                               'ax.update_url' => 'http://example.com/realm/update_path',\n                                               'ax.mode' => 'fetch_request',\n                                               'ax.type.' + name => uri,\n                                               'ax.count.' + name => '1',\n                                               'ax.value.' + name + '.1' => value\n                                             })\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = message\n        ax_req = StoreRequest.from_openid_request(openid_req)\n        assert(ax_req.nil?)\n      end\n\n      def test_get_extension_args_nonempty\n        @msg.set_values(@type_a, ['foo','bar'])\n        aliases = NamespaceMap.new\n        aliases.add_alias(@type_a, @name_a)\n        eargs = {\n          'mode' => 'store_request',\n          'type.' + @name_a => @type_a,\n          'value.' + @name_a + '.1' => 'foo',\n          'value.' + @name_a + '.2' => 'bar',\n          'count.' + @name_a =>  '2'\n        }\n        assert_equal(eargs, @msg.get_extension_args(aliases))\n      end\n    end\n\n    class StoreResponseTest < Minitest::Test\n      def test_success\n        msg = StoreResponse.new\n        assert(msg.succeeded?)\n        assert(!msg.error_message)\n        assert_equal({'mode' => 'store_response_success'},\n                     msg.get_extension_args)\n      end\n\n      def test_fail_nomsg\n        msg = StoreResponse.new(false)\n        assert(! msg.succeeded? )\n        assert(! msg.error_message )\n        assert_equal({'mode' => 'store_response_failure'},\n                     msg.get_extension_args)\n      end\n\n      def test_fail_msg\n        reason = \"because I said so\"\n        msg = StoreResponse.new(false, reason)\n        assert(! msg.succeeded? )\n        assert_equal(reason,  msg.error_message)\n        assert_equal({'mode' => 'store_response_failure', 'error' => reason},\n                     msg.get_extension_args)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_checkid_request.rb",
    "content": "require \"minitest/autorun\"\nrequire \"openid/consumer/checkid_request\"\nrequire \"openid/message\"\nrequire \"testutil\"\nrequire \"util\"\n\nmodule OpenID\n  class Consumer\n    class CheckIDRequest\n      class DummyEndpoint\n        attr_accessor :preferred_namespace, :local_id, :server_url,\n          :is_op_identifier, :claimed_id\n\n        def initialize\n          @preferred_namespace = nil\n          @local_id = nil\n          @server_url = nil\n          @is_op_identifier = false\n        end\n\n        def get_local_id\n          @local_id\n        end\n\n        def compatibility_mode\n          @preferred_namespace == OPENID1_NS\n        end\n      end\n\n      module CheckIDTestMixin\n        include TestUtil\n\n        def setup\n          @endpoint = DummyEndpoint.new\n          @endpoint.local_id = 'http://server.unittest/joe'\n          @endpoint.claimed_id = 'http://joe.vanity.example/'\n          @endpoint.server_url = 'http://server.unittest/'\n          @endpoint.preferred_namespace = preferred_namespace\n          @realm = 'http://example/'\n          @return_to = 'http://example/return/'\n          @assoc = GoodAssoc.new\n          @checkid_req = CheckIDRequest.new(@assoc, @endpoint)\n        end\n\n        def assert_has_identifiers(msg, local_id, claimed_id)\n          assert_openid_value_equal(msg, 'identity', local_id)\n          assert_openid_value_equal(msg, 'claimed_id', claimed_id)\n        end\n\n        def assert_openid_key_exists(msg, key)\n          assert(msg.get_arg(OPENID_NS, key),\n                 \"#{key} not present in #{msg.get_args(OPENID_NS).inspect}\")\n        end\n\n        def assert_openid_key_absent(msg, key)\n          assert(msg.get_arg(OPENID_NS, key).nil?)\n        end\n\n        def assert_openid_value_equal(msg, key, expected)\n          actual = msg.get_arg(OPENID_NS, key, NO_DEFAULT)\n          error_text = (\"Expected #{expected.inspect} for openid.#{key} \"\\\n                        \"but got #{actual.inspect}: #{msg.inspect}\")\n          assert_equal(expected, actual, error_text)\n        end\n\n        def assert_anonymous(msg)\n          ['claimed_id', 'identity'].each do |key|\n            assert_openid_key_absent(msg, key)\n          end\n        end\n\n        def assert_has_required_fields(msg)\n          internal_message = @checkid_req.instance_variable_get(:@message)\n          assert_equal(preferred_namespace,\n                       internal_message.get_openid_namespace)\n\n          assert_equal(preferred_namespace, msg.get_openid_namespace)\n          assert_openid_value_equal(msg, 'mode', expected_mode)\n\n          # Implement these in subclasses because they depend on\n          # protocol differences!\n          assert_has_realm(msg)\n          assert_identifiers_present(msg)\n        end\n\n        # TESTS\n\n        def test_check_no_assoc_handle\n          @checkid_req.instance_variable_set('@assoc', nil)\n          msg = assert_log_matches(\"Generated checkid\") {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_openid_key_absent(msg, 'assoc_handle')\n        end\n\n        def test_add_extension_arg\n          @checkid_req.add_extension_arg('bag:', 'color', 'brown')\n          @checkid_req.add_extension_arg('bag:', 'material', 'paper')\n          assert(@checkid_req.message.namespaces.member?('bag:'))\n          assert_equal(@checkid_req.message.get_args('bag:'),\n                       {'color' => 'brown', 'material' => 'paper'})\n\n          msg = assert_log_matches(\"Generated checkid\") {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n\n          # XXX: this depends on the way that Message assigns\n          # namespaces. Really it doesn't care that it has alias \"0\",\n          # but that is tested anyway\n          post_args = msg.to_post_args()\n          assert_equal('brown', post_args['openid.ext0.color'])\n          assert_equal('paper', post_args['openid.ext0.material'])\n        end\n\n        def test_standard\n          msg = assert_log_matches('Generated checkid') {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_has_identifiers(msg, @endpoint.local_id, @endpoint.claimed_id)\n        end\n\n        def test_send_redirect?\n          silence_logging {\n            url = @checkid_req.redirect_url(@realm, @return_to, immediate)\n            assert(url.length < OPENID1_URL_LIMIT)\n            assert(@checkid_req.send_redirect?(@realm, @return_to, immediate))\n\n            @return_to << '/foo' * 1000\n            url = @checkid_req.redirect_url(@realm, @return_to, immediate)\n            assert(url.length > OPENID1_URL_LIMIT)\n            actual = @checkid_req.send_redirect?(@realm, @return_to, immediate)\n            expected = preferred_namespace != OPENID2_NS\n            assert_equal(expected, actual)\n          }\n        end\n      end\n\n      class TestCheckIDRequestOpenID2 < Minitest::Test\n        include CheckIDTestMixin\n\n        def immediate\n          false\n        end\n\n        def expected_mode\n          'checkid_setup'\n        end\n\n        def preferred_namespace\n          OPENID2_NS\n        end\n\n        # check presence of proper realm key and absence of the wrong\n        # one.\n        def assert_has_realm(msg)\n          assert_openid_value_equal(msg, 'realm', @realm)\n          assert_openid_key_absent(msg, 'trust_root')\n        end\n\n        def assert_identifiers_present(msg)\n          identity_present = msg.has_key?(OPENID_NS, 'identity')\n          claimed_present = msg.has_key?(OPENID_NS, 'claimed_id')\n\n          assert_equal(claimed_present, identity_present)\n        end\n\n        # OpenID Checkid_Requests should be able to set 'anonymous' to true.\n        def test_set_anonymous_works_for_openid2\n          assert(@checkid_req.message.is_openid2)\n          @checkid_req.anonymous = true\n          @checkid_req.anonymous = false\n        end\n\n        def test_user_anonymous_ignores_identfier\n          @checkid_req.anonymous = true\n          msg = assert_log_matches('Generated checkid') {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_has_required_fields(msg)\n          assert_anonymous(msg)\n        end\n\n        def test_op_anonymous_ignores_identifier\n          @endpoint.is_op_identifier = true\n          @checkid_req.anonymous = true\n          msg = assert_log_matches('Generated checkid') {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_has_required_fields(msg)\n          assert_anonymous(msg)\n        end\n\n        def test_op_identifier_sends_identifier_select\n          @endpoint.is_op_identifier = true\n          msg = assert_log_matches('Generated checkid') {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_has_required_fields(msg)\n          assert_has_identifiers(msg, IDENTIFIER_SELECT, IDENTIFIER_SELECT)\n        end\n\n        def test_no_assoc_handle\n          msg = assert_log_matches(\"Generated checkid\") {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_openid_key_absent(msg, 'assoc_handle')\n        end\n      end\n\n      class TestCheckIDRequestOpenID1 < Minitest::Test\n        include CheckIDTestMixin\n\n        def immediate\n          false\n        end\n\n        def preferred_namespace\n          OPENID1_NS\n        end\n\n        def expected_mode\n          'checkid_setup'\n        end\n\n        # Make sure claimed_is is *absent* in request.\n        def assert_has_identifiers(msg, op_specific_id, claimed_id)\n          assert_openid_value_equal(msg, 'identity', op_specific_id)\n          assert_openid_key_absent(msg, 'claimed_id')\n        end\n\n        def assert_identifiers_present(msg)\n          assert_openid_key_absent(msg, 'claimed_id')\n          assert(msg.has_key?(OPENID_NS, 'identity'))\n        end\n\n        # check presence of proper realm key and absence of the wrong\n        # one.\n        def assert_has_realm(msg)\n          assert_openid_value_equal(msg, 'trust_root', @realm)\n          assert_openid_key_absent(msg, 'realm')\n        end\n\n        # TESTS\n\n        # OpenID 1 requests MUST NOT be able to set anonymous to true\n        def test_set_anonymous_fails_for_openid1\n          assert(@checkid_req.message.is_openid1)\n          assert_raises(ArgumentError) {\n            @checkid_req.anonymous = true\n          }\n          @checkid_req.anonymous = false\n        end\n\n        # Identfier select SHOULD NOT be sent, but this pathway is in\n        # here in case some special discovery stuff is done to trigger\n        # it with OpenID 1. If it is triggered, it will send\n        # identifier_select just like OpenID 2.\n        def test_identifier_select\n          @endpoint.is_op_identifier = true\n          msg = assert_log_matches('Generated checkid') {\n            @checkid_req.get_message(@realm, @return_to, immediate)\n          }\n          assert_has_required_fields(msg)\n          assert_equal(IDENTIFIER_SELECT,\n                       msg.get_arg(OPENID1_NS, 'identity'))\n        end\n\n      end\n\n      class TestCheckIDRequestOpenID1Immediate < TestCheckIDRequestOpenID1\n        def immediate\n          true\n        end\n\n        def expected_mode\n          'checkid_immediate'\n        end\n      end\n\n      class TestCheckid_RequestOpenID2Immediate < TestCheckIDRequestOpenID2\n        def immediate\n          true\n        end\n\n        def expected_mode\n          'checkid_immediate'\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_consumer.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"openid/consumer\"\n\nmodule OpenID\n  class Consumer\n    module TestConsumer\n      class TestLastEndpoint < Minitest::Test\n        def test_set_get\n          session = {}\n          consumer = Consumer.new(session, nil)\n          consumer.send(:last_requested_endpoint=, :endpoint)\n          ep = consumer.send(:last_requested_endpoint)\n          assert_equal(:endpoint, ep)\n          ep = consumer.send(:last_requested_endpoint)\n          assert_equal(:endpoint, ep)\n          consumer.send(:cleanup_last_requested_endpoint)\n          ep = consumer.send(:last_requested_endpoint)\n          assert_nil(ep)\n        end\n      end\n\n      class TestBegin < Minitest::Test\n        attr_accessor :user_input, :anonymous, :services,\n          :discovered_identifier, :checkid_request, :service\n\n        def setup\n          @discovered_identifier = 'http://discovered/'\n          @user_input = 'user.input'\n          @service = :service\n          @services = [@service]\n          @session = {}\n          @anonymous = false\n          @checkid_request = :checkid_request\n        end\n\n        def consumer\n          test = self\n          consumer = Consumer.new(@session, nil)\n          consumer.extend(InstanceDefExtension)\n          consumer.instance_def(:discover) do |identifier|\n            test.assert_equal(test.user_input, identifier)\n            [test.discovered_identifier, test.services]\n          end\n          consumer.instance_def(:begin_without_discovery) do\n            |service, sent_anonymous|\n            test.assert_equal(test.service, service)\n            test.assert_equal(test.anonymous, sent_anonymous)\n            test.checkid_request\n          end\n          consumer\n        end\n\n        def test_begin\n          checkid_request = consumer.begin(@user_input, @anonymous)\n          assert_equal(:checkid_request, checkid_request)\n          assert_equal(['OpenID::Consumer::DiscoveredServices::'\\\n                        'OpenID::Consumer::'], @session.keys.sort!)\n        end\n\n        def test_begin_failure\n          @services = []\n          assert_raises(DiscoveryFailure) {\n            consumer.begin(@user_input, @anonymous)\n          }\n        end\n\n        def test_begin_fallback\n          @services = [:service1, :service2]\n          consumer = self.consumer\n          @service = :service1\n          consumer.begin(@user_input, @anonymous)\n          @service = :service2\n          consumer.begin(@user_input, @anonymous)\n          @service = :service1\n          consumer.begin(@user_input, @anonymous)\n          @service = :service2\n          consumer.begin(@user_input, @anonymous)\n        end\n      end\n\n      class TestBeginWithoutDiscovery < Minitest::Test\n        attr_reader :assoc\n        def setup\n          @session = {}\n          @assoc = :assoc\n          @service = OpenIDServiceEndpoint.new\n          @claimed_id = 'http://claimed.id/'\n          @service.claimed_id = @claimed_id\n          @anonymous = false\n        end\n\n        def consumer\n          test = self\n          assoc_manager = Object.new\n          assoc_manager.extend(InstanceDefExtension)\n          assoc_manager.instance_def(:get_association) do\n            test.assoc\n          end\n\n          consumer = Consumer.new(@session, nil)\n          consumer.extend(InstanceDefExtension)\n          consumer.instance_def(:association_manager) do |service|\n            assoc_manager\n          end\n          consumer\n        end\n\n        def call_begin_without_discovery\n          result = consumer.begin_without_discovery(@service, @anonymous)\n          assert(result.instance_of?(CheckIDRequest))\n          assert_equal(@anonymous, result.anonymous)\n          assert_equal(@service, consumer.send(:last_requested_endpoint))\n          assert_equal(result.instance_variable_get(:@assoc), @assoc)\n          return result\n        end\n\n        def cid_name\n          Consumer.openid1_return_to_claimed_id_name\n        end\n\n        def nonce_name\n          Consumer.openid1_return_to_nonce_name\n        end\n\n        def test_begin_without_openid1\n          result = call_begin_without_discovery\n\n          assert_equal(@claimed_id, result.return_to_args[cid_name])\n          assert_equal([cid_name, nonce_name].sort!,\n                       result.return_to_args.keys.sort!)\n        end\n\n        def test_begin_without_openid1_anonymous\n          @anonymous = true\n          assert_raises(ArgumentError) {\n            call_begin_without_discovery\n          }\n        end\n\n        def test_begin_without_openid2\n          @service.type_uris = [OPENID_2_0_TYPE]\n          result = call_begin_without_discovery\n\n          assert(result.return_to_args.empty?)\n        end\n\n        def test_begin_without_openid2_anonymous\n          @anonymous = true\n          @service.type_uris = [OPENID_2_0_TYPE]\n          result = call_begin_without_discovery\n\n          assert(result.return_to_args.empty?)\n        end\n      end\n\n      class TestComplete < Minitest::Test\n        def setup\n          @session = {}\n          @consumer = Consumer.new(@session, nil)\n        end\n\n        def test_bad_mode\n          response = @consumer.complete({'openid.ns' => OPENID2_NS,\n                                        'openid.mode' => 'bad'}, nil)\n          assert_equal(FAILURE, response.status)\n        end\n\n        def test_missing_mode\n          response = @consumer.complete({'openid.ns' => OPENID2_NS}, nil)\n          assert_equal(FAILURE, response.status)\n        end\n\n        def test_cancel\n          response = @consumer.complete({'openid.mode' => 'cancel'}, nil)\n          assert_equal(CANCEL, response.status)\n        end\n\n        def test_setup_needed_openid1\n          response = @consumer.complete({'openid.mode' => 'setup_needed'}, nil)\n          assert_equal(FAILURE, response.status)\n        end\n\n        def test_setup_needed_openid2\n          setup_url = 'http://setup.url/'\n          args = {'openid.ns' => OPENID2_NS, 'openid.mode' => 'setup_needed', 'openid.user_setup_url' => setup_url}\n          response = @consumer.complete(args, nil)\n          assert_equal(SETUP_NEEDED, response.status)\n          assert_equal(setup_url, response.setup_url)\n        end\n\n        def test_idres_setup_needed_openid1\n          setup_url = 'http://setup.url/'\n          args = {\n            'openid.user_setup_url' => setup_url,\n            'openid.mode' => 'id_res',\n          }\n          response = @consumer.complete(args, nil)\n          assert_equal(SETUP_NEEDED, response.status)\n          assert_equal(setup_url, response.setup_url)\n        end\n\n        def test_error\n          contact = 'me'\n          reference = 'thing thing'\n          args = {\n            'openid.mode' => 'error',\n            'openid.contact' => contact,\n            'openid.reference' => reference,\n          }\n          response = @consumer.complete(args, nil)\n          assert_equal(FAILURE, response.status)\n          assert_equal(contact, response.contact)\n          assert_equal(reference, response.reference)\n\n          args['openid.ns'] = OPENID2_NS\n          response = @consumer.complete(args, nil)\n          assert_equal(FAILURE, response.status)\n          assert_equal(contact, response.contact)\n          assert_equal(reference, response.reference)\n        end\n\n        def test_idres_openid1\n          args = {\n            'openid.mode' => 'id_res',\n          }\n\n          endpoint = OpenIDServiceEndpoint.new\n          endpoint.claimed_id = :test_claimed_id\n\n          idres = Object.new\n          idres.extend(InstanceDefExtension)\n          idres.instance_def(:endpoint){endpoint}\n          idres.instance_def(:signed_fields){:test_signed_fields}\n\n          test = self\n          @consumer.extend(InstanceDefExtension)\n          @consumer.instance_def(:handle_idres) {|message, return_to|\n            test.assert_equal(args, message.to_post_args)\n            test.assert_equal(:test_return_to, return_to)\n            idres\n          }\n\n          response = @consumer.complete(args, :test_return_to)\n          assert_equal(SUCCESS, response.status, response.message)\n          assert_equal(:test_claimed_id, response.identity_url)\n          assert_equal(endpoint, response.endpoint)\n\n          error_message = \"In Soviet Russia, id_res handles you!\"\n          @consumer.instance_def(:handle_idres) {|message, return_to|\n            raise ProtocolError, error_message\n          }\n          response = @consumer.complete(args, :test_return_to)\n          assert_equal(FAILURE, response.status)\n          assert_equal(error_message, response.message)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_cryptutil.rb",
    "content": "# coding: ASCII-8BIT\nrequire \"minitest/autorun\"\nrequire \"openid/cryptutil\"\nrequire \"pathname\"\n\nclass CryptUtilTestCase < Minitest::Test\n  BIG = 2 ** 256\n\n  def test_rand\n    # If this is not true, the rest of our test won't work\n    assert(BIG.is_a?(Bignum))\n\n    # It's possible that these will be small enough for fixnums, but\n    # extraorindarily unlikely.\n    a = OpenID::CryptUtil.rand(BIG)\n    b = OpenID::CryptUtil.rand(BIG)\n    assert(a.is_a?(Bignum))\n    assert(b.is_a?(Bignum))\n    refute_equal(a, b)\n  end\n\n  def test_rand_doesnt_depend_on_srand\n    Kernel.srand(1)\n    a = OpenID::CryptUtil.rand(BIG)\n    Kernel.srand(1)\n    b = OpenID::CryptUtil.rand(BIG)\n    refute_equal(a, b)\n  end\n\n  def test_random_binary_convert\n    (0..500).each do\n      n = (0..10).inject(0) {|sum, element| sum + OpenID::CryptUtil.rand(BIG) }\n      s = OpenID::CryptUtil.num_to_binary n\n      assert(s.is_a?(String))\n      n_converted_back = OpenID::CryptUtil.binary_to_num(s)\n      assert_equal(n, n_converted_back)\n    end\n  end\n\n  def test_enumerated_binary_convert\n    {\n        \"\\x00\" => 0,\n        \"\\x01\" => 1,\n        \"\\x7F\" => 127,\n        \"\\x00\\xFF\" => 255,\n        \"\\x00\\x80\" => 128,\n        \"\\x00\\x81\" => 129,\n        \"\\x00\\x80\\x00\" => 32768,\n        \"OpenID is cool\" => 1611215304203901150134421257416556,\n    }.each do |str, num|\n      num_prime = OpenID::CryptUtil.binary_to_num(str)\n      str_prime = OpenID::CryptUtil.num_to_binary(num)\n      assert_equal(num, num_prime)\n      assert_equal(str, str_prime)\n    end\n  end\n\n  def with_n2b64\n    test_dir = Pathname.new(__FILE__).dirname\n    filename = test_dir.join('data', 'n2b64')\n    File.open(filename) do |file|\n      file.each_line do |line|\n        base64, base10 = line.chomp.split\n        yield base64, base10.to_i\n      end\n    end\n  end\n\n  def test_base64_to_num\n    with_n2b64 do |base64, num|\n      assert_equal(num, OpenID::CryptUtil.base64_to_num(base64))\n    end\n  end\n\n  def test_base64_to_num_invalid\n    assert_raises(ArgumentError) {\n      OpenID::CryptUtil.base64_to_num('!@#$')\n    }\n  end\n\n  def test_num_to_base64\n    with_n2b64 do |base64, num|\n      assert_equal(base64, OpenID::CryptUtil.num_to_base64(num))\n    end\n  end\n\n  def test_randomstring\n    s1 = OpenID::CryptUtil.random_string(42)\n    assert_equal(42, s1.length)\n    s2 = OpenID::CryptUtil.random_string(42)\n    assert_equal(42, s2.length)\n    refute_equal(s1, s2)\n  end\n\n  def test_randomstring_population\n    s1 = OpenID::CryptUtil.random_string(42, \"XO\")\n    assert_match(/[XO]{42}/, s1)\n  end\n\n  def test_sha1\n    assert_equal(\"\\x11\\xf6\\xad\\x8e\\xc5*)\\x84\\xab\\xaa\\xfd|;Qe\\x03x\\\\ r\",\n                 OpenID::CryptUtil.sha1('x'))\n  end\n\n  def test_hmac_sha1\n    assert_equal(\"\\x8bo\\xf7O\\xa7\\x18*\\x90\\xac ah\\x16\\xf7\\xb8\\x81JB\\x9f|\",\n                 OpenID::CryptUtil.hmac_sha1('x', 'x'))\n  end\n\n  def test_sha256\n    assert_equal(\"-q\\x16B\\xb7&\\xb0D\\x01b|\\xa9\\xfb\\xac2\\xf5\\xc8S\\x0f\\xb1\\x90<\\xc4\\xdb\\x02%\\x87\\x17\\x92\\x1aH\\x81\",\n                 OpenID::CryptUtil.sha256('x'))\n  end\n\n  def test_hmac_sha256\n    assert_equal(\"\\x94{\\xd2w\\xb2\\xd3\\\\\\xfc\\x07\\xfb\\xc7\\xe3b\\xf2iuXz1\\xf8:}\\xffx\\x8f\\xda\\xc1\\xfaC\\xc4\\xb2\\x87\",\n                 OpenID::CryptUtil.hmac_sha256('x', 'x'))\n  end\nend\n"
  },
  {
    "path": "test/test_dh.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'openid/dh'\n\nmodule OpenID\n  class DiffieHellmanExposed < OpenID::DiffieHellman\n    def DiffieHellmanExposed.strxor_for_testing(a, b)\n      return DiffieHellmanExposed.strxor(a, b)\n    end\n  end\n\n  class DiffieHellmanTestCase < Minitest::Test\n    include OpenID::TestDataMixin\n\n    NUL = \"\\x00\"\n\n    def test_strxor_success\n      [#input 1   input 2   expected\n       [NUL,      NUL,      NUL     ],\n       [\"\\x01\",   NUL,      \"\\x01\"  ],\n       [\"a\",      \"a\",      NUL     ],\n       [\"a\",      NUL,      \"a\"     ],\n       [\"abc\",    NUL * 3,  \"abc\"   ],\n       [\"x\" * 10, NUL * 10, \"x\" * 10],\n       [\"\\x01\",   \"\\x02\",   \"\\x03\"  ],\n       [\"\\xf0\",   \"\\x0f\",   \"\\xff\"  ],\n       [\"\\xff\",   \"\\x0f\",   \"\\xf0\"  ],\n      ].each do |input1, input2, expected|\n        actual = DiffieHellmanExposed.strxor_for_testing(input1, input2)\n        assert_equal(expected.force_encoding(\"UTF-8\"), actual.force_encoding(\"UTF-8\"))\n      end\n    end\n\n    def test_strxor_failure\n      [\n       ['',      'a'    ],\n       ['foo',   'ba'   ],\n       [NUL * 3, NUL * 4],\n       [255,     127    ].map{|h| (0..h).map{|i|i.chr}.join('')},\n      ].each do |aa, bb|\n        assert_raises(ArgumentError) {\n          DiffieHellmanExposed.strxor(aa, bb)\n        }\n      end\n    end\n\n    def test_simple_exchange\n      dh1 = DiffieHellman.from_defaults()\n      dh2 = DiffieHellman.from_defaults()\n      secret1 = dh1.get_shared_secret(dh2.public)\n      secret2 = dh2.get_shared_secret(dh1.public)\n      assert_equal(secret1, secret2)\n    end\n\n    def test_xor_secret\n      dh1 = DiffieHellman.from_defaults()\n      dh2 = DiffieHellman.from_defaults()\n      secret = \"Shhhhhh! don't tell!\"\n      encrypted = dh1.xor_secret((CryptUtil.method :sha1), dh2.public, secret)\n      decrypted = dh2.xor_secret((CryptUtil.method :sha1), dh1.public, encrypted)\n      assert_equal(secret, decrypted)\n    end\n\n    def test_dh\n      dh = DiffieHellman.from_defaults()\n      class << dh\n        def set_private_test(priv)\n          set_private(priv)\n        end\n      end\n\n      read_data_file('dh.txt', true).each do |line|\n        priv, pub = line.split(' ').map {|x| x.to_i}\n        dh.set_private_test(priv)\n        assert_equal(dh.public, pub)\n      end\n    end\n\n    def test_using_defaults\n      dh = DiffieHellman.from_defaults()\n      assert(dh.using_default_values?)\n      dh = DiffieHellman.new(3, 2750161)\n      assert(!dh.using_default_values?)\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_discover.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'util'\nrequire 'openid/fetchers'\nrequire 'openid/yadis/discovery'\nrequire 'openid/consumer/discovery'\nrequire 'openid/yadis/xrires'\nrequire 'openid/yadis/xri'\nrequire 'openid/message'\nrequire 'openid/util'\n\n### Tests for conditions that trigger DiscoveryFailure\n\nmodule OpenID\n  class SimpleMockFetcher\n    def initialize(test, responses)\n      @test = test\n      @responses = responses.dup\n    end\n\n    def fetch(url, body=nil, headers=nil, limit=nil)\n      response = @responses.shift\n      @test.assert(body.nil?)\n      @test.assert_equal(response.final_url, url)\n      return response\n    end\n  end\n\n  class TestDiscoveryFailure < Minitest::Test\n    def initialize(*args)\n      super(*args)\n\n      @responses = [\n                    [HTTPResponse._from_raw_data(nil, nil, {}, 'http://network.error/')],\n                    [HTTPResponse._from_raw_data(404, nil, {}, 'http://not.found/')],\n                    [HTTPResponse._from_raw_data(400, nil, {}, 'http://bad.request/')],\n                    [HTTPResponse._from_raw_data(500, nil, {}, 'http://server.error/')],\n                    [HTTPResponse._from_raw_data(200, nil, {'x-xrds-location' => 'http://xrds.missing/'},\n                                                 'http://header.found/'),\n                     HTTPResponse._from_raw_data(404, nil, {}, 'http://xrds.missing/')],\n                    ]\n    end\n\n    def test_discovery_failure\n\n      @responses.each { |response_set|\n        @url = response_set[0].final_url\n        OpenID.fetcher = SimpleMockFetcher.new(self, response_set)\n\n        expected_status = response_set[-1].code\n        begin\n          OpenID.discover(@url)\n        rescue DiscoveryFailure => why\n          assert_equal(why.http_response.code, expected_status)\n        else\n          flunk('Did not raise DiscoveryFailure')\n        end\n\n        OpenID.fetcher = nil\n      }\n    end\n  end\n\n  ### Tests for raising/catching exceptions from the fetcher through\n  ### the discover function\n\n  class ErrorRaisingFetcher\n    # Just raise an exception when fetch is called\n\n    def initialize(thing_to_raise)\n      @thing_to_raise = thing_to_raise\n    end\n\n    def fetch(url, body=nil, headers=nil, limit=nil)\n      raise @thing_to_raise\n    end\n  end\n\n  class DidFetch < Exception\n    # Custom exception just to make sure it's not handled differently\n  end\n\n  class TestFetchException < Minitest::Test\n    # Discovery should only raise DiscoveryFailure\n\n    def initialize(*args)\n      super(*args)\n\n      @cases = [\n                DidFetch.new(),\n                Exception.new(),\n                ArgumentError.new(),\n                RuntimeError.new(),\n               ]\n    end\n\n    def test_fetch_exception\n      @cases.each { |exc|\n        OpenID.fetcher = ErrorRaisingFetcher.new(exc)\n        assert_raises(DiscoveryFailure) {\n          OpenID.discover('http://doesnt.matter/')\n        }\n        OpenID.fetcher = nil\n      }\n    end\n  end\n\n  ### Tests for openid.consumer.discover.discover\n\n  class TestNormalization < Minitest::Test\n    def test_addingProtocol\n      f = ErrorRaisingFetcher.new(RuntimeError.new())\n      OpenID.fetcher = f\n\n      begin\n        OpenID.discover('users.stompy.janrain.com:8000/x')\n      rescue DiscoveryFailure => why\n        assert why.to_s.match(\"Failed to fetch\")\n      rescue RuntimeError\n      end\n\n      OpenID.fetcher = nil\n    end\n  end\n\n  class DiscoveryMockFetcher\n    def initialize(documents)\n      @redirect = nil\n      @documents = documents\n      @fetchlog = []\n    end\n\n    def fetch(url, body=nil, headers=nil, limit=nil)\n      @fetchlog << [url, body, headers]\n      if @redirect\n        final_url = @redirect\n      else\n        final_url = url\n      end\n\n      begin\n        ctype, body = @documents.fetch(url)\n      rescue IndexError\n        status = 404\n        ctype = 'text/plain'\n        body = ''\n      else\n        status = 200\n      end\n\n      return HTTPResponse._from_raw_data(status, body, {'content-type' => ctype}, final_url)\n    end\n  end\n\n  class BaseTestDiscovery < Minitest::Test\n    attr_accessor :id_url, :fetcher_class\n\n    def initialize(*args)\n      super(*args)\n      @id_url = \"http://someuser.unittest/\"\n      @documents = {}\n      @fetcher_class = DiscoveryMockFetcher\n    end\n\n    def _checkService(s, server_url, claimed_id=nil,\n                      local_id=nil, canonical_id=nil,\n                      types=nil, used_yadis=false,\n                      display_identifier=nil)\n      assert_equal(server_url, s.server_url)\n      if types == ['2.0 OP']\n        assert(!claimed_id)\n        assert(!local_id)\n        assert(!s.claimed_id)\n        assert(!s.local_id)\n        assert(!s.get_local_id())\n        assert(!s.compatibility_mode())\n        assert(s.is_op_identifier())\n        assert_equal(s.preferred_namespace(),\n                     OPENID_2_0_MESSAGE_NS)\n      else\n        assert_equal(claimed_id, s.claimed_id)\n        assert_equal(local_id, s.get_local_id())\n      end\n\n      if used_yadis\n        assert(s.used_yadis, \"Expected to use Yadis\")\n      else\n        assert(!s.used_yadis,\n               \"Expected to use old-style discovery\")\n      end\n\n      openid_types = {\n        '1.1' => OPENID_1_1_TYPE,\n        '1.0' => OPENID_1_0_TYPE,\n        '2.0' => OPENID_2_0_TYPE,\n        '2.0 OP' => OPENID_IDP_2_0_TYPE,\n      }\n\n      type_uris = types.collect { |t| openid_types[t] }\n\n      assert_equal(type_uris, s.type_uris)\n      assert_equal(canonical_id, s.canonical_id)\n\n      if canonical_id.nil?\n        assert_equal(claimed_id, s.display_identifier)\n      else\n        assert_equal(display_identifier, s.display_identifier)\n      end\n    end\n\n    def setup\n      # @documents = @documents.dup\n      @fetcher = @fetcher_class.new(@documents)\n      OpenID.fetcher = @fetcher\n    end\n\n    def teardown\n      OpenID.fetcher = nil\n    end\n\n    def test_blank\n      # XXX to avoid > 0 test requirement\n    end\n  end\n\n#   def readDataFile(filename):\n#     module_directory = os.path.dirname(os.path.abspath(__file__))\n#     filename = os.path.join(\n#         module_directory, 'data', 'test_discover', filename)\n#     return file(filename).read()\n\n  class TestDiscovery < BaseTestDiscovery\n    include TestDataMixin\n\n    def _discover(content_type, data,\n                  expected_services, expected_id=nil)\n      if expected_id.nil?\n        expected_id = @id_url\n      end\n\n      @documents[@id_url] = [content_type, data]\n      id_url, services = OpenID.discover(@id_url)\n\n      assert_equal(expected_services, services.length)\n      assert_equal(expected_id, id_url)\n      return services\n    end\n\n    def test_404\n      assert_raises(DiscoveryFailure) {\n        OpenID.discover(@id_url + '/404')\n      }\n    end\n\n    def test_noOpenID\n      services = _discover('text/plain',\n                           \"junk\", 0)\n\n      services = _discover(\n                           'text/html',\n                           read_data_file('test_discover/openid_no_delegate.html', false),\n                           1)\n\n      _checkService(\n                    services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    @id_url,\n                    nil,\n                    ['1.1'],\n                    false)\n    end\n\n    def test_malformed_meta_tag\n      @id_url = \"http://user.myopenid.com/\"\n\n      services = _discover(\n                           'text/html',\n                           read_data_file('test_discover/malformed_meta_tag.html', false),\n                           2)\n\n      _checkService(\n                    services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    @id_url,\n                    nil,\n                    ['2.0'],\n                    false)\n\n      _checkService(\n                    services[1],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    @id_url,\n                    nil,\n                    ['1.1'],\n                    false)\n    end\n\n    def test_html1\n      services = _discover('text/html',\n                           read_data_file('test_discover/openid.html', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['1.1'],\n                    false)\n    end\n\n    def test_html1Fragment\n      # Ensure that the Claimed Identifier does not have a fragment if\n      # one is supplied in the User Input.\n      content_type = 'text/html'\n      data = read_data_file('test_discover/openid.html', false)\n      expected_services = 1\n\n      @documents[@id_url] = [content_type, data]\n      expected_id = @id_url\n      @id_url = @id_url + '#fragment'\n      id_url, services = OpenID.discover(@id_url)\n\n      assert_equal(expected_services, services.length)\n      assert_equal(expected_id, id_url)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    expected_id,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['1.1'],\n                    false)\n    end\n\n    def test_html2\n      services = _discover('text/html',\n                           read_data_file('test_discover/openid2.html', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['2.0'],\n                    false)\n    end\n\n    def test_html1And2\n      services = _discover(\n                           'text/html',\n                           read_data_file('test_discover/openid_1_and_2.html', false),\n                           2)\n\n      services.zip(['2.0', '1.1']).each { |s, t|\n          _checkService(s,\n                        \"http://www.myopenid.com/server\",\n                        @id_url,\n                        'http://smoker.myopenid.com/',\n                        nil,\n                        [t],\n                        false)\n      }\n    end\n\n    def test_html_utf8\n      utf8_html = read_data_file('test_discover/openid_utf8.html', false)\n      utf8_html.force_encoding(\"UTF-8\") if utf8_html.respond_to?(:force_encoding)\n      services = _discover('text/html', utf8_html, 1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['1.1'],\n                    false)\n    end\n\n    def test_yadisEmpty\n      _discover('application/xrds+xml',\n                read_data_file('test_discover/yadis_0entries.xml', false),\n                0)\n    end\n\n    def test_htmlEmptyYadis\n      # HTML document has discovery information, but points to an\n      # empty Yadis document.  The XRDS document pointed to by\n      # \"openid_and_yadis.html\"\n      @documents[@id_url + 'xrds'] = ['application/xrds+xml',\n                                      read_data_file('test_discover/yadis_0entries.xml', false)]\n\n      services = _discover('text/html',\n                           read_data_file('test_discover/openid_and_yadis.html', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['1.1'],\n                    false)\n    end\n\n    def test_yadis1NoDelegate\n      services = _discover('application/xrds+xml',\n                           read_data_file('test_discover/yadis_no_delegate.xml', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    @id_url,\n                    nil,\n                    ['1.0'],\n                    true)\n    end\n\n    def test_yadis2NoLocalID\n      services = _discover('application/xrds+xml',\n                           read_data_file('test_discover/openid2_xrds_no_local_id.xml', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    @id_url,\n                    nil,\n                    ['2.0'],\n                    true)\n    end\n\n    def test_yadis2\n      services = _discover('application/xrds+xml',\n                           read_data_file('test_discover/openid2_xrds.xml', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['2.0'],\n                    true)\n    end\n\n    def test_yadis2OP\n      services = _discover('application/xrds+xml',\n                           read_data_file('test_discover/yadis_idp.xml', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    nil, nil, nil,\n                    ['2.0 OP'],\n                    true)\n    end\n\n    def test_yadis2OPDelegate\n      # The delegate tag isn't meaningful for OP entries.\n      services = _discover('application/xrds+xml',\n                           read_data_file('test_discover/yadis_idp_delegate.xml', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    nil, nil, nil,\n                    ['2.0 OP'],\n                    true)\n    end\n\n    def test_yadis2BadLocalID\n      assert_raises(DiscoveryFailure) {\n        _discover('application/xrds+xml',\n                  read_data_file('test_discover/yadis_2_bad_local_id.xml', false),\n                  1)\n      }\n    end\n\n    def test_yadis1And2\n      services = _discover('application/xrds+xml',\n                           read_data_file('test_discover/openid_1_and_2_xrds.xml', false),\n                           1)\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    @id_url,\n                    'http://smoker.myopenid.com/',\n                    nil,\n                    ['2.0', '1.1'],\n                    true)\n    end\n\n    def test_yadis1And2BadLocalID\n      assert_raises(DiscoveryFailure) {\n        _discover('application/xrds+xml',\n                  read_data_file('test_discover/openid_1_and_2_xrds_bad_delegate.xml', false),\n                  1)\n      }\n    end\n  end\n\n  class MockFetcherForXRIProxy\n\n    def initialize(documents, proxy_url=Yadis::XRI::ProxyResolver::DEFAULT_PROXY)\n      @documents = documents\n      @fetchlog = []\n      @proxy_url = nil\n    end\n\n    def fetch(url, body=nil, headers=nil, limit=nil)\n      @fetchlog << [url, body, headers]\n\n      u = URI::parse(url)\n      xri = u.path\n      query = u.query\n\n      if !headers and !query\n        raise ArgumentError.new(\"No headers or query; you probably didn't \" +\n                                \"mean to do that.\")\n      end\n\n      if xri.start_with?('/')\n        xri = xri[1..-1]\n      end\n\n      begin\n        ctype, body = @documents.fetch(xri)\n      rescue IndexError\n        status = 404\n        ctype = 'text/plain'\n        body = ''\n      else\n        status = 200\n      end\n\n      return HTTPResponse._from_raw_data(status, body,\n                                         {'content-type' => ctype}, url)\n    end\n  end\n\n  class TestXRIDiscovery < BaseTestDiscovery\n\n    include TestDataMixin\n    include TestUtil\n\n    def initialize(*args)\n      super(*args)\n\n      @fetcher_class = MockFetcherForXRIProxy\n\n      @documents = {'=smoker' => ['application/xrds+xml',\n                                  read_data_file('test_discover/yadis_2entries_delegate.xml', false)],\n        '=smoker*bad' => ['application/xrds+xml',\n                          read_data_file('test_discover/yadis_another_delegate.xml', false)]}\n    end\n\n    def test_xri\n      _, services = OpenID.discover_xri('=smoker')\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    'http://smoker.myopenid.com/',\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    ['1.0'],\n                    true,\n                    '=smoker')\n\n      _checkService(services[1],\n                    \"http://www.livejournal.com/openid/server.bml\",\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    'http://frank.livejournal.com/',\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    ['1.0'],\n                    true,\n                    '=smoker')\n    end\n\n    def test_xri_normalize\n      _, services = OpenID.discover_xri('xri://=smoker')\n\n      _checkService(services[0],\n                    \"http://www.myopenid.com/server\",\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    'http://smoker.myopenid.com/',\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    ['1.0'],\n                    true,\n                    '=smoker')\n\n      _checkService(services[1],\n                    \"http://www.livejournal.com/openid/server.bml\",\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    'http://frank.livejournal.com/',\n                    Yadis::XRI.make_xri(\"=!1000\"),\n                    ['1.0'],\n                    true,\n                    '=smoker')\n    end\n\n    def test_xriNoCanonicalID\n      silence_logging {\n        _, services = OpenID.discover_xri('=smoker*bad')\n        assert(services.empty?)\n      }\n    end\n\n    def test_useCanonicalID\n      # When there is no delegate, the CanonicalID should be used with\n      # XRI.\n      endpoint = OpenIDServiceEndpoint.new()\n      endpoint.claimed_id = Yadis::XRI.make_xri(\"=!1000\")\n      endpoint.canonical_id = Yadis::XRI.make_xri(\"=!1000\")\n      assert_equal(endpoint.get_local_id, Yadis::XRI.make_xri(\"=!1000\"))\n    end\n  end\n\n  class TestXRIDiscoveryIDP < BaseTestDiscovery\n    include TestDataMixin\n\n    def initialize(*args)\n      super(*args)\n\n      @fetcher_class = MockFetcherForXRIProxy\n\n      @documents = {'=smoker' => ['application/xrds+xml',\n                                  read_data_file('test_discover/yadis_2entries_idp.xml', false)] }\n    end\n\n    def test_xri\n      _, services = OpenID.discover_xri('=smoker')\n      assert(!services.empty?, \"Expected services, got zero\")\n      assert_equal(services[0].server_url,\n                   \"http://www.livejournal.com/openid/server.bml\")\n    end\n  end\n\n  class TestPreferredNamespace < Minitest::Test\n    def initialize(*args)\n      super(*args)\n\n      @cases = [\n               [OPENID1_NS, []],\n               [OPENID1_NS, ['http://jyte.com/']],\n               [OPENID1_NS, [OPENID_1_0_TYPE]],\n               [OPENID1_NS, [OPENID_1_1_TYPE]],\n               [OPENID2_NS, [OPENID_2_0_TYPE]],\n               [OPENID2_NS, [OPENID_IDP_2_0_TYPE]],\n               [OPENID2_NS, [OPENID_2_0_TYPE,\n                             OPENID_1_0_TYPE]],\n               [OPENID2_NS, [OPENID_1_0_TYPE,\n                             OPENID_2_0_TYPE]],\n              ]\n    end\n\n    def test_preferred_namespace\n\n      @cases.each { |expected_ns, type_uris|\n        endpoint = OpenIDServiceEndpoint.new()\n        endpoint.type_uris = type_uris\n        actual_ns = endpoint.preferred_namespace()\n        assert_equal(actual_ns, expected_ns)\n      }\n    end\n  end\n\n  class TestIsOPIdentifier < Minitest::Test\n    def setup\n      @endpoint = OpenIDServiceEndpoint.new()\n    end\n\n    def test_none\n      assert(!@endpoint.is_op_identifier())\n    end\n\n    def test_openid1_0\n      @endpoint.type_uris = [OPENID_1_0_TYPE]\n      assert(!@endpoint.is_op_identifier())\n    end\n\n    def test_openid1_1\n      @endpoint.type_uris = [OPENID_1_1_TYPE]\n      assert(!@endpoint.is_op_identifier())\n    end\n\n    def test_openid2\n      @endpoint.type_uris = [OPENID_2_0_TYPE]\n      assert(!@endpoint.is_op_identifier())\n    end\n\n    def test_openid2OP\n      @endpoint.type_uris = [OPENID_IDP_2_0_TYPE]\n      assert(@endpoint.is_op_identifier())\n    end\n\n    def test_multipleMissing\n      @endpoint.type_uris = [OPENID_2_0_TYPE,\n                             OPENID_1_0_TYPE]\n      assert(!@endpoint.is_op_identifier())\n    end\n\n    def test_multiplePresent\n      @endpoint.type_uris = [OPENID_2_0_TYPE,\n                             OPENID_1_0_TYPE,\n                             OPENID_IDP_2_0_TYPE]\n      assert(@endpoint.is_op_identifier())\n    end\n  end\n\n  class TestFromOPEndpointURL < Minitest::Test\n    def setup\n      @op_endpoint_url = 'http://example.com/op/endpoint'\n      @endpoint = OpenIDServiceEndpoint.from_op_endpoint_url(@op_endpoint_url)\n    end\n\n    def test_isOPEndpoint\n      assert(@endpoint.is_op_identifier())\n    end\n\n    def test_noIdentifiers\n      assert_nil(@endpoint.get_local_id)\n      assert_nil(@endpoint.claimed_id)\n    end\n\n    def test_compatibility\n      assert(!@endpoint.compatibility_mode())\n    end\n\n    def test_canonical_id\n      assert_nil(@endpoint.canonical_id)\n    end\n\n    def test_serverURL\n      assert_equal(@endpoint.server_url, @op_endpoint_url)\n    end\n  end\n\n  class TestDiscoverFunction < Minitest::Test\n    def test_discover_function\n      # XXX these were all different tests in python, but they're\n      # combined here so I only have to use with_method_overridden\n      # once.\n      discoverXRI = Proc.new { |identifier| 'XRI' }\n\n      discoverURI = Proc.new { |identifier| 'URI' }\n\n      OpenID.extend(OverrideMethodMixin)\n\n      OpenID.with_method_overridden(:discover_uri, discoverURI) do\n        OpenID.with_method_overridden(:discover_xri, discoverXRI) do\n          assert_equal('URI', OpenID.discover('http://woo!'))\n          assert_equal('URI', OpenID.discover('not a URL or XRI'))\n          assert_equal('XRI', OpenID.discover('xri://=something'))\n          assert_equal('XRI', OpenID.discover('=something'))\n        end\n      end\n    end\n  end\n\n  class TestEndpointSupportsType < Minitest::Test\n    def setup\n      @endpoint = OpenIDServiceEndpoint.new()\n    end\n\n    def failUnlessSupportsOnly(*types)\n      ['foo',\n       OPENID_1_1_TYPE,\n       OPENID_1_0_TYPE,\n       OPENID_2_0_TYPE,\n       OPENID_IDP_2_0_TYPE].each { |t|\n        if types.member?(t)\n          assert(@endpoint.supports_type(t),\n                 sprintf(\"Must support %s\", t))\n        else\n          assert(!@endpoint.supports_type(t),\n                 sprintf(\"Shouldn't support %s\", t))\n        end\n      }\n    end\n\n    def test_supportsNothing\n      failUnlessSupportsOnly()\n    end\n\n    def test_openid2\n      @endpoint.type_uris = [OPENID_2_0_TYPE]\n      failUnlessSupportsOnly(OPENID_2_0_TYPE)\n    end\n\n    def test_openid2provider\n      @endpoint.type_uris = [OPENID_IDP_2_0_TYPE]\n      failUnlessSupportsOnly(OPENID_IDP_2_0_TYPE,\n                             OPENID_2_0_TYPE)\n    end\n\n    def test_openid1_0\n      @endpoint.type_uris = [OPENID_1_0_TYPE]\n      failUnlessSupportsOnly(OPENID_1_0_TYPE)\n    end\n\n    def test_openid1_1\n      @endpoint.type_uris = [OPENID_1_1_TYPE]\n      failUnlessSupportsOnly(OPENID_1_1_TYPE)\n    end\n\n    def test_multiple\n      @endpoint.type_uris = [OPENID_1_1_TYPE,\n                             OPENID_2_0_TYPE]\n      failUnlessSupportsOnly(OPENID_1_1_TYPE,\n                             OPENID_2_0_TYPE)\n    end\n\n    def test_multipleWithProvider\n      @endpoint.type_uris = [OPENID_1_1_TYPE,\n                             OPENID_2_0_TYPE,\n                             OPENID_IDP_2_0_TYPE]\n      failUnlessSupportsOnly(OPENID_1_1_TYPE,\n                             OPENID_2_0_TYPE,\n                             OPENID_IDP_2_0_TYPE)\n    end\n  end\n\n  class TestEndpointDisplayIdentifier < Minitest::Test\n    def test_strip_fragment\n      @endpoint = OpenIDServiceEndpoint.new()\n      @endpoint.claimed_id = 'http://recycled.invalid/#123'\n      assert_equal 'http://recycled.invalid/', @endpoint.display_identifier\n    end\n  end\n\n\n  class TestNormalizeURL < Minitest::Test\n    def test_no_host\n      assert_raises(DiscoveryFailure) {\n        OpenID::normalize_url('http:///too-many.invalid/slashes')\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_discovery_manager.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/consumer'\nrequire 'testutil'\n\nmodule OpenID\n  class TestDiscoveredServices < Minitest::Test\n    def setup\n      @starting_url = \"http://starting.url.com/\"\n      @yadis_url = \"http://starting.url.com/xrds\"\n      @services = [\"bogus\", \"not_a_service\"]\n\n      @disco_services = Consumer::DiscoveredServices.new(@starting_url,\n                                                         @yadis_url,\n                                                         @services.dup)\n    end\n\n    def test_next\n      assert_equal(@disco_services.next, @services[0])\n      assert_equal(@disco_services.current, @services[0])\n\n      assert_equal(@disco_services.next, @services[1])\n      assert_equal(@disco_services.current, @services[1])\n\n      assert_nil(@disco_services.next)\n      assert_nil(@disco_services.current)\n    end\n\n    def test_for_url\n      assert(@disco_services.for_url?(@starting_url))\n      assert(@disco_services.for_url?(@yadis_url))\n\n      assert(!@disco_services.for_url?(nil))\n      assert(!@disco_services.for_url?(\"invalid\"))\n    end\n\n    def test_started\n      assert(!@disco_services.started?)\n      @disco_services.next\n      assert(@disco_services.started?)\n      @disco_services.next\n      assert(@disco_services.started?)\n      @disco_services.next\n      assert(!@disco_services.started?)\n    end\n\n    def test_empty\n      assert(Consumer::DiscoveredServices.new(nil, nil, []).empty?)\n\n      assert(!@disco_services.empty?)\n\n      @disco_services.next\n      @disco_services.next\n\n      assert(@disco_services.started?)\n    end\n  end\n\n  # I need to be able to test the protected methods; this lets me do\n  # that.\n  class PassthroughDiscoveryManager < Consumer::DiscoveryManager\n    def method_missing(m, *args)\n      method(m).call(*args)\n    end\n  end\n\n  class TestDiscoveryManager < Minitest::Test\n    def setup\n      session = {}\n      @session = OpenID::Consumer::Session.new(session, OpenID::Consumer::DiscoveredServices)\n      @url = \"http://unittest.com/\"\n      @key_suffix = \"testing\"\n      @yadis_url = \"http://unittest.com/xrds\"\n      @manager = PassthroughDiscoveryManager.new(session, @url, @key_suffix)\n      @key = @manager.session_key\n    end\n\n    def test_construct\n      # Make sure the default session key suffix is not nil.\n      m = Consumer::DiscoveryManager.new(nil, nil)\n      assert(!m.instance_variable_get(\"@session_key_suffix\").nil?)\n\n      m = Consumer::DiscoveryManager.new(nil, nil, \"override\")\n      assert_equal(m.instance_variable_get(\"@session_key_suffix\"), \"override\")\n    end\n\n    def test_get_next_service\n      assert_nil(@session[@key])\n\n      next_service = @manager.get_next_service {\n        [@yadis_url, [\"one\", \"two\", \"three\"]]\n      }\n\n      disco = @session[@key]\n      assert_equal(disco.current, \"one\")\n      assert_equal(next_service, \"one\")\n      assert(disco.for_url?(@url))\n      assert(disco.for_url?(@yadis_url))\n\n      # The first two calls to get_next_service should return the\n      # services in @disco.\n      assert_equal(@manager.get_next_service, \"two\")\n      assert_equal(@manager.get_next_service, \"three\")\n      disco = @session[@key]\n      assert_equal(disco.current, \"three\")\n\n      # The manager is exhausted and should be deleted and a new one\n      # should be created.\n      @manager.get_next_service {\n        [@yadis_url, [\"four\"]]\n      }\n\n      disco2 = @session[@key]\n      assert_equal(disco2.current, \"four\")\n\n      # create_manager may return a nil manager, in which case the\n      # next service should be nil.\n      @manager.extend(OpenID::InstanceDefExtension)\n      @manager.instance_def(:create_manager) do |yadis_url, services|\n        nil\n      end\n\n      result = @manager.get_next_service { |url|\n        [\"unused\", []]\n      }\n\n      assert_nil(result)\n    end\n\n    def test_cleanup\n      # With no preexisting manager, cleanup() returns nil.\n      assert_nil(@manager.cleanup)\n\n      # With a manager, it returns the manager's current service.\n      disco = Consumer::DiscoveredServices.new(@url, @yadis_url, [\"one\", \"two\"])\n\n      @session[@key] = disco\n      assert_nil(@manager.cleanup)\n      assert_nil(@session[@key])\n\n      disco.next\n      @session[@key] = disco\n      assert_equal(@manager.cleanup, \"one\")\n      assert_nil(@session[@key])\n\n      # The force parameter should be passed through to get_manager\n      # and destroy_manager.\n      force_value = \"yo\"\n      testcase = self\n\n      m = Consumer::DiscoveredServices.new(nil, nil, [\"inner\"])\n      m.next\n\n      @manager.extend(OpenID::InstanceDefExtension)\n      @manager.instance_def(:get_manager) do |force|\n        testcase.assert_equal(force, force_value)\n        m\n      end\n\n      @manager.instance_def(:destroy_manager) do |force|\n        testcase.assert_equal(force, force_value)\n      end\n\n      assert_equal(\"inner\", @manager.cleanup(force_value))\n    end\n\n    def test_get_manager\n      # get_manager should always return the loaded manager when\n      # forced.\n      @session[@key] = \"bogus\"\n      assert_equal(\"bogus\", @manager.get_manager(true))\n\n      # When not forced, only managers for @url should be returned.\n      disco = Consumer::DiscoveredServices.new(@url, @yadis_url, [\"one\"])\n      @session[@key] = disco\n      assert_equal(@manager.get_manager, disco)\n\n      # Try to get_manager for a manger that doesn't manage @url:\n      disco2 = Consumer::DiscoveredServices.new(\"http://not.this.url.com/\",\n                                                \"http://other.yadis.url/\", [\"one\"])\n      @session[@key] = disco2\n      assert_nil(@manager.get_manager)\n      assert_equal(@manager.get_manager(true), disco2)\n    end\n\n    def test_create_manager\n      assert(@session[@key].nil?)\n\n      services = [\"created\", \"manager\"]\n      returned_disco = @manager.create_manager(@yadis_url, services)\n\n      stored_disco = @session[@key]\n      assert_equal(stored_disco, returned_disco)\n\n      assert(stored_disco.for_url?(@yadis_url))\n      assert_equal(stored_disco.next, \"created\")\n\n\n      # Calling create_manager with a preexisting manager should\n      # result in StandardError.\n      assert_raises(StandardError) {\n        @manager.create_manager(@yadis_url, services)\n      }\n\n      # create_manager should do nothing (and return nil) if given no\n      # services.\n      @session[@key] = nil\n      result = @manager.create_manager(@yadis_url, [])\n      assert(result.nil?)\n      assert(@session[@key].nil?)\n    end\n\n    class DestroyCalledException < StandardError; end\n\n    def test_destroy_manager\n      # destroy_manager should remove the manager from the session,\n      # forcibly if necessary.\n      valid_disco = Consumer::DiscoveredServices.new(@url, @yadis_url, [\"serv\"])\n      invalid_disco = Consumer::DiscoveredServices.new(\"http://not.mine.com/\",\n                                                       \"http://different.url.com/\",\n                                                       [\"serv\"])\n\n      @session[@key] = valid_disco\n      @manager.destroy_manager\n      assert(@session[@key].nil?)\n\n      @session[@key] = invalid_disco\n      @manager.destroy_manager\n      assert_equal(@session[@key], invalid_disco)\n\n      # Force destruction of manager, no matter which URLs it's for.\n      @manager.destroy_manager(true)\n      assert(@session[@key].nil?)\n    end\n\n    def test_session_key\n      assert(@manager.session_key.end_with?(\n               @manager.instance_variable_get(\"@session_key_suffix\")))\n    end\n\n    def test_store\n      thing = \"opaque\"\n      assert(@session[@key].nil?)\n      @manager.store(thing)\n      assert_equal(@session[@key], thing)\n    end\n\n    def test_load\n      thing = \"opaque\"\n      @session[@key] = thing\n      assert_equal(@manager.load, thing)\n    end\n\n    def test_destroy!\n      thing = \"opaque\"\n      @manager.store(thing)\n      assert_equal(@manager.load, thing)\n      @manager.destroy!\n      assert(@session[@key].nil?)\n      assert(@manager.load.nil?)\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_extension.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/extension'\nrequire 'openid/message'\n\nmodule OpenID\n  class DummyExtension < OpenID::Extension\n    TEST_URI = 'http://an.extension'\n    TEST_ALIAS = 'dummy'\n    def initialize\n      @ns_uri = TEST_URI\n      @ns_alias = TEST_ALIAS\n    end\n\n    def get_extension_args\n      return {}\n    end\n  end\n\n  class ToMessageTest < Minitest::Test\n     def test_OpenID1\n       oid1_msg = Message.new(OPENID1_NS)\n       ext = DummyExtension.new\n       ext.to_message(oid1_msg)\n       namespaces = oid1_msg.namespaces\n       assert(namespaces.implicit?(DummyExtension::TEST_URI))\n       assert_equal(\n                    DummyExtension::TEST_URI,\n                    namespaces.get_namespace_uri(DummyExtension::TEST_ALIAS))\n       assert_equal(DummyExtension::TEST_ALIAS,\n                    namespaces.get_alias(DummyExtension::TEST_URI))\n     end\n\n     def test_OpenID2\n       oid2_msg = Message.new(OPENID2_NS)\n       ext = DummyExtension.new\n       ext.to_message(oid2_msg)\n       namespaces = oid2_msg.namespaces\n       assert(!namespaces.implicit?(DummyExtension::TEST_URI))\n       assert_equal(\n             DummyExtension::TEST_URI,\n             namespaces.get_namespace_uri(DummyExtension::TEST_ALIAS))\n       assert_equal(DummyExtension::TEST_ALIAS,\n                    namespaces.get_alias(DummyExtension::TEST_URI))\n     end\n  end\nend\n"
  },
  {
    "path": "test/test_fetchers.rb",
    "content": "# encoding: utf-8\nrequire 'minitest/autorun'\nrequire 'net/http'\nrequire 'webrick'\nrequire 'testutil'\nrequire 'util'\nrequire 'openid/fetchers'\nrequire 'stringio'\n\nbegin\n  require 'net/https'\nrescue LoadError\n  # We need these names for testing.\n\n  module OpenSSL\n    module SSL\n      class SSLError < StandardError; end\n    end\n  end\nend\n\nmodule HttpResultAssertions\n  def assert_http_result_is(expected, result)\n    assert_equal expected.code, result.code\n    assert_equal expected.body, result.body\n    assert_equal expected.final_url, result.final_url\n  end\nend\n\nclass BogusFetcher\n  RESPONSE = \"bogus\"\n\n  def fetch(url, body=nil, headers=nil, redirect_limit=5)\n    return BogusFetcher::RESPONSE\n  end\nend\n\nclass FetcherTestCase < Minitest::Test\n  include HttpResultAssertions\n  include OpenID::TestUtil\n\n  @@test_header_name = 'X-test-header'\n  @@test_header_value = 'marmoset'\n\n  class ExpectedResponse < Net::HTTPResponse\n    attr_reader :final_url\n\n    def initialize(code, final_url, body=\"the expected body\",\n                   httpv=\"1.1\", msg=nil)\n      super(httpv, code, msg)\n      @code = code\n      @body = body\n      @final_url = final_url\n    end\n\n    def body\n      @body\n    end\n  end\n\n  @@cases =\n    [\n     # path, status code, expected url (nil = default to path)\n     ['/success', 200, nil],\n     ['/notfound', 404, nil],\n     ['/badreq', 400, nil],\n     ['/forbidden', 403, nil],\n     ['/error', 500, nil],\n     ['/server_error', 503, nil],\n     ['/301redirect', 200, '/success'],\n     ['/302redirect', 200, '/success'],\n     ['/303redirect', 200, '/success'],\n     ['/307redirect', 200, '/success'],\n    ]\n\n  def _redirect_with_code(code)\n    lambda { |req, resp|\n      resp.status = code\n      resp['Location'] = _uri_build('/success')\n    }\n  end\n\n  def _respond_with_code(code)\n    lambda { |req, resp|\n      resp.status = code\n      resp.body = \"the expected body\"\n    }\n  end\n\n  def _require_header\n    lambda { |req, resp|\n      assert_equal @@test_header_value, req[@@test_header_name]\n      assert_match 'ruby-openid', req['User-agent']\n    }\n  end\n\n  def _require_post\n    lambda { |req, resp|\n      assert_equal 'POST', req.request_method\n      assert_equal \"postbody\\n\", req.body\n    }\n  end\n\n  def _redirect_loop\n    lambda { |req, resp|\n      @_redirect_counter += 1\n      resp.status = 302\n      resp['Location'] = _uri_build('/redirect_loop')\n      resp.body = \"Fetched #{@_redirect_counter} times.\"\n      assert @_redirect_counter < 10, \"Fetched too many times.\"\n    }\n  end\n\n  UTF8_PAGE_CONTENT = <<-EOHTML\n<html>\n  <head><title>UTF-8</title></head>\n  <body>こんにちは</body>\n</html>\nEOHTML\n  def _utf8_page\n    lambda { |req, resp|\n      resp['Content-Type'] = \"text/html; charset=utf-8\"\n      body = UTF8_PAGE_CONTENT.dup\n      resp.body = body\n    }\n  end\n\n  def _unencoded_page\n    lambda { |req, resp|\n      resp['Content-Type'] = \"text/html\"\n      body = \"unencoded-body\"\n      resp.body = body\n    }\n  end\n\n  def _badly_encoded_page\n    lambda { |req, resp|\n      resp['Content-Type'] = \"text/html; charset=wtf\"\n      body = \"badly-encoded-body\"\n      resp.body = body\n    }\n  end\n\n  def setup\n    if defined?(Encoding.default_external)\n      @encoding_was = Encoding.default_external\n    end\n    @fetcher = OpenID::StandardFetcher.new\n    @logfile = StringIO.new\n    @weblog = WEBrick::Log.new(@logfile)\n    @server = WEBrick::HTTPServer.new(:Port => 0,\n                                      :Logger => @weblog,\n                                      :AccessLog => [])\n    @server_thread = Thread.new {\n      @server.mount_proc('/success', _respond_with_code(200))\n      @server.mount_proc('/301redirect', _redirect_with_code(301))\n      @server.mount_proc('/302redirect', _redirect_with_code(302))\n      @server.mount_proc('/303redirect', _redirect_with_code(303))\n      @server.mount_proc('/307redirect', _redirect_with_code(307))\n      @server.mount_proc('/badreq', _respond_with_code(400))\n      @server.mount_proc('/forbidden', _respond_with_code(403))\n      @server.mount_proc('/notfound', _respond_with_code(404))\n      @server.mount_proc('/error', _respond_with_code(500))\n      @server.mount_proc('/server_error', _respond_with_code(503))\n      @server.mount_proc('/require_header', _require_header)\n      @server.mount_proc('/redirect_to_reqheader') { |req, resp|\n        resp.status = 302\n        resp['Location'] = _uri_build('/require_header')\n      }\n      @server.mount_proc('/post', _require_post)\n      @server.mount_proc('/redirect_loop', _redirect_loop)\n      @server.mount_proc('/utf8_page', _utf8_page)\n      @server.mount_proc('/unencoded_page', _unencoded_page)\n      @server.mount_proc('/badly_encoded_page', _badly_encoded_page)\n      @server.start\n    }\n    @uri = _uri_build\n    sleep 0.2\n  end\n\n  def _uri_build(path='/')\n    u = URI::HTTP.build({\n                          :host => \"localhost\",\n                          :port => @server.config[:Port],\n                          :path => path,\n                        })\n    return u.to_s\n  end\n\n  def teardown\n    if defined?(Encoding.default_external)\n      Encoding.default_external = @encoding_was\n    end\n    @server.shutdown\n    # Sleep a little because sometimes this blocks forever.\n    @server_thread.join\n  end\n\n=begin\n# XXX This test no longer works since we're not dealing with URI\n# objects internally.\n  def test_final_url_tainted\n    uri = _uri_build('/301redirect')\n    result = @fetcher.fetch(uri)\n\n    final_url = URI::parse(result.final_url)\n\n    assert final_url.host.tainted?\n    assert final_url.path.tainted?\n  end\n=end\n\n  def test_headers\n    headers = {\n      @@test_header_name => @@test_header_value\n    }\n    uri = _uri_build('/require_header')\n    result = @fetcher.fetch(uri, nil, headers)\n    # The real test runs under the WEBrick handler _require_header,\n    # this just checks the return code from that.\n    assert_equal '200', result.code, @logfile.string\n  end\n\n  def test_headers_after_redirect\n    headers = {\n      @@test_header_name => @@test_header_value\n    }\n    uri = _uri_build('/redirect_to_reqheader')\n    result = @fetcher.fetch(uri, nil, headers)\n    # The real test runs under the WEBrick handler _require_header,\n    # this just checks the return code from that.\n    assert_equal '200', result.code, @logfile.string\n  end\n\n  def test_post\n    uri = _uri_build('/post')\n    result = @fetcher.fetch(uri, \"postbody\\n\")\n    # The real test runs under the WEBrick handler _require_header,\n    # this just checks the return code from that.\n    assert_equal '200', result.code, @logfile.string\n  end\n\n  def test_redirect_limit\n    @_redirect_counter = 0\n    uri = _uri_build('/redirect_loop')\n    assert_raises(OpenID::HTTPRedirectLimitReached) {\n      @fetcher.fetch(uri, body=nil, headers=nil, redirect_limit=0)\n    }\n  end\n\n  def test_utf8_page\n    uri = _uri_build('/utf8_page')\n    response = @fetcher.fetch(uri)\n    assert_equal(UTF8_PAGE_CONTENT, response.body)\n    if response.body.respond_to?(:encoding)\n      assert_equal(Encoding::UTF_8, response.body.encoding)\n    end\n  end\n\n  def test_unencoded_page\n    uri = _uri_build('/unencoded_page')\n    response = @fetcher.fetch(uri)\n    assert_equal(\"unencoded-body\", response.body)\n    # The actual encoding seems to depend on the server\n    # setting in case it is not defined explicitely\n    # if defined?(Encoding.default_external)\n    #   assert_equal(Encoding::UTF_8, response.body.encoding)\n    # end\n  end\n\n  def test_badly_encoded_page\n    if defined?(Encoding.default_external)\n      Encoding.default_external = Encoding::SHIFT_JIS\n    end\n    uri = _uri_build('/badly_encoded_page')\n    response = @fetcher.fetch(uri)\n    assert_equal(\"badly-encoded-body\", response.body)\n    if defined?(Encoding.default_external)\n      assert_equal(Encoding::SHIFT_JIS, response.body.encoding)\n    end\n  end\n\n  def test_cases\n    for path, expected_code, expected_url in @@cases\n      uri = _uri_build(path)\n      if expected_url.nil?\n        expected_url = uri\n      else\n        expected_url = _uri_build(expected_url)\n      end\n\n      expected = ExpectedResponse.new(expected_code.to_s, expected_url)\n      result = @fetcher.fetch(uri)\n\n      begin\n        assert_http_result_is expected, result\n      rescue Minitest::Assertion => err\n        if result.code == '500' && expected_code != 500\n          # Looks like our WEBrick harness broke.\n          msg = <<EOF\nStatus #{result.code} from case #{path}.  Logs:\n#{@logfile.string}\nEOF\n          raise msg\n        end\n\n        # Wrap failure messages so we can tell which case failed.\n        new_msg = \"#{path}: #{err.message.to_s}\"\n        new_err = Minitest::Assertion.new(new_msg)\n        new_err.set_backtrace(err.backtrace)\n        raise new_err\n      end\n    end\n  end\n\n  def test_https_no_openssl\n    # Override supports_ssl? to always claim that connections don't\n    # support SSL.  Test the behavior of fetch() for HTTPS URLs in\n    # that case.\n    f = OpenID::StandardFetcher.new\n    f.extend(OpenID::InstanceDefExtension)\n\n    f.instance_def(:supports_ssl?) do |conn|\n      false\n    end\n\n    begin\n      f.fetch(\"https://someurl.com/\")\n      flunk(\"Expected RuntimeError\")\n    rescue RuntimeError => why\n      assert_equal(why.to_s, \"SSL support not found; cannot fetch https://someurl.com/\")\n    end\n  end\n\n  class FakeConnection < Net::HTTP\n    attr_reader :use_ssl, :ca_file\n\n    def initialize *args\n      super\n      @ca_file = nil\n    end\n\n    def use_ssl=(v)\n      @use_ssl = v\n    end\n\n    def ca_file=(ca_file)\n      @ca_file = ca_file\n    end\n  end\n\n  def test_ssl_with_ca_file\n    f = OpenID::StandardFetcher.new\n    ca_file = \"BOGUS\"\n    f.ca_file = ca_file\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_http) do |uri|\n      FakeConnection.new(uri.host, uri.port)\n    end\n\n    testcase = self\n\n    f.instance_def(:set_verified) do |conn, verified|\n      testcase.assert(verified)\n    end\n\n    conn = f.make_connection(URI::parse(\"https://someurl.com\"))\n    assert_equal(conn.ca_file, ca_file)\n  end\n\n  def test_ssl_without_ca_file\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_http) do |uri|\n      FakeConnection.new(uri.host, uri.port)\n    end\n\n    testcase = self\n\n    f.instance_def(:set_verified) do |conn, verified|\n      testcase.assert(!verified)\n    end\n\n    conn = nil\n    assert_log_matches(/making https request to https:\\/\\/someurl.com without verifying/) {\n      conn = f.make_connection(URI::parse(\"https://someurl.com\"))\n    }\n\n    assert(conn.ca_file.nil?)\n  end\n\n  def test_make_http_nil\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_http) do |uri|\n      nil\n    end\n\n    assert_raises(RuntimeError) {\n      f.make_connection(URI::parse(\"http://example.com/\"))\n    }\n  end\n\n  def test_make_http_invalid\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_http) do |uri|\n      \"not a Net::HTTP object\"\n    end\n\n    assert_raises(RuntimeError) {\n      f.make_connection(URI::parse(\"http://example.com/\"))\n    }\n  end\n\n  class BrokenSSLConnection\n    def start(&block)\n      raise OpenSSL::SSL::SSLError\n    end\n  end\n\n  def test_sslfetchingerror\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_connection) do |uri|\n      BrokenSSLConnection.new\n    end\n\n    assert_raises(OpenID::SSLFetchingError) {\n      f.fetch(\"https://bogus.com/\")\n    }\n  end\n\n  class TimeoutConnection\n    def start(&block)\n      raise Timeout::Error\n    end\n  end\n\n  def test_fetchingerror\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_connection) do |uri|\n      TimeoutConnection.new\n    end\n\n    assert_raises(OpenID::FetchingError) {\n      f.fetch(\"https://bogus.com/\")\n    }\n  end\n\n  class TestingException < OpenID::FetchingError; end\n\n  class NoSSLSupportConnection\n    def supports_ssl?\n      false\n    end\n\n    def start\n      yield\n    end\n\n    def request_get(*args)\n      raise TestingException\n    end\n\n    def post_connection_check(hostname)\n      raise RuntimeError\n    end\n\n    def use_ssl?\n      true\n    end\n  end\n\n  class NoUseSSLConnection < NoSSLSupportConnection\n    def use_ssl?\n      false\n    end\n  end\n\n  def test_post_connection_check_no_support_ssl\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_connection) do |uri|\n      NoSSLSupportConnection.new\n    end\n\n    # post_connection_check should not be called.\n    assert_raises(TestingException) {\n      f.fetch(\"https://bogus.com/\")\n    }\n  end\n\n  def test_post_connection_check_no_use_ssl\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_connection) do |uri|\n      NoUseSSLConnection.new\n    end\n\n    # post_connection_check should not be called.\n    assert_raises(TestingException) {\n      f.fetch(\"https://bogus.com/\")\n    }\n  end\n\n  class PostConnectionCheckException < OpenID::FetchingError; end\n\n  class UseSSLConnection < NoSSLSupportConnection\n    def use_ssl?\n      true\n    end\n\n    def post_connection_check(hostname)\n      raise PostConnectionCheckException\n    end\n  end\n\n  def test_post_connection_check\n    f = OpenID::StandardFetcher.new\n\n    f.extend(OpenID::InstanceDefExtension)\n    f.instance_def(:make_connection) do |uri|\n      UseSSLConnection.new\n    end\n\n    f.instance_def(:supports_ssl?) do |conn|\n      true\n    end\n\n    # post_connection_check should be called.\n    assert_raises(PostConnectionCheckException) {\n      f.fetch(\"https://bogus.com/\")\n    }\n  end\nend\n\nclass DefaultFetcherTest < Minitest::Test\n  def setup\n    OpenID.fetcher = nil\n  end\n\n  def test_default_fetcher\n    assert(OpenID.fetcher.is_a?(OpenID::StandardFetcher))\n\n    # A custom fetcher can be set\n    OpenID.fetcher = BogusFetcher.new\n\n    # A test fetch should call the new fetcher\n    assert(OpenID.fetch('not-a-url') == BogusFetcher::RESPONSE)\n\n    # Set the fetcher to nil again\n    OpenID.fetcher = nil\n    assert(OpenID.fetcher.is_a?(OpenID::StandardFetcher))\n  end\nend\n\nclass ProxyTest < Minitest::Test\n  def test_proxy_unreachable\n    begin\n      f = OpenID::StandardFetcher.new('127.0.0.1', 1)\n      # If this tries to connect to the proxy (on port 1), I expect\n      # a 'connection refused' error.  If it tries to contact the below\n      # URI first, it will get some other sort of error.\n      f.fetch(\"http://unittest.invalid\")\n    rescue OpenID::FetchingError => why\n      # XXX: Is this a translatable string that is going to break?\n      if why.message =~ /Connection refused/\n        return\n      end\n      raise why\n    end\n    flunk \"expected Connection Refused, but it passed.\"\n  end\n\n  def test_proxy_env\n    ENV['http_proxy'] = 'http://127.0.0.1:3128/'\n    OpenID.fetcher_use_env_http_proxy\n\n    # make_http just to give us something with readable attributes to inspect.\n    conn = OpenID.fetcher.make_http(URI.parse('http://127.0.0.2'))\n    assert_equal('127.0.0.1', conn.proxy_address)\n    assert_equal(3128, conn.proxy_port)\n  end\n  # These aren't fully automated tests, but if you start a proxy\n  # on port 8888 (tinyproxy's default) and check its logs...\n#   def test_proxy\n#     f = OpenID::StandardFetcher.new('127.0.0.1', 8888)\n#     result = f.fetch(\"http://www.example.com/\")\n#     assert_match(/RFC.*2606/, result.body)\n#   end\n\n#   def test_proxy_https\n#     f = OpenID::StandardFetcher.new('127.0.0.1', 8888)\n#     result = f.fetch(\"https://www.myopenid.com/\")\n#     assert_match(/myOpenID/, result.body)\n#   end\nend\n"
  },
  {
    "path": "test/test_filters.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"rexml/document\"\nrequire \"openid/yadis/xrds\"\nrequire \"openid/yadis/filters\"\n\nmodule OpenID\n  class BasicServiceEndpointTest < Minitest::Test\n    def test_match_types\n      # Make sure the match_types operation returns the expected\n      # results with various inputs.\n      types = [\"urn:bogus\", \"urn:testing\"]\n      yadis_url = \"http://yadis/\"\n\n      no_types_endpoint = Yadis::BasicServiceEndpoint.new(yadis_url, [], nil, nil)\n\n      some_types_endpoint = Yadis::BasicServiceEndpoint.new(yadis_url, types, nil, nil)\n\n      assert(no_types_endpoint.match_types([]) == [])\n      assert(no_types_endpoint.match_types([\"urn:absent\"]) == [])\n\n      assert(some_types_endpoint.match_types([]) == [])\n      assert(some_types_endpoint.match_types([\"urn:absent\"]) == [])\n      assert(some_types_endpoint.match_types(types) == types)\n      assert(some_types_endpoint.match_types([types[1], types[0]]) == types)\n      assert(some_types_endpoint.match_types([types[0]]) == [types[0]])\n      assert(some_types_endpoint.match_types(types + [\"urn:absent\"]) == types)\n    end\n\n    def test_from_basic_service_endpoint\n      # Check BasicServiceEndpoint.from_basic_service_endpoint\n      endpoint = \"unused\"\n      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n\n      assert(Yadis::BasicServiceEndpoint.from_basic_service_endpoint(endpoint) ==\n             endpoint)\n      assert(e.from_basic_service_endpoint(endpoint) ==\n             endpoint)\n    end\n  end\n\n  class TransformFilterMakerTest < Minitest::Test\n    def make_service_element(types, uris)\n      service = REXML::Element.new('Service')\n      types.each { |type_text|\n        service.add_element('Type').text = type_text\n      }\n      uris.each { |uri_text|\n        service.add_element('URI').text = uri_text\n      }\n      return service\n    end\n    def test_get_service_endpoints\n      yadis_url = \"http://yad.is/\"\n      uri = \"http://uri/\"\n      type_uris = [\"urn:type1\", \"urn:type2\"]\n      element = make_service_element(type_uris, [uri])\n\n      filters = [Proc.new { |endpoint|\n                   if endpoint.service_element == element\n                     endpoint\n                   else\n                     nil\n                   end\n                 }\n                ]\n\n      tf = Yadis::TransformFilterMaker.new(filters)\n      result = tf.get_service_endpoints(yadis_url, element)\n\n      assert_equal(result[0].yadis_url, yadis_url, result)\n      assert_equal(result[0].uri, uri, result)\n    end\n\n    def test_empty_transform_filter\n      # A transform filter with no filter procs should return nil.\n      endpoint = \"unused\"\n      t = Yadis::TransformFilterMaker.new([])\n      assert(t.apply_filters(endpoint).nil?)\n    end\n\n    def test_nil_filter\n      # A transform filter with a single nil filter should return nil.\n      nil_filter = Proc.new { |endpoint| nil }\n      t = Yadis::TransformFilterMaker.new([nil_filter])\n      endpoint = \"unused\"\n      assert(t.apply_filters(endpoint).nil?)\n    end\n\n    def test_identity_filter\n      # A transform filter with an identity filter should return the\n      # input.\n      identity_filter = Proc.new { |endpoint| endpoint }\n      t = Yadis::TransformFilterMaker.new([identity_filter])\n      endpoint = \"unused\"\n      assert(t.apply_filters(endpoint) == endpoint)\n    end\n\n    def test_return_different_endpoint\n      # Make sure the result of the filter is returned, rather than\n      # the input.\n      returned_endpoint = \"returned endpoint\"\n      filter = Proc.new { |endpoint| returned_endpoint }\n      t = Yadis::TransformFilterMaker.new([filter])\n      endpoint = \"unused\"\n      assert(t.apply_filters(endpoint) == returned_endpoint)\n    end\n\n    def test_multiple_filters\n      # Check filter fallback behavior on different inputs.\n      odd, odd_result = 45, \"odd\"\n      even, even_result = 46, \"even\"\n\n      filter_odd = Proc.new { |endpoint|\n        if endpoint % 2 == 1\n          odd_result\n        else\n          nil\n        end\n      }\n\n      filter_even = Proc.new { |endpoint|\n        if endpoint % 2 == 0\n          even_result\n        else\n          nil\n        end\n      }\n\n      t = Yadis::TransformFilterMaker.new([filter_odd, filter_even])\n      assert(t.apply_filters(odd) == odd_result)\n      assert(t.apply_filters(even) == even_result)\n    end\n  end\n\n  class BogusServiceEndpointExtractor\n    def initialize(data)\n      @data = data\n    end\n\n    def get_service_endpoints(yadis_url, service_element)\n      return @data\n    end\n  end\n\n  class CompoundFilterTest < Minitest::Test\n    def test_get_service_endpoints\n      first = [\"bogus\", \"test\"]\n      second = [\"third\"]\n      all = first + second\n\n      subfilters = [\n                    BogusServiceEndpointExtractor.new(first),\n                    BogusServiceEndpointExtractor.new(second),\n                   ]\n\n      cf = Yadis::CompoundFilter.new(subfilters)\n      assert cf.get_service_endpoints(\"unused\", \"unused\") == all\n    end\n  end\n\n  class MakeFilterTest < Minitest::Test\n    def test_parts_nil\n      result = Yadis.make_filter(nil)\n      assert result.is_a?(Yadis::TransformFilterMaker)\n    end\n\n    def test_parts_array\n      e1 = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n      e2 = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n\n      result = Yadis.make_filter([e1, e2])\n      assert result.is_a?(Yadis::TransformFilterMaker)\n      assert result.filter_procs[0] == e1.method('from_basic_service_endpoint')\n      assert result.filter_procs[1] == e2.method('from_basic_service_endpoint')\n    end\n\n    def test_parts_single\n      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n      result = Yadis.make_filter(e)\n      assert result.is_a?(Yadis::TransformFilterMaker)\n    end\n  end\n\n  class MakeCompoundFilterTest < Minitest::Test\n    def test_no_filters\n      result = Yadis.mk_compound_filter([])\n      assert result.subfilters == []\n    end\n\n    def test_single_transform_filter\n      f = Yadis::TransformFilterMaker.new([])\n      assert_equal f, Yadis.mk_compound_filter([f])\n    end\n\n    def test_single_endpoint\n      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n      result = Yadis.mk_compound_filter([e])\n      assert result.is_a?(Yadis::TransformFilterMaker)\n\n      # Expect the transform filter to call\n      # from_basic_service_endpoint on the endpoint\n      filter = result.filter_procs[0]\n      assert_equal filter, e.method('from_basic_service_endpoint')\n    end\n\n    def test_single_proc\n      # Create a proc that just returns nil for any endpoint\n      p = Proc.new { |endpoint| nil }\n      result = Yadis.mk_compound_filter([p])\n      assert result.is_a?(Yadis::TransformFilterMaker)\n\n      # Expect the transform filter to call\n      # from_basic_service_endpoint on the endpoint\n      assert_equal result.filter_procs[0], p\n    end\n\n    def test_multiple_filters_same_type\n      f1 = Yadis::TransformFilterMaker.new([])\n      f2 = Yadis::TransformFilterMaker.new([])\n\n      # Expect mk_compound_filter to actually make a CompoundFilter\n      # from f1 and f2.\n      result = Yadis.mk_compound_filter([f1, f2])\n\n      assert result.is_a?(Yadis::CompoundFilter)\n      assert result.subfilters == [f1, f2]\n    end\n\n    def test_multiple_filters_different_type\n      f1 = Yadis::TransformFilterMaker.new([])\n      f2 = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n      f3 = Proc.new { |endpoint| nil }\n\n      e = Yadis::BasicServiceEndpoint.new(nil, [], nil, nil)\n      f4 = [e]\n\n      # Expect mk_compound_filter to actually make a CompoundFilter\n      # from f1 and f2.\n      result = Yadis.mk_compound_filter([f1, f2, f3, f4])\n\n      assert result.is_a?(Yadis::CompoundFilter)\n\n      assert result.subfilters[0] == f1\n      assert result.subfilters[1].filter_procs[0] == e.method('from_basic_service_endpoint')\n      assert result.subfilters[2].filter_procs[0] == f2.method('from_basic_service_endpoint')\n      assert result.subfilters[2].filter_procs[1] == f3\n    end\n\n    def test_filter_type_error\n      # Pass various non-filter objects and make sure the filter\n      # machinery explodes.\n      [nil, [\"bogus\"], [1], [nil, \"bogus\"]].each { |thing|\n        assert_raises(TypeError) {\n          Yadis.mk_compound_filter(thing)\n        }\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_idres.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"util\"\nrequire \"openid/consumer/idres\"\nrequire \"openid/protocolerror\"\nrequire \"openid/store/memory\"\nrequire \"openid/store/nonce\"\n\nmodule OpenID\n  class Consumer\n    class IdResHandler\n\n      # Subclass of IdResHandler that doesn't do verification upon\n      # construction. All of the tests call this, except for the ones\n      # explicitly for id_res.\n      class IdResHandler < OpenID::Consumer::IdResHandler\n        def id_res\n        end\n      end\n\n      class CheckForFieldsTest < Minitest::Test\n        include ProtocolErrorMixin\n\n        BASE_FIELDS = ['return_to', 'assoc_handle', 'sig', 'signed']\n        OPENID2_FIELDS = BASE_FIELDS + ['op_endpoint']\n        OPENID1_FIELDS = BASE_FIELDS + ['identity']\n\n        OPENID1_SIGNED = ['return_to', 'identity']\n        OPENID2_SIGNED =\n          OPENID1_SIGNED + ['response_nonce', 'claimed_id', 'assoc_handle',\n                            'op_endpoint']\n\n        def mkMsg(ns, fields, signed_fields)\n          msg = Message.new(ns)\n          fields.each do |field|\n            msg.set_arg(OPENID_NS, field, \"don't care\")\n          end\n          if fields.member?('signed')\n            msg.set_arg(OPENID_NS, 'signed', signed_fields.join(','))\n          end\n          msg\n        end\n\n        1.times do # so as not to bleed into the outer namespace\n          n = 0\n          [[],\n           ['foo'],\n           ['bar', 'baz'],\n          ].each do |signed_fields|\n            test = lambda do\n              msg = mkMsg(OPENID2_NS, OPENID2_FIELDS, signed_fields)\n              idres = IdResHandler.new(msg, nil)\n              assert_equal(signed_fields, idres.send(:signed_list))\n              # Do it again to make sure logic for caching is correct\n              assert_equal(signed_fields, idres.send(:signed_list))\n            end\n            define_method(\"test_signed_list_#{n += 1}\", test)\n          end\n        end\n\n        # test all missing fields for OpenID 1 and 2\n        1.times do\n          [[\"openid1\", OPENID1_NS, OPENID1_FIELDS],\n           [\"openid1\", OPENID11_NS, OPENID1_FIELDS],\n           [\"openid2\", OPENID2_NS, OPENID2_FIELDS],\n          ].each_with_index do |(ver, ns, all_fields), i|\n            all_fields.each do |field|\n              test = lambda do\n                fields = all_fields.dup\n                fields.delete(field)\n                msg = mkMsg(ns, fields, [])\n                idres = IdResHandler.new(msg, nil)\n                assert_protocol_error(\"Missing required field #{field}\") {\n                  idres.send(:check_for_fields)\n                }\n              end\n              define_method(\"test_#{i}_#{ver}_check_missing_#{field}\", test)\n            end\n          end\n        end\n\n        # Test all missing signed for OpenID 1 and 2\n        1.times do\n          [[\"openid1\", OPENID1_NS, OPENID1_FIELDS, OPENID1_SIGNED],\n           [\"openid1\", OPENID11_NS, OPENID1_FIELDS, OPENID1_SIGNED],\n           [\"openid2\", OPENID2_NS, OPENID2_FIELDS, OPENID2_SIGNED],\n          ].each_with_index do |(ver, ns, all_fields, signed_fields), i|\n            signed_fields.each do |signed_field|\n              test = lambda do\n                fields = signed_fields.dup\n                fields.delete(signed_field)\n                msg = mkMsg(ns, all_fields, fields)\n                # Make sure the signed field is actually in the request\n                msg.set_arg(OPENID_NS, signed_field, \"don't care\")\n                idres = IdResHandler.new(msg, nil)\n                assert_protocol_error(\"#{signed_field.inspect} not signed\") {\n                  idres.send(:check_for_fields)\n                }\n              end\n              define_method(\"test_#{i}_#{ver}_check_missing_signed_#{signed_field}\", test)\n            end\n          end\n        end\n\n        def test_112\n          args = {'openid.assoc_handle' => 'fa1f5ff0-cde4-11dc-a183-3714bfd55ca8',\n                  'openid.claimed_id' => 'http://binkley.lan/user/test01',\n                  'openid.identity' => 'http://test01.binkley.lan/',\n                  'openid.mode' => 'id_res',\n                  'openid.ns' => 'http://specs.openid.net/auth/2.0',\n                  'openid.ns.pape' => 'http://specs.openid.net/extensions/pape/1.0',\n                  'openid.op_endpoint' => 'http://binkley.lan/server',\n                  'openid.pape.auth_policies' => 'none',\n                  'openid.pape.auth_time' => '2008-01-28T20:42:36Z',\n                  'openid.pape.nist_auth_level' => '0',\n                  'openid.response_nonce' => '2008-01-28T21:07:04Z99Q=',\n                  'openid.return_to' => 'http://binkley.lan:8001/process?janrain_nonce=2008-01-28T21%3A07%3A02Z0tMIKx',\n                  'openid.sig' => 'YJlWH4U6SroB1HoPkmEKx9AyGGg=',\n                  'openid.signed' => 'assoc_handle,identity,response_nonce,return_to,claimed_id,op_endpoint,pape.auth_time,ns.pape,pape.nist_auth_level,pape.auth_policies'\n\t         }\n          assert_equal(args['openid.ns'], OPENID2_NS)\n          incoming = Message.from_post_args(args)\n          assert(incoming.is_openid2)\n          idres = IdResHandler.new(incoming, nil)\n          car = idres.send(:create_check_auth_request)\n          expected_args = args.dup\n          expected_args['openid.mode'] = 'check_authentication'\n          expected = Message.from_post_args(expected_args)\n          assert(expected.is_openid2)\n          assert_equal(expected, car)\n          assert_equal(expected_args, car.to_post_args)\n        end\n\n        def test_no_signed_list\n          msg = Message.new(OPENID2_NS)\n          idres = IdResHandler.new(msg, nil)\n          assert_protocol_error(\"Response missing signed\") {\n            idres.send(:signed_list)\n          }\n        end\n\n        def test_success_openid1\n          msg = mkMsg(OPENID1_NS, OPENID1_FIELDS, OPENID1_SIGNED)\n          idres = IdResHandler.new(msg, nil)\n          idres.send(:check_for_fields)\n        end\n\n        def test_success_openid1_1\n          msg = mkMsg(OPENID11_NS, OPENID1_FIELDS, OPENID1_SIGNED)\n          idres = IdResHandler.new(msg, nil)\n          idres.send(:check_for_fields)\n        end\n      end\n\n      class ReturnToArgsTest < Minitest::Test\n        include OpenID::ProtocolErrorMixin\n\n        def check_return_to_args(query)\n          idres = IdResHandler.new(Message.from_post_args(query), nil)\n          class << idres\n            def verify_return_to_base(unused)\n            end\n          end\n          idres.send(:verify_return_to)\n        end\n\n        def assert_bad_args(msg, query)\n          assert_protocol_error(msg) {\n            check_return_to_args(query)\n          }\n        end\n\n        def test_return_to_args_okay\n          check_return_to_args({\n            'openid.mode' => 'id_res',\n            'openid.return_to' => 'http://example.com/?foo=bar',\n            'foo' => 'bar',\n            })\n        end\n\n        def test_unexpected_arg_okay\n          assert_bad_args(\"Unexpected parameter\", {\n              'openid.mode' => 'id_res',\n              'openid.return_to' => 'http://example.com/',\n              'foo' => 'bar',\n              })\n        end\n\n        def test_return_to_mismatch\n          assert_bad_args('Message missing ret', {\n            'openid.mode' => 'id_res',\n            'openid.return_to' => 'http://example.com/?foo=bar',\n            })\n\n          assert_bad_args(\"Parameter 'foo' val\", {\n            'openid.mode' => 'id_res',\n            'openid.return_to' => 'http://example.com/?foo=bar',\n            'foo' => 'foos',\n            })\n        end\n      end\n\n      class ReturnToVerifyTest < Minitest::Test\n        def test_bad_return_to\n          return_to = \"http://some.url/path?foo=bar\"\n\n          m = Message.new(OPENID1_NS)\n          m.set_arg(OPENID_NS, 'mode', 'cancel')\n          m.set_arg(BARE_NS, 'foo', 'bar')\n\n          # Scheme, authority, and path differences are checked by\n          # IdResHandler.verify_return_to_base.  Query args checked by\n          # IdResHandler.verify_return_to_args.\n          [\n            # Scheme only\n            \"https://some.url/path?foo=bar\",\n            # Authority only\n            \"http://some.url.invalid/path?foo=bar\",\n            # Path only\n            \"http://some.url/path_extra?foo=bar\",\n            # Query args differ\n            \"http://some.url/path?foo=bar2\",\n            \"http://some.url/path?foo2=bar\",\n            ].each do |bad|\n              m.set_arg(OPENID_NS, 'return_to', bad)\n              idres = IdResHandler.new(m, return_to)\n              assert_raises(ProtocolError) {\n                idres.send(:verify_return_to)\n              }\n          end\n        end\n\n        def test_good_return_to\n          base = 'http://example.janrain.com/path'\n          [ [base, {}],\n            [base + \"?another=arg\", {'another' => 'arg'}],\n            [base + \"?another=arg#frag\", {'another' => 'arg'}],\n            ['HTTP'+base[4..-1], {}],\n            [base.sub('com', 'COM'), {}],\n            ['http://example.janrain.com:80/path', {}],\n            ['http://example.janrain.com/p%61th', {}],\n            ['http://example.janrain.com/./path',{}],\n          ].each do |return_to, args|\n            args['openid.return_to'] = return_to\n            msg = Message.from_post_args(args)\n            idres = IdResHandler.new(msg, base)\n            idres.send(:verify_return_to)\n          end\n        end\n      end\n\n      class DummyEndpoint\n        attr_accessor :server_url\n        def initialize(server_url)\n          @server_url = server_url\n        end\n      end\n\n      class CheckSigTest < Minitest::Test\n        include ProtocolErrorMixin\n        include TestUtil\n\n        def setup\n          @assoc = GoodAssoc.new('{not_dumb}')\n          @store = Store::Memory.new\n          @server_url = 'http://server.url/'\n          @endpoint = DummyEndpoint.new(@server_url)\n          @store.store_association(@server_url, @assoc)\n\n          @message = Message.from_post_args({\n              'openid.mode' => 'id_res',\n              'openid.identity' => '=example',\n              'openid.sig' => GOODSIG,\n              'openid.assoc_handle' => @assoc.handle,\n              'openid.signed' => 'mode,identity,assoc_handle,signed',\n              'frobboz' => 'banzit',\n              })\n        end\n\n        def call_idres_method(method_name)\n          idres = IdResHandler.new(@message, nil, @store, @endpoint)\n          idres.extend(InstanceDefExtension)\n          yield idres\n          idres.send(method_name)\n        end\n\n        def call_check_sig(&proc)\n          call_idres_method(:check_signature, &proc)\n        end\n\n        def no_check_auth(idres)\n          idres.instance_def(:check_auth) { fail \"Called check_auth\" }\n        end\n\n        def test_sign_good\n          call_check_sig(&method(:no_check_auth))\n        end\n\n        def test_bad_sig\n          @message.set_arg(OPENID_NS, 'sig', 'bad sig!')\n          assert_protocol_error('Bad signature') {\n            call_check_sig(&method(:no_check_auth))\n          }\n        end\n\n        def test_check_auth_ok\n          @message.set_arg(OPENID_NS, 'assoc_handle', 'dumb-handle')\n          check_auth_called = false\n          call_check_sig do |idres|\n            idres.instance_def(:check_auth) do\n              check_auth_called = true\n            end\n          end\n          assert(check_auth_called)\n        end\n\n        def test_check_auth_ok_no_store\n          @store = nil\n          check_auth_called = false\n          call_check_sig do |idres|\n            idres.instance_def(:check_auth) do\n              check_auth_called = true\n            end\n          end\n          assert(check_auth_called)\n        end\n\n        def test_expired_assoc\n          @assoc.expires_in = -1\n          @store.store_association(@server_url, @assoc)\n          assert_protocol_error('Association with') {\n            call_check_sig(&method(:no_check_auth))\n          }\n        end\n\n        def call_check_auth(&proc)\n          assert_log_matches(\"Using 'check_authentication'\") {\n            call_idres_method(:check_auth, &proc)\n          }\n        end\n\n        def test_check_auth_create_fail\n          assert_protocol_error(\"Could not generate\") {\n            call_check_auth do |idres|\n              idres.instance_def(:create_check_auth_request) do\n                raise Message::KeyNotFound, \"Testing\"\n              end\n            end\n          }\n        end\n\n        def test_check_auth_okay\n          OpenID.extend(OverrideMethodMixin)\n          me = self\n          send_resp = Proc.new do |req, server_url|\n            me.assert_equal(:req, req)\n            :expected_response\n          end\n\n          OpenID.with_method_overridden(:make_kv_post, send_resp) do\n            call_check_auth do |idres|\n              idres.instance_def(:create_check_auth_request) {\n                :req\n              }\n              idres.instance_def(:process_check_auth_response) do |resp|\n                me.assert_equal(:expected_response, resp)\n              end\n            end\n          end\n        end\n\n        def test_check_auth_process_fail\n          OpenID.extend(OverrideMethodMixin)\n          me = self\n          send_resp = Proc.new do |req, server_url|\n            me.assert_equal(:req, req)\n            :expected_response\n          end\n\n          OpenID.with_method_overridden(:make_kv_post, send_resp) do\n            assert_protocol_error(\"Testing\") do\n              call_check_auth do |idres|\n                idres.instance_def(:create_check_auth_request) { :req }\n                idres.instance_def(:process_check_auth_response) do |resp|\n                  me.assert_equal(:expected_response, resp)\n                  raise ProtocolError, \"Testing\"\n                end\n              end\n            end\n          end\n        end\n\n        1.times do\n          # Fields from the signed list\n          ['mode', 'identity', 'assoc_handle'\n          ].each do |field|\n            test = lambda do\n              @message.del_arg(OPENID_NS, field)\n              assert_raises(Message::KeyNotFound) {\n                call_idres_method(:create_check_auth_request) {}\n              }\n            end\n            define_method(\"test_create_check_auth_missing_#{field}\", test)\n          end\n        end\n\n        def test_create_check_auth_request_success\n          ca_msg = call_idres_method(:create_check_auth_request) {}\n          expected = @message.copy\n          expected.set_arg(OPENID_NS, 'mode', 'check_authentication')\n          assert_equal(expected, ca_msg)\n        end\n\n      end\n\n      class CheckAuthResponseTest < Minitest::Test\n        include TestUtil\n        include ProtocolErrorMixin\n\n        def setup\n          @message = Message.from_openid_args({\n            'is_valid' => 'true',\n            })\n          @assoc = GoodAssoc.new\n          @store = Store::Memory.new\n          @server_url = 'http://invalid/'\n          @endpoint =  DummyEndpoint.new(@server_url)\n          @idres = IdResHandler.new(nil, nil, @store, @endpoint)\n        end\n\n        def call_process\n          @idres.send(:process_check_auth_response, @message)\n        end\n\n        def test_valid\n          assert_log_matches() { call_process }\n        end\n\n        def test_invalid\n          ['false', 'monkeys'].each do\n            @message.set_arg(OPENID_NS, 'is_valid', 'false')\n            assert_protocol_error(\"Server #{@server_url} responds\") {\n              assert_log_matches() { call_process }\n            }\n          end\n        end\n\n        def test_valid_invalidate\n          @message.set_arg(OPENID_NS, 'invalidate_handle', 'cheese')\n          assert_log_matches(\"Received 'invalidate_handle'\") { call_process }\n        end\n\n        def test_invalid_invalidate\n          @message.set_arg(OPENID_NS, 'invalidate_handle', 'cheese')\n          ['false', 'monkeys'].each do\n            @message.set_arg(OPENID_NS, 'is_valid', 'false')\n            assert_protocol_error(\"Server #{@server_url} responds\") {\n              assert_log_matches(\"Received 'invalidate_handle'\") {\n                call_process\n              }\n            }\n          end\n        end\n\n        def test_invalidate_no_store\n          @idres.instance_variable_set(:@store, nil)\n          @message.set_arg(OPENID_NS, 'invalidate_handle', 'cheese')\n          assert_log_matches(\"Received 'invalidate_handle'\",\n                             'Unexpectedly got \"invalidate_handle\"') {\n            call_process\n          }\n        end\n      end\n\n      class NonceTest < Minitest::Test\n        include TestUtil\n        include ProtocolErrorMixin\n\n        def setup\n          @store = Object.new\n          class << @store\n            attr_accessor :nonces, :succeed\n            def use_nonce(server_url, time, extra)\n              @nonces << [server_url, time, extra]\n              @succeed\n            end\n          end\n          @store.nonces = []\n          @nonce = Nonce.mk_nonce\n        end\n\n        def call_check_nonce(post_args, succeed=false)\n          response = Message.from_post_args(post_args)\n          if !@store.nil?\n            @store.succeed = succeed\n          end\n          idres = IdResHandler.new(response, nil, @store, nil)\n          idres.send(:check_nonce)\n        end\n\n        def test_openid1_success\n          [{},\n           {'openid.ns' => OPENID1_NS},\n           {'openid.ns' => OPENID11_NS}\n          ].each do |args|\n            call_check_nonce({'rp_nonce' => @nonce}.merge(args), true)\n          end\n        end\n\n        def test_openid1_missing\n          [{},\n           {'openid.ns' => OPENID1_NS},\n           {'openid.ns' => OPENID11_NS}\n          ].each do |args|\n            assert_protocol_error('Nonce missing') { call_check_nonce(args) }\n          end\n        end\n\n        def test_openid2_ignore_rp_nonce\n          assert_protocol_error('Nonce missing') {\n            call_check_nonce({'rp_nonce' => @nonce,\n                               'openid.ns' => OPENID2_NS})\n          }\n        end\n\n        def test_openid2_success\n          call_check_nonce({'openid.response_nonce' => @nonce,\n                             'openid.ns' => OPENID2_NS}, true)\n        end\n\n        def test_openid1_ignore_response_nonce\n          [{},\n           {'openid.ns' => OPENID1_NS},\n           {'openid.ns' => OPENID11_NS}\n          ].each do |args|\n            assert_protocol_error('Nonce missing') {\n              call_check_nonce({'openid.response_nonce' => @nonce}.merge(args))\n            }\n          end\n        end\n\n        def test_no_store\n          @store = nil\n          call_check_nonce({'rp_nonce' => @nonce})\n        end\n\n        def test_already_used\n          assert_protocol_error('Nonce already used') {\n            call_check_nonce({'rp_nonce' => @nonce}, false)\n          }\n        end\n\n        def test_malformed_nonce\n          assert_protocol_error('Malformed nonce') {\n            call_check_nonce({'rp_nonce' => 'whee!'})\n          }\n        end\n      end\n\n      class DiscoveryVerificationTest < Minitest::Test\n        include ProtocolErrorMixin\n        include TestUtil\n\n        def setup\n          @endpoint = OpenIDServiceEndpoint.new\n        end\n\n        def call_verify(msg_args)\n          call_verify_modify(msg_args){}\n        end\n\n        def call_verify_modify(msg_args)\n          msg = Message.from_openid_args(msg_args)\n          idres = IdResHandler.new(msg, nil, nil, @endpoint)\n          idres.extend(InstanceDefExtension)\n          yield idres\n          idres.send(:verify_discovery_results)\n          idres.instance_variable_get(:@endpoint)\n        end\n\n        def assert_verify_protocol_error(error_prefix, openid_args)\n          assert_protocol_error(error_prefix) {call_verify(openid_args)}\n        end\n\n        def test_openid1_no_local_id\n          @endpoint.claimed_id = 'http://invalid/'\n          assert_verify_protocol_error(\"Missing required field: \"\\\n                                       \"<#{OPENID1_NS}>identity\", {})\n        end\n\n        def test_openid1_no_endpoint\n          @endpoint = nil\n          assert_raises(ProtocolError) {\n            call_verify({'identity' => 'snakes on a plane'})\n          }\n        end\n\n        def test_openid1_fallback_1_0\n          [OPENID1_NS, OPENID11_NS].each do |openid1_ns|\n            claimed_id = 'http://claimed.id/'\n            @endpoint = nil\n            resp_mesg = Message.from_openid_args({\n              'ns' => openid1_ns,\n              'identity' => claimed_id,\n              })\n\n            # Pass the OpenID 1 claimed_id this way since we're\n            # passing None for the endpoint.\n            resp_mesg.set_arg(BARE_NS, 'openid1_claimed_id', claimed_id)\n\n            # We expect the OpenID 1 discovery verification to try\n            # matching the discovered endpoint against the 1.1 type\n            # and fall back to 1.0.\n            expected_endpoint = OpenIDServiceEndpoint.new\n            expected_endpoint.type_uris = [OPENID_1_0_TYPE]\n            expected_endpoint.local_id = nil\n            expected_endpoint.claimed_id = claimed_id\n\n            hacked_discover = Proc.new {\n              |_claimed_id| ['unused', [expected_endpoint]]\n            }\n            idres = IdResHandler.new(resp_mesg, nil, nil, @endpoint)\n            assert_log_matches('Performing discovery') {\n              OpenID.with_method_overridden(:discover, hacked_discover) {\n                idres.send(:verify_discovery_results)\n              }\n            }\n            actual_endpoint = idres.instance_variable_get(:@endpoint)\n            assert_equal(actual_endpoint, expected_endpoint)\n          end\n        end\n\n        def test_openid2_no_op_endpoint\n          assert_protocol_error(\"Missing required field: \"\\\n                                \"<#{OPENID2_NS}>op_endpoint\") {\n            call_verify({'ns'=>OPENID2_NS})\n          }\n        end\n\n        def test_openid2_local_id_no_claimed\n          assert_verify_protocol_error('openid.identity is present without',\n                                       {'ns' => OPENID2_NS,\n                                         'op_endpoint' => 'Phone Home',\n                                         'identity' => 'Jorge Lius Borges'})\n        end\n\n        def test_openid2_no_local_id_claimed\n          assert_log_matches() {\n            assert_protocol_error('openid.claimed_id is present without') {\n              call_verify({'ns' => OPENID2_NS,\n                            'op_endpoint' => 'Phone Home',\n                            'claimed_id' => 'Manuel Noriega'})\n            }\n          }\n        end\n\n        def test_openid2_no_identifiers\n          op_endpoint = 'Phone Home'\n          result_endpoint = assert_log_matches() {\n            call_verify({'ns' => OPENID2_NS,\n                          'op_endpoint' => op_endpoint})\n          }\n          assert(result_endpoint.is_op_identifier)\n          assert_equal(op_endpoint, result_endpoint.server_url)\n          assert(result_endpoint.claimed_id.nil?)\n        end\n\n        def test_openid2_no_endpoint_does_disco\n          endpoint = OpenIDServiceEndpoint.new\n          endpoint.claimed_id = 'monkeysoft'\n          @endpoint = nil\n          result = assert_log_matches('No pre-discovered') {\n            call_verify_modify({'ns' => OPENID2_NS,\n                                 'identity' => 'sour grapes',\n                                 'claimed_id' => 'monkeysoft',\n                                 'op_endpoint' => 'Phone Home'}) do |idres|\n              idres.instance_def(:discover_and_verify) do |claimed_id, endpoints|\n                @endpoint = endpoint\n              end\n            end\n          }\n          assert_equal(endpoint, result)\n        end\n\n\n        def test_openid2_mismatched_does_disco\n          @endpoint.claimed_id = 'nothing special, but different'\n          @endpoint.local_id = 'green cheese'\n\n          endpoint = OpenIDServiceEndpoint.new\n          endpoint.claimed_id = 'monkeysoft'\n\n          result = assert_log_matches('Error attempting to use stored',\n                             'Attempting discovery') {\n            call_verify_modify({'ns' => OPENID2_NS,\n                                 'identity' => 'sour grapes',\n                                 'claimed_id' => 'monkeysoft',\n                                 'op_endpoint' => 'Green Cheese'}) do |idres|\n              idres.instance_def(:discover_and_verify) do |claimed_id, endpoints|\n                @endpoint = endpoint\n              end\n            end\n          }\n          assert(endpoint.equal?(result))\n        end\n\n        def test_verify_discovery_single_claimed_id_mismatch\n          idres = IdResHandler.new(nil, nil)\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = 'http://i-am-sam/'\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_2_0_TYPE]\n\n          to_match = @endpoint.dup\n          to_match.claimed_id = 'http://something.else/'\n\n          e = assert_raises(ProtocolError) {\n            idres.send(:verify_discovery_single, @endpoint, to_match)\n          }\n          assert(e.to_s =~ /different subjects/)\n        end\n\n        def test_openid1_1_verify_discovery_single_no_server_url\n          idres = IdResHandler.new(nil, nil)\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = 'http://i-am-sam/'\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_1_1_TYPE]\n\n          to_match = @endpoint.dup\n          to_match.claimed_id = 'http://i-am-sam/'\n          to_match.type_uris = [OPENID_1_1_TYPE]\n          to_match.server_url = nil\n\n          idres.send(:verify_discovery_single, @endpoint, to_match)\n        end\n\n        def test_openid2_use_pre_discovered\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = 'http://i-am-sam/'\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_2_0_TYPE]\n\n          result = assert_log_matches() {\n            call_verify({'ns' => OPENID2_NS,\n                          'identity' => @endpoint.local_id,\n                          'claimed_id' => @endpoint.claimed_id,\n                          'op_endpoint' => @endpoint.server_url\n                        })\n          }\n          assert(result.equal?(@endpoint))\n        end\n\n        def test_openid2_use_pre_discovered_wrong_type\n          text = \"verify failed\"\n          me = self\n\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = 'i am sam'\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_1_1_TYPE]\n          endpoint = @endpoint\n\n          msg = Message.from_openid_args({'ns' => OPENID2_NS,\n                                           'identity' => @endpoint.local_id,\n                                           'claimed_id' =>\n                                           @endpoint.claimed_id,\n                                           'op_endpoint' =>\n                                           @endpoint.server_url})\n\n          idres = IdResHandler.new(msg, nil, nil, @endpoint)\n          idres.extend(InstanceDefExtension)\n          idres.instance_def(:discover_and_verify) { |claimed_id, to_match|\n            me.assert_equal(endpoint.claimed_id, to_match[0].claimed_id)\n            me.assert_equal(claimed_id, endpoint.claimed_id)\n            raise ProtocolError, text\n          }\n          assert_log_matches('Error attempting to use stored',\n                             'Attempting discovery') {\n            assert_protocol_error(text) {\n              idres.send(:verify_discovery_results)\n            }\n          }\n        end\n\n\n        def test_openid1_use_pre_discovered\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = 'http://i-am-sam/'\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_1_1_TYPE]\n\n          result = assert_log_matches() {\n            call_verify({'ns' => OPENID1_NS,\n                          'identity' => @endpoint.local_id})\n          }\n          assert(result.equal?(@endpoint))\n        end\n\n\n        def test_openid1_use_pre_discovered_wrong_type\n          verified_error = Class.new(Exception)\n\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = 'i am sam'\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_2_0_TYPE]\n\n          assert_log_matches('Error attempting to use stored',\n                             'Attempting discovery') {\n            assert_raises(verified_error) {\n              call_verify_modify({'ns' => OPENID1_NS,\n                                   'identity' => @endpoint.local_id}) { |idres|\n                idres.instance_def(:discover_and_verify) do |claimed_id, endpoints|\n                  raise verified_error\n                end\n              }\n            }\n          }\n        end\n\n        def test_openid2_fragment\n          claimed_id = \"http://unittest.invalid/\"\n          claimed_id_frag = claimed_id + \"#fragment\"\n\n          @endpoint.local_id = 'my identity'\n          @endpoint.claimed_id = claimed_id\n          @endpoint.server_url = 'Phone Home'\n          @endpoint.type_uris = [OPENID_2_0_TYPE]\n\n          result = assert_log_matches() {\n            call_verify({'ns' => OPENID2_NS,\n                          'identity' => @endpoint.local_id,\n                          'claimed_id' => claimed_id_frag,\n                          'op_endpoint' => @endpoint.server_url})\n          }\n\n          [:local_id, :server_url, :type_uris].each do |sym|\n            assert_equal(@endpoint.send(sym), result.send(sym))\n          end\n          assert_equal(claimed_id_frag, result.claimed_id)\n        end\n\n        def test_endpoint_without_local_id\n          # An endpoint like this with no local_id is generated as a result of\n          # e.g. Yadis discovery with no LocalID tag.\n          @endpoint.server_url = \"http://localhost:8000/openidserver\"\n          @endpoint.claimed_id = \"http://localhost:8000/id/id-jo\"\n\n          to_match = OpenIDServiceEndpoint.new\n          to_match.server_url = \"http://localhost:8000/openidserver\"\n          to_match.claimed_id = \"http://localhost:8000/id/id-jo\"\n          to_match.local_id = \"http://localhost:8000/id/id-jo\"\n\n          idres = IdResHandler.new(nil, nil)\n          assert_log_matches() {\n            idres.send(:verify_discovery_single, @endpoint, to_match)\n          }\n        end\n      end\n\n      class IdResTopLevelTest < Minitest::Test\n        def test_id_res\n          endpoint = OpenIDServiceEndpoint.new\n          endpoint.server_url = 'http://invalid/server'\n          endpoint.claimed_id = 'http://my.url/'\n          endpoint.local_id = 'http://invalid/username'\n          endpoint.type_uris = [OPENID_2_0_TYPE]\n\n          assoc = GoodAssoc.new\n          store = Store::Memory.new\n          store.store_association(endpoint.server_url, assoc)\n\n          signed_fields =\n            [\n             'response_nonce',\n             'op_endpoint',\n             'assoc_handle',\n             'identity',\n             'claimed_id',\n             'ns',\n             'return_to',\n            ]\n\n          return_to = 'http://return.to/'\n          args = {\n            'ns' => OPENID2_NS,\n            'return_to' => return_to,\n            'claimed_id' => endpoint.claimed_id,\n            'identity' => endpoint.local_id,\n            'assoc_handle' => assoc.handle,\n            'op_endpoint' => endpoint.server_url,\n            'response_nonce' => Nonce.mk_nonce,\n            'signed' => signed_fields.join(','),\n            'sig' => GOODSIG,\n          }\n          msg = Message.from_openid_args(args)\n          idres = OpenID::Consumer::IdResHandler.new(msg, return_to,\n                                                     store, endpoint)\n          assert_equal(idres.signed_fields,\n                       signed_fields.map {|f|'openid.' + f})\n        end\n      end\n\n\n      class DiscoverAndVerifyTest < Minitest::Test\n        include ProtocolErrorMixin\n        include TestUtil\n        OpenID.extend(OverrideMethodMixin)\n\n        def test_no_services\n          me = self\n          disco = Proc.new do |e|\n            me.assert_equal(e, :sentinel)\n            [:undefined, []]\n          end\n          endpoint = OpenIDServiceEndpoint.new\n          endpoint.claimed_id = :sentinel\n          idres = IdResHandler.new(nil, nil)\n          assert_log_matches('Performing discovery on') do\n            assert_protocol_error('No OpenID information found') do\n              OpenID.with_method_overridden(:discover, disco) do\n                idres.send(:discover_and_verify, :sentinel, [endpoint])\n              end\n            end\n          end\n        end\n      end\n\n      class VerifyDiscoveredServicesTest < Minitest::Test\n        include ProtocolErrorMixin\n        include TestUtil\n\n        def test_no_services\n          endpoint = OpenIDServiceEndpoint.new\n          endpoint.claimed_id = :sentinel\n          idres = IdResHandler.new(nil, nil)\n          assert_log_matches('Discovery verification failure') do\n            assert_protocol_error('No matching endpoint') do\n              idres.send(:verify_discovered_services,\n                         'http://bogus.id/', [], [endpoint])\n            end\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_kvform.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/kvform'\nrequire 'openid/util'\nrequire 'util'\n\ninclude OpenID\n\nclass KVFormTests < Minitest::Test\n  include OpenID::TestUtil\n\n  def test_kvdict\n    [\n     # (kvform, parsed dictionary, expected warnings)\n     [\"\", {}, 0],\n     [\"\\n  \\n     \\n\", {}, 0],\n     [\"college:harvey mudd\\n\", {\"college\" => \"harvey mudd\"}, 0],\n     [\"city:claremont\\nstate:CA\\n\",\n      {\"city\" => \"claremont\", \"state\" => \"CA\"}, 0],\n     [\"is_valid:true\\ninvalidate_handle:{HMAC-SHA1:2398410938412093}\\n\",\n      {\"is_valid\" => \"true\",\n        \"invalidate_handle\" => \"{HMAC-SHA1:2398410938412093}\"}, 0],\n\n     # Warnings from lines with no colon:\n     [\"x\\n\", {}, 1],\n     [\"x\\nx\\n\", {}, 2],\n     [\"East is least\\n\", {}, 1],\n\n     # But not from blank lines (because LJ generates them)\n     [\"x\\n\\n\", {}, 1],\n\n     # Warning from empty key\n     [\":\\n\", {''=>''}, 1],\n     [\":missing key\\n\", {''=>'missing key'}, 1],\n\n     # Warnings from leading or trailing whitespace in key or value\n     [\" street:foothill blvd\\n\", {\"street\"=>\"foothill blvd\"}, 1],\n     [\"major: computer science\\n\", {\"major\"=>\"computer science\"}, 1],\n     [\" dorm : east \\n\", {\"dorm\"=>\"east\"}, 2],\n\n     # Warnings from missing trailing newline\n     [\"e^(i*pi)+1:0\", {\"e^(i*pi)+1\" => \"0\"}, 1],\n     [\"east:west\\nnorth:south\", {\"east\"=>\"west\", \"north\"=>\"south\"}, 1],\n    ].each { |case_|\n      _run_kvdictTest(case_)\n    }\n  end\n\n  def _run_kvdictTest(case_)\n    kv, dct, warnings = case_\n\n    d = nil\n    d2 = nil\n    assert_log_line_count(warnings) {\n      # Convert KVForm to dict\n      d = Util.kv_to_dict(kv)\n\n      # Strict mode should raise KVFormError instead of logging\n      # messages\n      if warnings > 0\n        assert_raises(KVFormError) do\n          Util.kv_to_seq(kv, true)\n        end\n      end\n\n      # make sure it parses to expected dict\n      assert_equal(dct, d)\n    }\n\n    # Convert back to KVForm and round-trip back to dict to make sure\n    # that *** dict -> kv -> dict is identity. ***\n    kv = Util.dict_to_kv(d)\n\n    silence_logging {\n      d2 = Util.kv_to_dict(kv)\n    }\n\n    assert_equal(d, d2)\n  end\n\n  def test_kvseq\n    [\n     [[], \"\", 0],\n\n     [[[\"openid\", \"useful\"], [\"a\", \"b\"]], \"openid:useful\\na:b\\n\", 0],\n\n     # Warnings about leading whitespace\n     [[[\" openid\", \"useful\"], [\"a\", \"b\"]], \" openid:useful\\na:b\\n\", 2],\n\n     # Warnings about leading and trailing whitespace\n     [[[\" openid \", \" useful \"],\n       [\" a \", \" b \"]], \" openid : useful \\n a : b \\n\", 8],\n\n     # warnings about leading and trailing whitespace, but not about\n     # internal whitespace.\n     [[[\" open id \", \" use ful \"],\n       [\" a \", \" b \"]], \" open id : use ful \\n a : b \\n\", 8],\n\n     [[[\"foo\", \"bar\"]], \"foo:bar\\n\", 0],\n    ].each { |case_|\n      _run_kvseqTest(case_)\n    }\n  end\n\n  def _cleanSeq(seq)\n    # Create a new sequence by stripping whitespace from start and end\n    # of each value of each pair\n    seq.collect { |k, v| [k.strip(), v.strip()] }\n  end\n\n  def _run_kvseqTest(case_)\n    seq, kvform, warnings = case_\n\n    assert_log_line_count(warnings) {\n      # seq serializes to expected kvform\n      actual = Util.seq_to_kv(seq)\n\n      assert_equal(kvform, actual)\n      assert actual.is_a?(String)\n\n      # Strict mode should raise KVFormError instead of logging\n      # messages\n      if warnings > 0\n        assert_raises(KVFormError) do\n          Util.seq_to_kv(seq, true)\n        end\n      end\n\n      # Parse back to sequence. Expected to be unchanged, except\n      # stripping whitespace from start and end of values\n      # (i. e. ordering, case, and internal whitespace is preserved)\n      seq = Util.kv_to_seq(actual)\n      clean_seq = _cleanSeq(seq)\n\n      assert_equal(seq, clean_seq)\n    }\n  end\n\n  def test_kvexc\n    [\n     [[\"openid\", \"use\\nful\"]],\n     [[\"open\\nid\", \"useful\"]],\n     [[\"open\\nid\", \"use\\nful\"]],\n     [[\"open:id\", \"useful\"]],\n     [[\"foo\", \"bar\"], [\"ba\\n d\", \"seed\"]],\n     [[\"foo\", \"bar\"], [\"bad:\", \"seed\"]],\n    ].each { |case_|\n      _run_kvexcTest(case_)\n    }\n  end\n\n  def _run_kvexcTest(case_)\n    seq = case_\n\n    assert_raises(KVFormError) do\n      Util.seq_to_kv(seq)\n    end\n  end\n\n  def test_convert\n    assert_log_line_count(2) {\n      result = Util.seq_to_kv([[1, 1]])\n      assert_equal(result, \"1:1\\n\")\n    }\n  end\nend\n"
  },
  {
    "path": "test/test_kvpost.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"openid/kvpost\"\nrequire \"openid/kvform\"\nrequire \"openid/message\"\n\nmodule OpenID\n  class KVPostTestCase < Minitest::Test\n    include FetcherMixin\n\n    def mk_resp(status, resp_hash)\n      return MockResponse.new(status, Util.dict_to_kv(resp_hash))\n    end\n\n    def test_msg_from_http_resp_success\n      resp = mk_resp(200, {'mode' => 'seitan'})\n      msg = Message.from_http_response(resp, 'http://invalid/')\n      assert_equal({'openid.mode' => 'seitan'}, msg.to_post_args)\n    end\n\n    def test_400\n      args = {'error' => 'I ate too much cheese',\n        'error_code' => 'sadness'}\n      resp = mk_resp(400, args)\n      begin\n        val = Message.from_http_response(resp, 'http://invalid/')\n      rescue ServerError => why\n        assert_equal(why.error_text, 'I ate too much cheese')\n        assert_equal(why.error_code, 'sadness')\n        assert_equal(why.message.to_args, args)\n      else\n        fail(\"Expected exception. Got: #{val}\")\n      end\n    end\n\n    def test_500\n      args = {'error' => 'I ate too much cheese',\n        'error_code' => 'sadness'}\n      resp = mk_resp(500, args)\n      assert_raises(HTTPStatusError) {\n        Message.from_http_response(resp, 'http://invalid')\n      }\n    end\n\n    def make_kv_post_with_response(status, args)\n      resp = mk_resp(status, args)\n      mock_fetcher = Class.new do\n        define_method(:fetch) do |url, body, xxx, yyy|\n          resp\n        end\n      end\n\n      with_fetcher(mock_fetcher.new) do\n        OpenID.make_kv_post(Message.from_openid_args(args), 'http://invalid/')\n      end\n    end\n\n    def test_make_kv_post\n      assert_raises(HTTPStatusError) {\n        make_kv_post_with_response(500, {})\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_linkparse.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'openid/consumer/html_parse'\n\nclass LinkParseTestCase < Minitest::Test\n  include OpenID::TestDataMixin\n\n  def attr_cmp(expected, found)\n    e = expected.to_a.sort\n    f = found.to_a.sort\n    while (ep = e.shift)\n      ek, ev = ep\n      fk, fv = f.shift\n      ok = false\n      while ek[-1] == '*'[0] # optional entry detected\n        if fk == ek[0...-1] and fv==ev # optional entry found\n          ok = true\n          break\n        else # not found. okay, move on to next expected pair\n          ek, ev = e.shift\n        end\n        if ek.nil?\n          if fk == nil\n            ok = true\n          end\n          break\n        end\n      end\n      next if ok\n      next if fk == ek and fv == ev\n      return false\n    end\n    return f.empty?\n  end\n\n  def test_attrcmp\n    good = [\n     [{'foo' => 'bar'},{'foo' => 'bar'}],\n     [{'foo*' => 'bar'},{'foo' => 'bar'}],\n     [{'foo' => 'bar', 'bam*' => 'baz'},{'foo' => 'bar'}],\n     [{'foo' => 'bar', 'bam*' => 'baz', 'tak' => 'tal'},\n        {'foo' => 'bar', 'tak' => 'tal'}],\n     ]\n    bad = [\n     [{},{'foo' => 'bar'}],\n     [{'foo' => 'bar'}, {'bam' => 'baz'}],\n     [{'foo' => 'bar'}, {}],\n     [{'foo*' => 'bar'},{'foo*' => 'bar'}],\n     [{'foo' => 'bar', 'tak' => 'tal'}, {'foo' => 'bar'}]\n    ]\n    good.each{|c|assert(attr_cmp(c[0],c[1]),c.inspect)}\n    bad.each{|c|assert(!attr_cmp(c[0],c[1]),c.inspect)}\n\n  end\n\n  def test_linkparse\n    cases = read_data_file('linkparse.txt', false).split(\"\\n\\n\\n\")\n\n    numtests = nil\n    testnum = 0\n    cases.each {|c|\n      headers, html = c.split(\"\\n\\n\",2)\n      expected_links = []\n      name = \"\"\n      testnum += 1\n      headers.split(\"\\n\").each{|h|\n        k,v = h.split(\":\",2)\n        v = '' if v.nil?\n        if k == \"Num Tests\"\n          assert(numtests.nil?, \"datafile parsing error: there can be only one NumTests\")\n          numtests = v.to_i\n          testnum = 0\n          next\n        elsif k == \"Name\"\n          name = v.strip\n        elsif k == \"Link\" or k == \"Link*\"\n          attrs = {}\n          v.strip.split.each{|a|\n            kk,vv = a.split('=')\n            attrs[kk]=vv\n          }\n          expected_links << [k== \"Link*\", attrs]\n        else\n          assert(false, \"datafile parsing error: bad header #{h}\")\n        end\n      }\n      html = html.force_encoding('UTF-8') if html.respond_to? :force_encoding\n      links = OpenID::parse_link_attrs(html)\n      \n      found = links.dup\n      expected = expected_links.dup\n      while(fl = found.shift)\n        optional, el = expected.shift\n        while optional and !attr_cmp(el, fl) and not expected.empty?\n          optional, el = expected.shift\n        end\n        assert(attr_cmp(el,fl), \"#{name}: #{fl.inspect} does not match #{el.inspect}\")\n      end\n    }\n    assert_equal(numtests, testnum, \"Number of tests\")\n\n    # test handling of invalid UTF-8 byte sequences\n    if \"\".respond_to? :force_encoding\n      html = \"<html><body>hello joel\\255</body></html>\".force_encoding('UTF-8') \n    else\n      html = \"<html><body>hello joel\\255</body></html>\"\n    end\n    OpenID::parse_link_attrs(html)\n\n  end\nend\n"
  },
  {
    "path": "test/test_message.rb",
    "content": "# last synced with Python openid.test.test_message on 6/29/2007.\nrequire 'minitest/autorun'\nrequire 'util'\nrequire 'openid/message'\nrequire 'rexml/document'\n\nmodule OpenID\n  module GetArgsMixin\n\n    # Tests a standard set of behaviors of Message.get_arg with\n    # variations on handling defaults.\n    def get_arg_tests(ns, key, expected=nil)\n      if expected.nil?\n        assert_nil(@m.get_arg(ns, key))\n\n        assert_equal(@m.get_arg(ns, key, :a_default), :a_default)\n        assert_raises(Message::KeyNotFound) { @m.get_arg(ns, key, NO_DEFAULT) }\n      else\n        assert_equal(expected, @m.get_arg(ns, key))\n\n        assert_equal(@m.get_arg(ns, key, :a_default), expected)\n        assert_equal(@m.get_arg(ns, key, NO_DEFAULT), expected)\n      end\n\n    end\n\n  end\n\n  class EmptyMessageTestCase < Minitest::Test\n    include GetArgsMixin\n\n    def setup\n      @m = Message.new\n    end\n\n    def test_get_aliased_arg_no_default\n      assert_raises(Message::KeyNotFound) do\n        @m.get_aliased_arg('ns.pork', NO_DEFAULT)\n      end\n\n      ns_uri = \"urn:pork\"\n      @m.namespaces.add_alias(ns_uri, 'pork_alias')\n\n      # Should return ns_uri.\n      assert_equal(ns_uri, @m.get_aliased_arg('ns.pork_alias', NO_DEFAULT))\n    end\n\n    def test_to_post_args\n      assert_equal({}, @m.to_post_args)\n    end\n\n    def test_to_args\n      assert_equal({}, @m.to_args)\n    end\n\n    def test_to_kvform\n      assert_equal('', @m.to_kvform)\n    end\n\n    def test_from_kvform\n      kvform = \"foo:bar\\none:two\\n\"\n      args = {'foo' => 'bar', 'one' => 'two'}\n      expected_result = Message.from_openid_args(args)\n\n      assert_equal(expected_result, Message.from_kvform(kvform))\n    end\n\n    def test_to_url_encoded\n      assert_equal('', @m.to_url_encoded)\n    end\n\n    def test_to_url\n      base_url = 'http://base.url/'\n      assert_equal(base_url, @m.to_url(base_url))\n    end\n\n    def test_get_openid\n      assert_nil(@m.get_openid_namespace)\n    end\n\n    def test_get_key_openid\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.get_key(OPENID_NS, nil)\n      }\n    end\n\n    def test_get_key_bare\n      assert_equal('foo', @m.get_key(BARE_NS, 'foo'))\n    end\n\n    def test_get_key_ns1\n      assert_nil(@m.get_key(OPENID1_NS, 'foo'))\n    end\n\n    def test_get_key_ns2\n      assert_nil(@m.get_key(OPENID2_NS, 'foo'))\n    end\n\n    def test_get_key_ns3\n      assert_nil(@m.get_key('urn:something-special', 'foo'))\n    end\n\n    def test_has_key\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.has_key?(OPENID_NS, 'foo')\n      }\n    end\n\n    def test_has_key_bare\n      assert_equal(false, @m.has_key?(BARE_NS, 'foo'))\n    end\n\n    def test_has_key_ns1\n      assert_equal(false, @m.has_key?(OPENID1_NS, 'foo'))\n    end\n\n    def test_has_key_ns2\n      assert_equal(false, @m.has_key?(OPENID2_NS, 'foo'))\n    end\n\n    def test_has_key_ns3\n      assert_equal(false, @m.has_key?('urn:xxx', 'foo'))\n    end\n\n    def test_get_arg\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.get_args(OPENID_NS)\n      }\n    end\n\n    def test_get_arg_bare\n      get_arg_tests(BARE_NS, 'foo')\n    end\n\n    def test_get_arg_ns1\n      get_arg_tests(OPENID1_NS, 'foo')\n    end\n\n    def test_get_arg_ns2\n      get_arg_tests(OPENID2_NS, 'foo')\n    end\n\n    def test_get_arg_ns3\n      get_arg_tests('urn:nothing-significant', 'foo')\n    end\n\n    def test_get_args\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.get_args(OPENID_NS)\n      }\n    end\n\n    def test_get_args_bare\n      assert_equal({}, @m.get_args(BARE_NS))\n    end\n\n    def test_get_args_ns1\n      assert_equal({}, @m.get_args(OPENID1_NS))\n    end\n\n    def test_get_args_ns2\n      assert_equal({}, @m.get_args(OPENID2_NS))\n    end\n\n    def test_get_args_ns3\n      assert_equal({}, @m.get_args('urn:xxx'))\n    end\n\n    def test_update_args\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.update_args(OPENID_NS, {'does not'=>'matter'})\n      }\n    end\n\n    def _test_update_args_ns(ns)\n      updates = {\n        'camper van beethoven' => 'david l',\n        'magnolia electric, co' => 'jason m'\n      }\n      assert_equal({}, @m.get_args(ns))\n      @m.update_args(ns, updates)\n      assert_equal(updates, @m.get_args(ns))\n    end\n\n    def test_update_args_bare\n      _test_update_args_ns(BARE_NS)\n    end\n    def test_update_args_ns1\n      _test_update_args_ns(OPENID1_NS)\n    end\n    def test_update_args_ns2\n      _test_update_args_ns(OPENID2_NS)\n    end\n    def test_update_args_ns3\n      _test_update_args_ns('urn:xxx')\n    end\n\n    def test_set_arg\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.set_arg(OPENID_NS,'does not','matter')\n      }\n    end\n\n    def _test_set_arg_ns(ns)\n      key = 'Camper Van Beethoven'\n      value = 'David Lowery'\n      assert_nil(@m.get_arg(ns, key))\n      @m.set_arg(ns, key, value)\n      assert_equal(value, @m.get_arg(ns, key))\n    end\n\n    def test_set_arg_bare\n      _test_set_arg_ns(BARE_NS)\n    end\n    def test_set_arg_ns1\n      _test_set_arg_ns(OPENID1_NS)\n    end\n    def test_set_arg_ns2\n      _test_set_arg_ns(OPENID2_NS)\n    end\n    def test_set_arg_ns3\n      _test_set_arg_ns('urn:xxx')\n    end\n\n    def test_del_arg\n      assert_raises(UndefinedOpenIDNamespace) {\n        @m.set_arg(OPENID_NS, 'does not', 'matter')\n      }\n    end\n\n    def _test_del_arg_ns(ns)\n      key = 'Fleeting Joys'\n      assert_nil(@m.del_arg(ns, key))\n    end\n\n    def test_del_arg_bare\n      _test_del_arg_ns(BARE_NS)\n    end\n    def test_del_arg_ns1\n      _test_del_arg_ns(OPENID1_NS)\n    end\n    def test_del_arg_ns2\n      _test_del_arg_ns(OPENID2_NS)\n    end\n    def test_del_arg_ns3\n      _test_del_arg_ns('urn:xxx')\n    end\n\n    def test_isOpenID1\n      assert_equal(false, @m.is_openid1)\n    end\n\n    def test_isOpenID2\n      assert_equal(false, @m.is_openid2)\n    end\n\n    def test_set_openid_namespace\n      assert_raises(InvalidOpenIDNamespace) {\n        @m.set_openid_namespace('http://invalid/', false)\n      }\n    end\n  end\n\n  class OpenID1MessageTest < Minitest::Test\n    include GetArgsMixin\n\n    def setup\n      @m = Message.from_post_args({'openid.mode' => 'error',\n                                            'openid.error' => 'unit test'})\n    end\n\n    def test_has_openid_ns\n      assert_equal(OPENID1_NS, @m.get_openid_namespace)\n      assert_equal(OPENID1_NS,\n                   @m.namespaces.get_namespace_uri(NULL_NAMESPACE))\n    end\n\n    def test_get_aliased_arg\n      assert_equal('error', @m.get_aliased_arg('mode'))\n    end\n\n    def test_get_aliased_arg_ns\n      assert_equal(OPENID1_NS, @m.get_aliased_arg('ns'))\n    end\n\n    def test_get_aliased_arg_with_ns\n      @m = Message.from_post_args(\n          {'openid.mode' => 'error',\n           'openid.error' => 'unit test',\n           'openid.ns.invalid' => 'http://invalid/',\n           'openid.invalid.stuff' => 'things',\n           'openid.invalid.stuff.blinky' => 'powerplant',\n          })\n      assert_equal('http://invalid/', @m.get_aliased_arg('ns.invalid'))\n      assert_equal('things', @m.get_aliased_arg('invalid.stuff'))\n      assert_equal('powerplant', @m.get_aliased_arg('invalid.stuff.blinky'))\n    end\n\n    def test_get_aliased_arg_with_ns_default\n      @m = Message.from_post_args({})\n      assert_equal('monkeys!', @m.get_aliased_arg('ns.invalid', \"monkeys!\"))\n    end\n\n    def test_to_post_args\n      assert_equal({'openid.mode' => 'error',\n                     'openid.error' => 'unit test'},\n                   @m.to_post_args)\n    end\n\n    def test_to_post_args_ns\n      invalid_ns = 'http://invalid/'\n      @m.namespaces.add_alias(invalid_ns, 'foos')\n      @m.set_arg(invalid_ns, 'ball', 'awesome')\n      @m.set_arg(BARE_NS, 'xey', 'value')\n      assert_equal({'openid.mode' => 'error',\n                     'openid.error' => 'unit test',\n                     'openid.foos.ball' => 'awesome',\n                     'xey' => 'value',\n                     'openid.ns.foos' => 'http://invalid/'\n                   }, @m.to_post_args)\n    end\n\n    def test_to_args\n      assert_equal({'mode' => 'error',\n                     'error' => 'unit test'},\n                   @m.to_args)\n    end\n\n    def test_to_kvform\n      assert_equal(\"error:unit test\\nmode:error\\n\",\n                   @m.to_kvform)\n    end\n\n    def test_to_url_encoded\n      assert_equal('openid.error=unit+test&openid.mode=error',\n                   @m.to_url_encoded)\n    end\n\n    def test_to_url\n      base_url = 'http://base.url/'\n      actual = @m.to_url(base_url)\n      actual_base = actual[0...base_url.length]\n      assert_equal(base_url, actual_base)\n      assert_equal('?', actual[base_url.length].chr)\n      query = actual[base_url.length+1..-1]\n      assert_equal({'openid.mode'=>['error'],'openid.error'=>['unit test']},\n                   CGI.parse(query))\n    end\n\n    def test_get_openid\n      assert_equal(OPENID1_NS, @m.get_openid_namespace)\n    end\n\n    def test_get_key_openid\n      assert_equal('openid.mode', @m.get_key(OPENID_NS, 'mode'))\n    end\n\n    def test_get_key_bare\n      assert_equal('mode', @m.get_key(BARE_NS, 'mode'))\n    end\n\n    def test_get_key_ns1\n      assert_equal('openid.mode', @m.get_key(OPENID1_NS, 'mode'))\n    end\n\n    def test_get_key_ns2\n      assert_nil(@m.get_key(OPENID2_NS, 'mode'))\n    end\n\n    def test_get_key_ns3\n      assert_nil(@m.get_key('urn:xxx', 'mode'))\n    end\n\n    def test_has_key\n      assert_equal(true, @m.has_key?(OPENID_NS, 'mode'))\n    end\n    def test_has_key_bare\n      assert_equal(false, @m.has_key?(BARE_NS, 'mode'))\n    end\n    def test_has_key_ns1\n      assert_equal(true, @m.has_key?(OPENID1_NS, 'mode'))\n    end\n    def test_has_key_ns2\n      assert_equal(false, @m.has_key?(OPENID2_NS, 'mode'))\n    end\n    def test_has_key_ns3\n      assert_equal(false, @m.has_key?('urn:xxx', 'mode'))\n    end\n\n    def test_get_arg\n      assert_equal('error', @m.get_arg(OPENID_NS, 'mode'))\n    end\n\n    def test_get_arg_bare\n      get_arg_tests(BARE_NS, 'mode')\n    end\n\n    def test_get_arg_ns\n      get_arg_tests(OPENID_NS, 'mode', 'error')\n    end\n\n    def test_get_arg_ns1\n      get_arg_tests(OPENID1_NS, 'mode', 'error')\n    end\n\n    def test_get_arg_ns2\n      get_arg_tests(OPENID2_NS, 'mode')\n    end\n\n    def test_get_arg_ns3\n      get_arg_tests('urn:nothing-significant', 'mode')\n    end\n\n    def test_get_args\n      assert_equal({'mode'=>'error','error'=>'unit test'},\n                   @m.get_args(OPENID_NS))\n    end\n    def test_get_args_bare\n      assert_equal({}, @m.get_args(BARE_NS))\n    end\n    def test_get_args_ns1\n      assert_equal({'mode'=>'error','error'=>'unit test'},\n                   @m.get_args(OPENID1_NS))\n    end\n    def test_get_args_ns2\n      assert_equal({}, @m.get_args(OPENID2_NS))\n    end\n    def test_get_args_ns3\n      assert_equal({}, @m.get_args('urn:xxx'))\n    end\n\n    def _test_update_args_ns(ns, before=nil)\n      if before.nil?\n        before = {}\n      end\n      update_args = {\n        'Camper van Beethoven'=>'David Lowery',\n        'Magnolia Electric Co.'=>'Jason Molina'\n      }\n      assert_equal(before, @m.get_args(ns))\n      @m.update_args(ns, update_args)\n      after = before.dup\n      after.update(update_args)\n      assert_equal(after, @m.get_args(ns))\n    end\n\n    def test_update_args\n      _test_update_args_ns(OPENID_NS, {'mode'=>'error','error'=>'unit test'})\n    end\n    def test_update_args_bare\n      _test_update_args_ns(BARE_NS)\n    end\n    def test_update_args_ns1\n      _test_update_args_ns(OPENID1_NS, {'mode'=>'error','error'=>'unit test'})\n    end\n    def test_update_args_ns2\n      _test_update_args_ns(OPENID2_NS)\n    end\n    def test_update_args_ns3\n      _test_update_args_ns('urn:xxx')\n    end\n\n    def _test_set_arg_ns(ns)\n      key = 'awesometown'\n      value = 'funny'\n      assert_nil(@m.get_arg(ns,key))\n      @m.set_arg(ns, key, value)\n      assert_equal(value, @m.get_arg(ns,key))\n    end\n\n    def test_set_arg; _test_set_arg_ns(OPENID_NS); end\n    def test_set_arg_bare; _test_set_arg_ns(BARE_NS); end\n    def test_set_arg_ns1; _test_set_arg_ns(OPENID1_NS); end\n    def test_set_arg_ns2; _test_set_arg_ns(OPENID2_NS); end\n    def test_set_arg_ns3; _test_set_arg_ns('urn:xxx'); end\n\n    def _test_del_arg_ns(ns)\n      key = 'marry an'\n      value = 'ice cream sandwich'\n      @m.set_arg(ns, key, value)\n      assert_equal(value, @m.get_arg(ns,key))\n      @m.del_arg(ns,key)\n      assert_nil(@m.get_arg(ns,key))\n    end\n\n    def test_del_arg; _test_del_arg_ns(OPENID_NS); end\n    def test_del_arg_bare; _test_del_arg_ns(BARE_NS); end\n    def test_del_arg_ns1; _test_del_arg_ns(OPENID1_NS); end\n    def test_del_arg_ns2; _test_del_arg_ns(OPENID2_NS); end\n    def test_del_arg_ns3; _test_del_arg_ns('urn:yyy'); end\n\n    def test_isOpenID1\n      assert_equal(true, @m.is_openid1)\n    end\n\n    def test_isOpenID2\n      assert_equal(false, @m.is_openid2)\n    end\n\n    def test_equal\n      assert_equal(Message.new, Message.new)\n      refute_equal(Message.new, nil)\n    end\n\n    def test_from_openid_args_undefined_ns\n      expected = 'almost.complete'\n      msg = Message.from_openid_args({'coverage.is' => expected})\n      actual = msg.get_arg(OPENID1_NS, 'coverage.is')\n      assert_equal(expected, actual)\n    end\n\n    # XXX: we need to implement the KVForm module before we can fix this\n    def TODOtest_from_kvform\n      kv = 'foos:ball\\n'\n      msg = Message.from_kvform(kv)\n      assert_equal(msg.get(OPENID1_NS, 'foos'), 'ball')\n    end\n\n    def test_initialize_sets_namespace\n      msg = Message.new(OPENID1_NS)\n      assert_equal(OPENID1_NS, msg.get_openid_namespace)\n    end\n  end\n\n  class OpenID1ExplicitMessageTest < Minitest::Test\n    # XXX - check to make sure the test suite will get built the way this\n    # expects.\n    def setup\n      @m = Message.from_post_args({'openid.mode'=>'error',\n                                          'openid.error'=>'unit test',\n                                          'openid.ns'=>OPENID1_NS})\n    end\n\n    def test_to_post_args\n      assert_equal({'openid.mode' => 'error',\n                    'openid.error' => 'unit test',\n                    'openid.ns'=>OPENID1_NS,\n                    },\n                   @m.to_post_args)\n    end\n\n    def test_to_post_args_ns\n      invalid_ns = 'http://invalid/'\n      @m.namespaces.add_alias(invalid_ns, 'foos')\n      @m.set_arg(invalid_ns, 'ball', 'awesome')\n      @m.set_arg(BARE_NS, 'xey', 'value')\n      assert_equal({'openid.mode' => 'error',\n                     'openid.error' => 'unit test',\n                     'openid.foos.ball' => 'awesome',\n                     'xey' => 'value',\n                     'openid.ns'=>OPENID1_NS,\n                     'openid.ns.foos' => 'http://invalid/'\n                   }, @m.to_post_args)\n    end\n\n    def test_to_args\n      assert_equal({'mode' => 'error',\n                     'error' => 'unit test',\n                     'ns'=>OPENID1_NS\n                     },\n                   @m.to_args)\n    end\n\n    def test_to_kvform\n      assert_equal(\"error:unit test\\nmode:error\\nns:#{OPENID1_NS}\\n\",\n                   @m.to_kvform)\n    end\n\n    def test_to_url_encoded\n      assert_equal('openid.error=unit+test&openid.mode=error&openid.ns=http%3A%2F%2Fopenid.net%2Fsignon%2F1.0',\n                   @m.to_url_encoded)\n    end\n\n    def test_to_url\n      base_url = 'http://base.url/'\n      actual = @m.to_url(base_url)\n      actual_base = actual[0...base_url.length]\n      assert_equal(base_url, actual_base)\n      assert_equal('?', actual[base_url.length].chr)\n      query = actual[base_url.length+1..-1]\n      assert_equal({'openid.mode'=>['error'],\n                    'openid.error'=>['unit test'],\n                    'openid.ns'=>[OPENID1_NS],\n                    },\n                   CGI.parse(query))\n    end\n\n\n  end\n\n  class OpenID2MessageTest < Minitest::Test\n    include TestUtil\n\n    def setup\n      @m = Message.from_post_args({'openid.mode'=>'error',\n                                          'openid.error'=>'unit test',\n                                          'openid.ns'=>OPENID2_NS})\n      @m.set_arg(BARE_NS, 'xey', 'value')\n    end\n\n    def test_to_args_fails\n      assert_raises(ArgumentError) {\n        @m.to_args\n      }\n    end\n\n    def test_fix_ns_non_string\n      # Using has_key to invoke _fix_ns since _fix_ns should be private\n      assert_raises(ArgumentError) {\n        @m.has_key?(:non_string_namespace, \"key\")\n      }\n    end\n\n    def test_fix_ns_non_uri\n      # Using has_key to invoke _fix_ns since _fix_ns should be private\n      assert_log_matches(/identifiers SHOULD be URIs/) {\n        @m.has_key?(\"foo\", \"key\")\n      }\n    end\n\n    def test_fix_ns_sreg_literal\n      # Using has_key to invoke _fix_ns since _fix_ns should be private\n      assert_log_matches(/identifiers SHOULD be URIs/, /instead of \"sreg\"/) {\n        @m.has_key?(\"sreg\", \"key\")\n      }\n    end\n\n    def test_copy\n      n = @m.copy\n      assert_equal(@m, n)\n    end\n\n    def test_to_post_args\n      assert_equal({'openid.mode' => 'error',\n                     'openid.error' => 'unit test',\n                     'openid.ns' => OPENID2_NS,\n                     'xey' => 'value',\n                   }, @m.to_post_args)\n    end\n\n    def test_to_post_args_ns\n      invalid_ns = 'http://invalid/'\n      @m.namespaces.add_alias(invalid_ns, 'foos')\n      @m.set_arg(invalid_ns, 'ball', 'awesome')\n      assert_equal({'openid.mode' => 'error',\n                     'openid.error' => 'unit test',\n                     'openid.ns' => OPENID2_NS,\n                     'openid.ns.foos' => invalid_ns,\n                     'openid.foos.ball' => 'awesome',\n                     'xey' => 'value',\n                   }, @m.to_post_args)\n    end\n\n    def test_to_args\n      @m.del_arg(BARE_NS, 'xey')\n      assert_equal({'mode' => 'error',\n                   'error' => 'unit test',\n                   'ns' => OPENID2_NS},\n                   @m.to_args)\n    end\n\n    def test_to_kvform\n      @m.del_arg(BARE_NS, 'xey')\n      assert_equal(\"error:unit test\\nmode:error\\nns:#{OPENID2_NS}\\n\",\n                   @m.to_kvform)\n    end\n\n    def _test_urlencoded(s)\n      expected_list = [\"openid.error=unit+test\",\n                       \"openid.mode=error\",\n                       \"openid.ns=#{CGI.escape(OPENID2_NS)}\",\n                       \"xey=value\"]\n      # Hard to do this with string comparison since the mapping doesn't\n      # preserve order.\n      encoded_list = s.split('&')\n      encoded_list.sort!\n      assert_equal(expected_list, encoded_list)\n    end\n\n    def test_to_urlencoded\n      _test_urlencoded(@m.to_url_encoded)\n    end\n\n    def test_to_url\n      base_url = 'http://base.url/'\n      actual = @m.to_url(base_url)\n      actual_base = actual[0...base_url.length]\n      assert_equal(base_url, actual_base)\n      assert_equal('?', actual[base_url.length].chr)\n      query = actual[base_url.length+1..-1]\n      _test_urlencoded(query)\n    end\n\n    def test_get_openid\n      assert_equal(OPENID2_NS, @m.get_openid_namespace)\n    end\n\n    def test_get_key_openid\n      assert_equal('openid.mode', @m.get_key(OPENID2_NS, 'mode'))\n    end\n\n    def test_get_key_bare\n      assert_equal('mode', @m.get_key(BARE_NS, 'mode'))\n    end\n\n    def test_get_key_ns1\n      assert_nil(@m.get_key(OPENID1_NS, 'mode'))\n    end\n\n    def test_get_key_ns2\n      assert_equal('openid.mode', @m.get_key(OPENID2_NS, 'mode'))\n    end\n\n    def test_get_key_ns3\n      assert_nil(@m.get_key('urn:xxx', 'mode'))\n    end\n\n    def test_has_key_openid\n      assert_equal(true, @m.has_key?(OPENID_NS,'mode'))\n    end\n\n    def test_has_key_bare\n      assert_equal(false, @m.has_key?(BARE_NS,'mode'))\n    end\n\n    def test_has_key_ns1\n      assert_equal(false, @m.has_key?(OPENID1_NS,'mode'))\n    end\n\n    def test_has_key_ns2\n      assert_equal(true, @m.has_key?(OPENID2_NS,'mode'))\n    end\n\n    def test_has_key_ns3\n      assert_equal(false, @m.has_key?('urn:xxx','mode'))\n    end\n\n    # XXX - getArgTest\n    def test_get_arg_openid\n      assert_equal('error', @m.get_arg(OPENID_NS,'mode'))\n    end\n\n    def test_get_arg_bare\n      assert_nil(@m.get_arg(BARE_NS,'mode'))\n    end\n\n    def test_get_arg_ns1\n      assert_nil(@m.get_arg(OPENID1_NS,'mode'))\n    end\n\n    def test_get_arg_ns2\n      assert_equal('error', @m.get_arg(OPENID2_NS,'mode'))\n    end\n\n    def test_get_arg_ns3\n      assert_nil(@m.get_arg('urn:bananastand','mode'))\n    end\n\n    def test_get_args_openid\n      assert_equal({'mode'=>'error','error'=>'unit test'},\n                   @m.get_args(OPENID_NS))\n    end\n\n    def test_get_args_bare\n      assert_equal({'xey'=>'value'},\n                   @m.get_args(BARE_NS))\n    end\n\n    def test_get_args_ns1\n      assert_equal({},\n                   @m.get_args(OPENID1_NS))\n    end\n\n    def test_get_args_ns2\n      assert_equal({'mode'=>'error','error'=>'unit test'},\n                   @m.get_args(OPENID2_NS))\n    end\n\n    def test_get_args_ns3\n      assert_equal({},\n                   @m.get_args('urn:loose seal'))\n    end\n\n    def _test_update_args_ns(ns, before=nil)\n      before = {} unless before\n      update_args = {'aa'=>'bb','cc'=>'dd'}\n\n      assert_equal(before, @m.get_args(ns))\n      @m.update_args(ns, update_args)\n      after = before.dup\n      after.update(update_args)\n      assert_equal(after, @m.get_args(ns))\n    end\n\n    def test_update_args_openid\n      _test_update_args_ns(OPENID_NS, {'mode'=>'error','error'=>'unit test'})\n    end\n\n    def test_update_args_bare\n      _test_update_args_ns(BARE_NS, {'xey'=>'value'})\n    end\n\n    def test_update_args_ns1\n      _test_update_args_ns(OPENID1_NS)\n    end\n\n    def test_update_args_ns2\n      _test_update_args_ns(OPENID2_NS, {'mode'=>'error','error'=>'unit test'})\n    end\n\n    def test_update_args_ns3\n      _test_update_args_ns('urn:sven')\n    end\n\n    def _test_set_arg_ns(ns)\n      key = \"logan's\"\n      value = \"run\"\n      assert_nil(@m.get_arg(ns,key))\n      @m.set_arg(ns, key, value)\n      assert_equal(value, @m.get_arg(ns,key))\n    end\n\n    def test_set_arg_openid; _test_set_arg_ns(OPENID_NS); end\n    def test_set_arg_bare; _test_set_arg_ns(BARE_NS); end\n    def test_set_arg_ns1; _test_set_arg_ns(OPENID1_NS); end\n    def test_set_arg_ns2; _test_set_arg_ns(OPENID2_NS); end\n    def test_set_arg_ns3; _test_set_arg_ns('urn:g'); end\n\n    def test_bad_alias\n      # Make sure dotted aliases and OpenID protocol fields are not allowed\n      # as namespace aliases.\n\n      fields = OPENID_PROTOCOL_FIELDS + ['dotted.alias']\n\n      fields.each { |f|\n        args = {\"openid.ns.#{f}\" => \"blah#{f}\",\n          \"openid.#{f}.foo\" => \"test#{f}\"}\n\n        # .fromPostArgs covers .fromPostArgs, .fromOpenIDArgs,\n        # ._fromOpenIDArgs, and .fromOpenIDArgs (since it calls\n        # .fromPostArgs).\n        assert_raises(AssertionError) {\n          Message.from_post_args(args)\n        }\n      }\n    end\n\n    def test_from_post_args\n      msg = Message.from_post_args({'foos' => 'ball'})\n      assert_equal('ball', msg.get_arg(BARE_NS, 'foos'))\n    end\n\n    def _test_del_arg_ns(ns)\n      key = 'no'\n      value = 'socks'\n      assert_nil(@m.get_arg(ns, key))\n      @m.set_arg(ns, key, value)\n      assert_equal(value, @m.get_arg(ns, key))\n      @m.del_arg(ns, key)\n      assert_nil(@m.get_arg(ns, key))\n    end\n\n    def test_del_arg_openid; _test_del_arg_ns(OPENID_NS); end\n    def test_del_arg_bare; _test_del_arg_ns(BARE_NS); end\n    def test_del_arg_ns1; _test_del_arg_ns(OPENID1_NS); end\n    def test_del_arg_ns2; _test_del_arg_ns(OPENID2_NS); end\n    def test_del_arg_ns3; _test_del_arg_ns('urn:tofu'); end\n\n    def test_overwrite_extension_arg\n      ns = 'urn:unittest_extension'\n      key = 'mykey'\n      value_1 = 'value_1'\n      value_2 = 'value_2'\n\n      @m.set_arg(ns, key, value_1)\n      assert_equal(value_1, @m.get_arg(ns, key))\n      @m.set_arg(ns, key, value_2)\n      assert_equal(value_2, @m.get_arg(ns, key))\n    end\n\n    def test_argList\n      assert_raises(ArgumentError) {\n        Message.from_post_args({'arg' => [1, 2, 3]})\n      }\n    end\n\n    def test_isOpenID1\n      assert_equal(false, @m.is_openid1)\n    end\n\n    def test_isOpenID2\n      assert_equal(true, @m.is_openid2)\n    end\n  end\n\n  class MessageTest < Minitest::Test\n    def setup\n      @postargs = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => 'http://bogus.example.invalid:port/',\n        'openid.assoc_handle' => 'FLUB',\n        'openid.return_to' => 'Neverland',\n        'openid.ax.value.fullname' => \"Bob&Smith'\"\n      }\n\n      @action_url = 'scheme://host:port/path?query'\n\n      @form_tag_attrs = {\n        'company' => 'janrain',\n        'class' => 'fancyCSS',\n      }\n\n      @submit_text = 'GO!'\n\n      ### Expected data regardless of input\n\n      @required_form_attrs = {\n        'accept-charset' => 'UTF-8',\n        'enctype' => 'application/x-www-form-urlencoded',\n        'method' => 'post',\n      }\n    end\n\n    def _checkForm(html, message_, action_url,\n                   form_tag_attrs, submit_text)\n      @xml = REXML::Document.new(html)\n\n      # Get root element\n      form = @xml.root\n\n      # Check required form attributes\n      @required_form_attrs.each { |k, v|\n        assert(form.attributes[k] == v,\n               \"Expected '#{v}' for required form attribute '#{k}', got '#{form.attributes[k]}'\")\n      }\n\n      # Check extra form attributes\n      @form_tag_attrs.each { |k, v|\n        # Skip attributes that already passed the required attribute\n        # check, since they should be ignored by the form generation\n        # code.\n        if @required_form_attrs.include?(k)\n          continue\n        end\n\n        assert(form.attributes[k] == v,\n               \"Form attribute '#{k}' should be '#{v}', found '#{form.attributes[k]}'\")\n\n        # Check hidden fields against post args\n        hiddens = []\n        form.each { |e|\n          if (e.is_a?(REXML::Element)) and\n              (e.name.upcase() == 'INPUT') and\n              (e.attributes['type'].upcase() == 'HIDDEN')\n            # For each post arg, make sure there is a hidden with that\n            # value.  Make sure there are no other hiddens.\n            hiddens += [e]\n          end\n        }\n\n        message_.to_post_args().each { |name, value|\n          success = false\n\n          hiddens.each { |e|\n            if e.attributes['name'] == name\n              assert(e.attributes['value'] == value,\n                     \"Expected value of hidden input '#{e.attributes['name']}' \" +\n                     \"to be '#{value}', got '#{e.attributes['value']}'\")\n              success = true\n              break\n            end\n          }\n\n          if !success\n            flunk \"Post arg '#{name}' not found in form\"\n          end\n        }\n\n        hiddens.each { |e|\n          assert(message_.to_post_args().keys().include?(e.attributes['name']),\n                 \"Form element for '#{e.attributes['name']}' not in \" +\n                 \"original message\")\n        }\n\n        # Check action URL\n        assert(form.attributes['action'] == action_url,\n               \"Expected form 'action' to be '#{action_url}', got '#{form.attributes['action']}'\")\n\n        # Check submit text\n        submits = []\n        form.each { |e|\n          if (e.is_a?(REXML::Element)) and\n              (e.name.upcase() == 'INPUT') and\n              e.attributes['type'].upcase() == 'SUBMIT'\n            submits += [e]\n          end\n        }\n\n        assert(submits.length == 1,\n               \"Expected only one 'input' with type = 'submit', got #{submits.length}\")\n\n        assert(submits[0].attributes['value'] == submit_text,\n               \"Expected submit value to be '#{submit_text}', \" +\n               \"got '#{submits[0].attributes['value']}'\")\n      }\n\n    end\n\n    def test_toFormMarkup\n      m = Message.from_post_args(@postargs)\n      html = m.to_form_markup(@action_url, @form_tag_attrs,\n                              @submit_text)\n      _checkForm(html, m, @action_url,\n                 @form_tag_attrs, @submit_text)\n    end\n\n    def test_overrideMethod\n      # Be sure that caller cannot change form method to GET.\n      m = Message.from_post_args(@postargs)\n\n      tag_attrs = @form_tag_attrs.clone\n      tag_attrs['method'] = 'GET'\n\n      html = m.to_form_markup(@action_url, @form_tag_attrs,\n                              @submit_text)\n      _checkForm(html, m, @action_url,\n                 @form_tag_attrs, @submit_text)\n    end\n\n    def test_overrideRequired\n      # Be sure that caller CANNOT change the form charset for\n      # encoding type.\n      m = Message.from_post_args(@postargs)\n\n      tag_attrs = @form_tag_attrs.clone\n      tag_attrs['accept-charset'] = 'UCS4'\n      tag_attrs['enctype'] = 'invalid/x-broken'\n\n      html = m.to_form_markup(@action_url, tag_attrs,\n                              @submit_text)\n      _checkForm(html, m, @action_url,\n                 tag_attrs, @submit_text)\n    end\n  end\n\n  class NamespaceMapTestCase < Minitest::Test\n\n    def test_onealias\n      nsm = NamespaceMap.new\n      uri = 'http://example.com/foo'\n      _alias = 'foo'\n      nsm.add_alias(uri, _alias)\n      assert_equal(uri, nsm.get_namespace_uri(_alias))\n      assert_equal(_alias, nsm.get_alias(uri))\n    end\n\n\n    def test_iteration\n      nsm = NamespaceMap.new\n      uripat = \"http://example.com/foo%i\"\n      nsm.add(uripat % 0)\n\n      (1..23).each { |i|\n        assert_equal(false, nsm.member?(uripat % i))\n        nsm.add(uripat % i)\n      }\n      nsm.each { |uri, _alias|\n        assert_equal(uri[22..-1], _alias[3..-1])\n      }\n\n      nsm = NamespaceMap.new\n      alias_ = 'bogus'\n      uri = 'urn:bogus'\n\n      nsm.add_alias(uri, alias_)\n\n      assert_equal(nsm.aliases(), [alias_])\n      assert_equal(nsm.namespace_uris(), [uri])\n    end\n\n    def test_register_default_alias\n      invalid_ns = 'http://invalid/'\n      alias_ = 'invalid'\n      Message.register_namespace_alias(invalid_ns, alias_)\n      # Doing it again doesn't raise an exception\n      Message.register_namespace_alias(invalid_ns, alias_)\n\n      # Once it's registered, you can't register it again\n      assert_raises(NamespaceAliasRegistrationError) {\n        Message.register_namespace_alias(invalid_ns, 'another_alias')\n      }\n\n      # Once it's registered, you can't register another URL with that alias\n      assert_raises(NamespaceAliasRegistrationError) {\n        Message.register_namespace_alias('http://janrain.com/', alias_)\n      }\n\n      # It gets used automatically by the Message class:\n      msg = Message.from_openid_args({'invalid.stuff' => 'things'})\n      assert(msg.is_openid1)\n      assert_equal(alias_, msg.namespaces.get_alias(invalid_ns))\n      assert_equal(invalid_ns, msg.namespaces.get_namespace_uri(alias_))\n    end\n\n    def test_alias_defined_twice\n      nsm = NamespaceMap.new\n      uri = 'urn:bogus'\n\n      nsm.add_alias(uri, 'foos')\n      assert_raises(IndexError) {\n        nsm.add_alias(uri, 'ball')\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_nonce.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/store/nonce'\n\nmodule OpenID\n  class NonceTestCase < Minitest::Test\n\n     NONCE_RE = /\\A\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ/\n\n    def test_mk_nonce\n      nonce = Nonce::mk_nonce\n      assert(nonce.match(NONCE_RE))\n      assert(nonce.size == 26)\n    end\n\n    def test_mk_nonce_time\n      nonce = Nonce::mk_nonce(0)\n      assert(nonce.match(NONCE_RE))\n      assert(nonce.size == 26)\n      assert(nonce.match(/^1970-01-01T00:00:00Z/))\n    end\n\n    def test_split\n      s = '1970-01-01T00:00:00Z'\n      expected_t = 0\n      expected_salt = ''\n      actual_t, actual_salt = Nonce::split_nonce(s)\n      assert_equal(expected_t, actual_t)\n      assert_equal(expected_salt, actual_salt)\n    end\n\n    def test_mk_split\n      t = 42\n      nonce_str = Nonce::mk_nonce(t)\n      assert(nonce_str.match(NONCE_RE))\n      at, salt = Nonce::split_nonce(nonce_str)\n      assert_equal(6, salt.size)\n      assert_equal(t, at)\n    end\n\n    def test_bad_split\n      cases = [\n        '',\n        '1970-01-01T00:00:00+1:00',\n        '1969-01-01T00:00:00Z',\n        '1970-00-01T00:00:00Z',\n        '1970.01-01T00:00:00Z',\n        'Thu Sep  7 13:29:31 PDT 2006',\n        'monkeys',\n        ]\n      cases.each{|c|\n        assert_raises(ArgumentError, c.inspect) { Nonce::split_nonce(c) }\n      }\n    end\n\n    def test_check_timestamp\n      cases = [\n        # exact, no allowed skew\n        ['1970-01-01T00:00:00Z', 0, 0, true],\n\n        # exact, large skew\n        ['1970-01-01T00:00:00Z', 1000, 0, true],\n\n        # no allowed skew, one second old\n        ['1970-01-01T00:00:00Z', 0, 1, false],\n\n        # many seconds old, outside of skew\n        ['1970-01-01T00:00:00Z', 10, 50, false],\n\n        # one second old, one second skew allowed\n        ['1970-01-01T00:00:00Z', 1, 1, true],\n\n        # One second in the future, one second skew allowed\n        ['1970-01-01T00:00:02Z', 1, 1, true],\n\n        # two seconds in the future, one second skew allowed\n        ['1970-01-01T00:00:02Z', 1, 0, false],\n\n        # malformed nonce string\n        ['monkeys', 0, 0, false],\n      ]\n      \n      cases.each{|c|\n        (nonce_str, allowed_skew, now, expected) = c\n        actual = Nonce::check_timestamp(nonce_str, allowed_skew, now)\n        assert_equal(expected, actual, c.inspect)\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_oauth.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/extensions/oauth'\nrequire 'openid/message'\nrequire 'openid/server'\nrequire 'openid/consumer/responses'\nrequire 'openid/consumer/discovery'\n\nmodule OpenID\n  module OAuthTest\n    class OAuthRequestTestCase < Minitest::Test\n      def setup\n        @req = OAuth::Request.new\n      end\n\n      def test_construct\n        assert_nil(@req.consumer)\n        assert_nil(@req.scope)\n        assert_equal('oauth', @req.ns_alias)\n\n        req2 = OAuth::Request.new(\"CONSUMER\",\"http://sample.com/some_scope\")\n        assert_equal(\"CONSUMER\",req2.consumer)\n        assert_equal(\"http://sample.com/some_scope\",req2.scope)\n      end\n\n      def test_add_consumer\n        @req.consumer=\"CONSUMER\"\n        assert_equal(\"CONSUMER\",@req.consumer)\n      end\n\n      def test_add_scope\n        @req.scope=\"http://sample.com/some_scope\"\n        assert_equal(\"http://sample.com/some_scope\",@req.scope)\n      end\n\n      def test_get_extension_args\n        assert_equal({}, @req.get_extension_args)\n        @req.consumer=\"CONSUMER\"\n        assert_equal({'consumer' => 'CONSUMER'}, @req.get_extension_args)\n        @req.scope=\"http://sample.com/some_scope\"\n        assert_equal({'consumer' => 'CONSUMER', 'scope' => 'http://sample.com/some_scope'}, @req.get_extension_args)\n      end\n\n      def test_parse_extension_args\n        args = {'consumer' => 'CONSUMER', 'scope' => 'http://sample.com/some_scope'}\n        @req.parse_extension_args(args)\n        assert_equal(\"CONSUMER\",@req.consumer)\n        assert_equal(\"http://sample.com/some_scope\",@req.scope)\n      end\n\n      def test_parse_extension_args_empty\n        @req.parse_extension_args({})\n        assert_nil( @req.consumer )\n        assert_nil( @req.scope )\n      end\n\n      def test_from_openid_request\n        openid_req_msg = Message.from_openid_args({\n          'mode' => 'checkid_setup',\n          'ns' => OPENID2_NS,\n          'ns.oauth' => OAuth::NS_URI,\n          'oauth.consumer' => 'CONSUMER',\n          'oauth.scope' => \"http://sample.com/some_scope\"\n          })\n        oid_req = Server::OpenIDRequest.new\n        oid_req.message = openid_req_msg\n        req = OAuth::Request.from_openid_request(oid_req)\n        assert_equal(\"CONSUMER\",req.consumer)\n        assert_equal(\"http://sample.com/some_scope\",req.scope)\n      end\n\n      def test_from_openid_request_no_oauth\n        message = Message.new\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = message\n        oauth_req = OAuth::Request.from_openid_request(openid_req)\n        assert(oauth_req.nil?)\n      end\n\n    end\n\n    class DummySuccessResponse\n      attr_accessor :message\n\n      def initialize(message, signed_stuff)\n        @message = message\n        @signed_stuff = signed_stuff\n      end\n\n      def get_signed_ns(ns_uri)\n        return @signed_stuff\n      end\n\n    end\n\n    class OAuthResponseTestCase < Minitest::Test\n      def setup\n        @req = OAuth::Response.new\n      end\n\n      def test_construct\n        assert_nil(@req.request_token)\n        assert_nil(@req.scope)\n\n        req2 = OAuth::Response.new(\"REQUESTTOKEN\",\"http://sample.com/some_scope\")\n        assert_equal(\"REQUESTTOKEN\",req2.request_token)\n        assert_equal(\"http://sample.com/some_scope\",req2.scope)\n      end\n\n      def test_add_request_token\n        @req.request_token=\"REQUESTTOKEN\"\n        assert_equal(\"REQUESTTOKEN\",@req.request_token)\n      end\n\n      def test_add_scope\n        @req.scope=\"http://sample.com/some_scope\"\n        assert_equal(\"http://sample.com/some_scope\",@req.scope)\n      end\n\n      def test_get_extension_args\n        assert_equal({}, @req.get_extension_args)\n        @req.request_token=\"REQUESTTOKEN\"\n        assert_equal({'request_token' => 'REQUESTTOKEN'}, @req.get_extension_args)\n        @req.scope=\"http://sample.com/some_scope\"\n        assert_equal({'request_token' => 'REQUESTTOKEN', 'scope' => 'http://sample.com/some_scope'}, @req.get_extension_args)\n      end\n\n      def test_parse_extension_args\n        args = {'request_token' => 'REQUESTTOKEN', 'scope' => 'http://sample.com/some_scope'}\n        @req.parse_extension_args(args)\n        assert_equal(\"REQUESTTOKEN\",@req.request_token)\n        assert_equal(\"http://sample.com/some_scope\",@req.scope)\n      end\n\n      def test_parse_extension_args_empty\n        @req.parse_extension_args({})\n        assert_nil( @req.request_token )\n        assert_nil( @req.scope )\n      end\n\n      def test_from_success_response\n\n        openid_req_msg = Message.from_openid_args({\n          'mode' => 'id_res',\n          'ns' => OPENID2_NS,\n          'ns.oauth' => OAuth::NS_URI,\n          'oauth.request_token' => 'REQUESTTOKEN',\n          'oauth.scope' => \"http://sample.com/some_scope\"\n        })\n        signed_stuff = {\n          'request_token' => 'REQUESTTOKEN',\n          'scope' => \"http://sample.com/some_scope\"\n        }\n        oid_req = DummySuccessResponse.new(openid_req_msg, signed_stuff)\n        req = OAuth::Response.from_success_response(oid_req)\n        assert_equal(\"REQUESTTOKEN\",req.request_token)\n        assert_equal(\"http://sample.com/some_scope\",req.scope)\n      end\n\n      def test_from_success_response_unsigned\n        openid_req_msg = Message.from_openid_args({\n          'mode' => 'id_res',\n          'ns' => OPENID2_NS,\n          'ns.oauth' => OAuth::NS_URI,\n          'oauth.request_token' => 'REQUESTTOKEN',\n          'oauth.scope' => \"http://sample.com/some_scope\"\n          })\n        signed_stuff = {}\n        endpoint = OpenIDServiceEndpoint.new\n        oid_req = Consumer::SuccessResponse.new(endpoint, openid_req_msg, signed_stuff)\n        req = OAuth::Response.from_success_response(oid_req)\n        assert(req.nil?, req.inspect)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_openid_yadis.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/consumer/discovery'\nrequire 'openid/yadis/services'\n\nmodule OpenID\n\n  XRDS_BOILERPLATE = <<EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xrds:XRDS xmlns:xrds=\"xri://$xrds\"\n           xmlns=\"xri://$xrd*($v*2.0)\"\n           xmlns:openid=\"http://openid.net/xmlns/1.0\">\n    <XRD>\n%s\n    </XRD>\n</xrds:XRDS>\nEOF\n\n  def self.mkXRDS(services)\n    return sprintf(XRDS_BOILERPLATE, services)\n  end\n\n  def self.mkService(uris=nil, type_uris=nil, local_id=nil, dent=\"        \")\n    chunks = [dent, \"<Service>\\n\"]\n    dent2 = dent + \"    \"\n    if type_uris\n      type_uris.each { |type_uri|\n        chunks += [dent2 + \"<Type>\", type_uri, \"</Type>\\n\"]\n      }\n    end\n\n    if uris\n      uris.each { |uri|\n        if uri.is_a?(Array)\n          uri, prio = uri\n        else\n          prio = nil\n        end\n\n        chunks += [dent2, \"<URI\"]\n        if !prio.nil?\n          chunks += [\" priority='\", str(prio), \"'\"]\n        end\n        chunks += [\">\", uri, \"</URI>\\n\"]\n      }\n    end\n\n    if local_id\n      chunks += [dent2, \"<openid:Delegate>\", local_id, \"</openid:Delegate>\\n\"]\n    end\n\n    chunks += [dent, \"</Service>\\n\"]\n\n    return chunks.join(\"\")\n  end\n\n  # Different sets of server URLs for use in the URI tag\n  SERVER_URL_OPTIONS = [\n                        [], # This case should not generate an endpoint object\n                        ['http://server.url/'],\n                        ['https://server.url/'],\n                        ['https://server.url/', 'http://server.url/'],\n                        ['https://server.url/',\n                         'http://server.url/',\n                         'http://example.server.url/'],\n                       ]\n\n  # Used for generating test data\n  def OpenID.subsets(l)\n    subsets_list = [[]]\n    l.each { |x|\n      subsets_list += subsets_list.collect { |t| [x] + t }\n    }\n\n    return subsets_list\n  end\n\n  # A couple of example extension type URIs. These are not at all\n  # official, but are just here for testing.\n  EXT_TYPES = [\n               'http://janrain.com/extension/blah',\n               'http://openid.net/sreg/1.0',\n              ]\n\n  # Range of valid Delegate tag values for generating test data\n  LOCAL_ID_OPTIONS = [\n                      nil,\n                      'http://vanity.domain/',\n                      'https://somewhere/yadis/',\n                     ]\n\n  class OpenIDYadisTest\n    def initialize(uris, type_uris, local_id)\n      super()\n      @uris = uris\n      @type_uris = type_uris\n      @local_id = local_id\n\n      @yadis_url = 'http://unit.test/'\n\n      # Create an XRDS document to parse\n      services = OpenID.mkService(@uris,\n                                  @type_uris,\n                                  @local_id)\n      @xrds = OpenID.mkXRDS(services)\n    end\n\n    def runTest(testcase)\n      # Parse into endpoint objects that we will check\n      endpoints = Yadis.apply_filter(@yadis_url, @xrds, OpenIDServiceEndpoint)\n\n      # make sure there are the same number of endpoints as URIs. This\n      # assumes that the type_uris contains at least one OpenID type.\n      testcase.assert_equal(@uris.length, endpoints.length)\n\n      # So that we can check equality on the endpoint types\n      type_uris = @type_uris.dup\n      type_uris.sort!\n\n      seen_uris = []\n      endpoints.each { |endpoint|\n        seen_uris << endpoint.server_url\n\n        # All endpoints will have same yadis_url\n        testcase.assert_equal(@yadis_url, endpoint.claimed_id)\n\n        # and local_id\n        testcase.assert_equal(@local_id, endpoint.local_id)\n\n        # and types\n        actual_types = endpoint.type_uris.dup\n        actual_types.sort!\n        testcase.assert_equal(type_uris, actual_types, actual_types.inspect)\n      }\n\n      # So that they will compare equal, because we don't care what\n      # order they are in\n      seen_uris.sort!\n      uris = @uris.dup\n      uris.sort!\n\n      # Make sure we saw all URIs, and saw each one once\n      testcase.assert_equal(uris, seen_uris)\n    end\n  end\n\n  class OpenIDYadisTests < Minitest::Test\n    def test_openid_yadis\n      data = []\n\n      # All valid combinations of Type tags that should produce an\n      # OpenID endpoint\n      type_uri_options = []\n\n      OpenID.subsets([OPENID_1_0_TYPE, OPENID_1_1_TYPE]).each { |ts|\n        OpenID.subsets(EXT_TYPES).each { |exts|\n          if !ts.empty?\n            type_uri_options << exts + ts\n          end\n        }\n      }\n\n      # All combinations of valid URIs, Type URIs and Delegate tags\n      SERVER_URL_OPTIONS.each { |uris|\n        type_uri_options.each { |type_uris|\n          LOCAL_ID_OPTIONS.each { |local_id|\n            data << [uris, type_uris, local_id]\n          }\n        }\n      }\n\n      data.each { |args|\n        t = OpenIDYadisTest.new(*args)\n        t.runTest(self)\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_pape.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/extensions/pape'\nrequire 'openid/message'\nrequire 'openid/server'\nrequire 'openid/consumer'\n\nmodule OpenID\n  module PAPETest\n    class PapeRequestTestCase < Minitest::Test\n      def setup\n        @req = PAPE::Request.new\n      end\n\n      def test_construct\n        assert_equal([], @req.preferred_auth_policies)\n        assert_nil(@req.max_auth_age)\n        assert_equal('pape', @req.ns_alias)\n\n        req2 = PAPE::Request.new([PAPE::AUTH_MULTI_FACTOR], 1000)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], req2.preferred_auth_policies)\n        assert_equal(1000, req2.max_auth_age)\n      end\n\n      def test_add_policy_uri\n        assert_equal([], @req.preferred_auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.preferred_auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.preferred_auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_PHISHING_RESISTANT)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.preferred_auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.preferred_auth_policies)\n      end\n\n      def test_get_extension_args\n        assert_equal({'preferred_auth_policies' => ''}, @req.get_extension_args)\n        @req.add_policy_uri('http://uri')\n        assert_equal({'preferred_auth_policies' => 'http://uri'}, @req.get_extension_args)\n        @req.add_policy_uri('http://zig')\n        assert_equal({'preferred_auth_policies' => 'http://uri http://zig'}, @req.get_extension_args)\n        @req.max_auth_age = 789\n        assert_equal({'preferred_auth_policies' => 'http://uri http://zig', 'max_auth_age' => '789'}, @req.get_extension_args)\n      end\n\n      def test_parse_extension_args\n        args = {'preferred_auth_policies' => 'http://foo http://bar',\n                'max_auth_age' => '9'}\n        @req.parse_extension_args(args)\n        assert_equal(9, @req.max_auth_age)\n        assert_equal(['http://foo','http://bar'], @req.preferred_auth_policies)\n      end\n\n      def test_parse_extension_args_empty\n        @req.parse_extension_args({})\n        assert_nil(@req.max_auth_age)\n        assert_equal([], @req.preferred_auth_policies)\n      end\n\n      def test_from_openid_request\n        openid_req_msg = Message.from_openid_args({\n          'mode' => 'checkid_setup',\n          'ns' => OPENID2_NS,\n          'ns.pape' => PAPE::NS_URI,\n          'pape.preferred_auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),\n          'pape.max_auth_age' => '5476'\n          })\n        oid_req = Server::OpenIDRequest.new\n        oid_req.message = openid_req_msg\n        req = PAPE::Request.from_openid_request(oid_req)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], req.preferred_auth_policies)\n        assert_equal(5476, req.max_auth_age)\n      end\n\n      def test_from_openid_request_no_pape\n        message = Message.new\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = message\n        pape_req = PAPE::Request.from_openid_request(openid_req)\n        assert(pape_req.nil?)\n      end\n\n      def test_preferred_types\n        @req.add_policy_uri(PAPE::AUTH_PHISHING_RESISTANT)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        pt = @req.preferred_types([PAPE::AUTH_MULTI_FACTOR,\n                                   PAPE::AUTH_MULTI_FACTOR_PHYSICAL])\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], pt)\n      end\n    end\n\n    class DummySuccessResponse\n      attr_accessor :message\n\n      def initialize(message, signed_stuff)\n        @message = message\n        @signed_stuff = signed_stuff\n      end\n\n      def get_signed_ns(ns_uri)\n        return @signed_stuff\n      end\n\n    end\n\n    class PapeResponseTestCase < Minitest::Test\n      def setup\n        @req = PAPE::Response.new\n      end\n\n      def test_construct\n        assert_equal([], @req.auth_policies)\n        assert_nil(@req.auth_time)\n        assert_equal('pape', @req.ns_alias)\n        assert_nil(@req.nist_auth_level)\n\n        req2 = PAPE::Response.new([PAPE::AUTH_MULTI_FACTOR], \"1983-11-05T12:30:24Z\", 3)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], req2.auth_policies)\n        assert_equal(\"1983-11-05T12:30:24Z\", req2.auth_time)\n        assert_equal(3, req2.nist_auth_level)\n      end\n\n      def test_add_policy_uri\n        assert_equal([], @req.auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR], @req.auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_PHISHING_RESISTANT)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.auth_policies)\n        @req.add_policy_uri(PAPE::AUTH_MULTI_FACTOR)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], @req.auth_policies)\n      end\n\n      def test_get_extension_args\n        assert_equal({'auth_policies' => 'none'}, @req.get_extension_args)\n        @req.add_policy_uri('http://uri')\n        assert_equal({'auth_policies' => 'http://uri'}, @req.get_extension_args)\n        @req.add_policy_uri('http://zig')\n        assert_equal({'auth_policies' => 'http://uri http://zig'}, @req.get_extension_args)\n        @req.auth_time =  \"1983-11-05T12:30:24Z\"\n        assert_equal({'auth_policies' => 'http://uri http://zig', 'auth_time' => \"1983-11-05T12:30:24Z\"}, @req.get_extension_args)\n        @req.nist_auth_level = 3\n        assert_equal({'auth_policies' => 'http://uri http://zig', 'auth_time' => \"1983-11-05T12:30:24Z\", 'nist_auth_level' => '3'}, @req.get_extension_args)\n      end\n\n      def test_get_extension_args_error_auth_age\n        @req.auth_time = \"the beginning of time\"\n        assert_raises(ArgumentError) { @req.get_extension_args }\n      end\n\n      def test_get_extension_args_error_nist_auth_level\n        @req.nist_auth_level = \"high as a kite\"\n        assert_raises(ArgumentError) { @req.get_extension_args }\n        @req.nist_auth_level = 5\n        assert_raises(ArgumentError) { @req.get_extension_args }\n        @req.nist_auth_level = -1\n        assert_raises(ArgumentError) { @req.get_extension_args }\n      end\n\n      def test_parse_extension_args\n        args = {'auth_policies' => 'http://foo http://bar',\n                'auth_time' => '1983-11-05T12:30:24Z'}\n        @req.parse_extension_args(args)\n        assert_equal('1983-11-05T12:30:24Z', @req.auth_time)\n        assert_equal(['http://foo','http://bar'], @req.auth_policies)\n      end\n\n      def test_parse_extension_args_empty\n        @req.parse_extension_args({})\n        assert_nil(@req.auth_time)\n        assert_equal([], @req.auth_policies)\n      end\n\n      def test_parse_extension_args_strict_bogus1\n        args = {'auth_policies' => 'http://foo http://bar',\n                'auth_time' => 'this one time'}\n        assert_raises(ArgumentError) {\n          @req.parse_extension_args(args, true)\n        }\n      end\n\n      def test_parse_extension_args_strict_bogus2\n        args = {'auth_policies' => 'http://foo http://bar',\n                'auth_time' => '1983-11-05T12:30:24Z',\n                'nist_auth_level' => 'some'}\n        assert_raises(ArgumentError) {\n          @req.parse_extension_args(args, true)\n        }\n      end\n\n      def test_parse_extension_args_strict_good\n        args = {'auth_policies' => 'http://foo http://bar',\n                'auth_time' => '2007-10-11T05:25:18Z',\n                'nist_auth_level' => '0'}\n        @req.parse_extension_args(args, true)\n        assert_equal(['http://foo','http://bar'], @req.auth_policies)\n        assert_equal('2007-10-11T05:25:18Z', @req.auth_time)\n        assert_equal(0, @req.nist_auth_level)\n      end\n\n      def test_parse_extension_args_nostrict_bogus\n        args = {'auth_policies' => 'http://foo http://bar',\n                'auth_time' => 'some time ago',\n                'nist_auth_level' => 'some'}\n        @req.parse_extension_args(args)\n        assert_equal(['http://foo','http://bar'], @req.auth_policies)\n        assert_nil(@req.auth_time)\n        assert_nil(@req.nist_auth_level)\n      end\n\n\n      def test_from_success_response\n\n        openid_req_msg = Message.from_openid_args({\n          'mode' => 'id_res',\n          'ns' => OPENID2_NS,\n          'ns.pape' => PAPE::NS_URI,\n          'pape.auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),\n          'pape.auth_time' => '1983-11-05T12:30:24Z'\n          })\n        signed_stuff = {\n          'auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),\n          'auth_time' => '1983-11-05T12:30:24Z'\n        }\n        oid_req = DummySuccessResponse.new(openid_req_msg, signed_stuff)\n        req = PAPE::Response.from_success_response(oid_req)\n        assert_equal([PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT], req.auth_policies)\n        assert_equal('1983-11-05T12:30:24Z', req.auth_time)\n      end\n\n      def test_from_success_response_unsigned\n        openid_req_msg = Message.from_openid_args({\n          'mode' => 'id_res',\n          'ns' => OPENID2_NS,\n          'ns.pape' => PAPE::NS_URI,\n          'pape.auth_policies' => [PAPE::AUTH_MULTI_FACTOR, PAPE::AUTH_PHISHING_RESISTANT].join(' '),\n          'pape.auth_time' => '1983-11-05T12:30:24Z'\n          })\n        signed_stuff = {}\n        endpoint = OpenIDServiceEndpoint.new\n        oid_req = Consumer::SuccessResponse.new(endpoint, openid_req_msg, signed_stuff)\n        req = PAPE::Response.from_success_response(oid_req)\n        assert(req.nil?, req.inspect)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_parsehtml.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"openid/yadis/parsehtml\"\n\nmodule OpenID\n  class ParseHTMLTestCase < Minitest::Test\n    include OpenID::TestDataMixin\n\n    def test_parsehtml\n      reserved_values = ['None', 'EOF']\n      chunks = read_data_file('test1-parsehtml.txt', false).split(\"\\f\\n\")\n\n      chunks.each{|c|\n        expected, html = c.split(\"\\n\", 2)\n        found = Yadis::html_yadis_location(html)\n\n        assert(!reserved_values.member?(found))\n\n        # this case is a little hard to detect and the distinction\n        # seems unimportant\n        expected = \"None\" if expected == \"EOF\"\n\n        found = \"None\" if found.nil?\n        assert_equal(expected, found, html.split(\"\\n\",2)[0])\n      }\n    end\n  end\n\n  # the HTML tokenizer test\n  class TC_TestHTMLTokenizer < Minitest::Test\n    def test_bad_link\n      toke = HTMLTokenizer.new(\"<p><a href=http://bad.com/link>foo</a></p>\")\n      assert(\"http://bad.com/link\" == toke.getTag(\"a\").attr_hash['href'])\n    end\n\n    def test_namespace\n      toke = HTMLTokenizer.new(\"<f:table xmlns:f=\\\"http://www.com/foo\\\">\")\n      assert(\"http://www.com/foo\" == toke.getTag(\"f:table\").attr_hash['xmlns:f'])\n    end\n\n    def test_comment\n      toke = HTMLTokenizer.new(\"<!-- comment on me -->\")\n      t = toke.getNextToken\n      assert(HTMLComment == t.class)\n      assert(\"comment on me\" == t.contents)\n    end\n\n    def test_full\n      page = \"<HTML>\n<HEAD>\n<TITLE>This is the title</TITLE>\n</HEAD>\n<!-- Here comes the <a href=\\\"missing.link\\\">blah</a>\ncomment body\n-->\n<BODY>\n<H1>This is the header</H1>\n<P>\n  This is the paragraph, it contains\n  <a href=\\\"link.html\\\">links</a>,\n  <img src=\\\"blah.gif\\\" optional alt='images\nare\nreally cool'>.  Ok, here is some more text and\n  <A href=\\\"http://another.link.com/\\\" target=\\\"_blank\\\">another link</A>.\n</P>\n</body>\n</HTML>\n\"\n      toke = HTMLTokenizer.new(page)\n\n      assert(\"<h1>\" == toke.getTag(\"h1\", \"h2\", \"h3\").to_s.downcase)\n      assert(HTMLTag.new(\"<a href=\\\"link.html\\\">\") == toke.getTag(\"IMG\", \"A\"))\n      assert(\"links\" == toke.getTrimmedText)\n      assert(toke.getTag(\"IMG\", \"A\").attr_hash['optional'])\n      assert(\"_blank\" == toke.getTag(\"IMG\", \"A\").attr_hash['target'])\n    end\n  end\nend\n\n"
  },
  {
    "path": "test/test_responses.rb",
    "content": "require \"minitest/autorun\"\nrequire \"openid/consumer/discovery\"\nrequire \"openid/consumer/responses\"\n\nmodule OpenID\n  class Consumer\n    module TestResponses\n      class TestSuccessResponse < Minitest::Test\n        def setup\n          @endpoint = OpenIDServiceEndpoint.new\n          @endpoint.claimed_id = 'identity_url'\n        end\n\n        def test_extension_response\n          q = {\n            'ns.sreg' => 'urn:sreg',\n            'ns.unittest' => 'urn:unittest',\n            'unittest.one' => '1',\n            'unittest.two' => '2',\n            'sreg.nickname' => 'j3h',\n            'return_to' => 'return_to',\n          }\n          signed_list = q.keys.map { |k| 'openid.' + k }\n          msg = Message.from_openid_args(q)\n          resp = SuccessResponse.new(@endpoint, msg, signed_list)\n          utargs = resp.extension_response('urn:unittest', false)\n          assert_equal(utargs, {'one' => '1', 'two' => '2'})\n          sregargs = resp.extension_response('urn:sreg', false)\n          assert_equal(sregargs, {'nickname' => 'j3h'})\n        end\n\n        def test_extension_response_signed\n          args = {\n            'ns.sreg' => 'urn:sreg',\n            'ns.unittest' => 'urn:unittest',\n            'unittest.one' => '1',\n            'unittest.two' => '2',\n            'sreg.nickname' => 'j3h',\n            'sreg.dob' => 'yesterday',\n            'return_to' => 'return_to',\n            'signed' => 'sreg.nickname,unittest.one,sreg.dob',\n          }\n\n          signed_list = ['openid.sreg.nickname',\n                         'openid.unittest.one',\n                         'openid.sreg.dob',]\n\n          msg = Message.from_openid_args(args)\n          resp = SuccessResponse.new(@endpoint, msg, signed_list)\n\n          # All args in this NS are signed, so expect all.\n          sregargs = resp.extension_response('urn:sreg', true)\n          assert_equal(sregargs, {'nickname' => 'j3h', 'dob' => 'yesterday'})\n\n          # Not all args in this NS are signed, so expect nil when\n          # asking for them.\n          utargs = resp.extension_response('urn:unittest', true)\n          assert_nil(utargs)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_server.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'util'\nrequire 'uri'\nrequire 'openid/server'\nrequire 'openid/cryptutil'\nrequire 'openid/association'\nrequire 'openid/util'\nrequire 'openid/message'\nrequire 'openid/store/memory'\nrequire 'openid/dh'\nrequire 'openid/consumer/associationmanager'\n\n# In general, if you edit or add tests here, try to move in the\n# direction of testing smaller units.  For testing the external\n# interfaces, we'll be developing an implementation-agnostic testing\n# suite.\n\n# for more, see /etc/ssh/moduli\n\nmodule OpenID\n\n  ALT_MODULUS = 0xCAADDDEC1667FC68B5FA15D53C4E1532DD24561A1A2D47A12C01ABEA1E00731F6921AAC40742311FDF9E634BB7131BEE1AF240261554389A910425E044E88C8359B010F5AD2B80E29CB1A5B027B19D9E01A6F63A6F45E5D7ED2FF6A2A0085050A7D0CF307C3DB51D2490355907B4427C23A98DF1EB8ABEF2BA209BB7AFFE86A7\n  ALT_GEN = 5\n\n  class CatchLogs\n    def catchlogs_setup\n      @old_logger = Util.logger\n      Util.logger = self.method('got_log_message')\n      @messages = []\n    end\n\n    def got_log_message(message)\n      @messages << message\n    end\n\n    def teardown\n      Util.logger = @old_logger\n    end\n  end\n\n  class TestProtocolError < Minitest::Test\n    def test_browserWithReturnTo\n      return_to = \"http://rp.unittest/consumer\"\n      # will be a ProtocolError raised by Decode or\n      # CheckIDRequest.answer\n      args = Message.from_post_args({\n                                      'openid.mode' => 'monkeydance',\n                                      'openid.identity' => 'http://wagu.unittest/',\n                                      'openid.return_to' => return_to,\n                                    })\n      e = Server::ProtocolError.new(args, \"plucky\")\n      assert(e.has_return_to)\n      expected_args = {\n        'openid.mode' => 'error',\n        'openid.error' => 'plucky',\n      }\n\n      _, result_args = e.encode_to_url.split('?', 2)\n      result_args = Util.parse_query(result_args)\n      assert_equal(result_args, expected_args)\n    end\n\n    def test_browserWithReturnTo_OpenID2_GET\n      return_to = \"http://rp.unittest/consumer\"\n      # will be a ProtocolError raised by Decode or\n      # CheckIDRequest.answer\n      args = Message.from_post_args({\n                                      'openid.ns' => OPENID2_NS,\n                                      'openid.mode' => 'monkeydance',\n                                      'openid.identity' => 'http://wagu.unittest/',\n                                      'openid.claimed_id' => 'http://wagu.unittest/',\n                                      'openid.return_to' => return_to,\n                                    })\n      e = Server::ProtocolError.new(args, \"plucky\")\n      assert(e.has_return_to)\n      expected_args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'error',\n        'openid.error' => 'plucky',\n      }\n\n      _, result_args = e.encode_to_url.split('?', 2)\n      result_args = Util.parse_query(result_args)\n      assert_equal(result_args, expected_args)\n    end\n\n    def test_browserWithReturnTo_OpenID2_POST\n      return_to = \"http://rp.unittest/consumer\" + ('x' * OPENID1_URL_LIMIT)\n      # will be a ProtocolError raised by Decode or\n      # CheckIDRequest.answer\n      args = Message.from_post_args({\n                                      'openid.ns' => OPENID2_NS,\n                                      'openid.mode' => 'monkeydance',\n                                      'openid.identity' => 'http://wagu.unittest/',\n                                      'openid.claimed_id' => 'http://wagu.unittest/',\n                                      'openid.return_to' => return_to,\n                                    })\n      e = Server::ProtocolError.new(args, \"plucky\")\n      assert(e.has_return_to)\n      assert(e.which_encoding == Server::ENCODE_HTML_FORM)\n      assert(e.to_form_markup == e.to_message.to_form_markup(\n                                                             args.get_arg(OPENID_NS, 'return_to')))\n    end\n\n    def test_browserWithReturnTo_OpenID1_exceeds_limit\n      return_to = \"http://rp.unittest/consumer\" + ('x' * OPENID1_URL_LIMIT)\n      # will be a ProtocolError raised by Decode or\n      # CheckIDRequest.answer\n      args = Message.from_post_args({\n                                      'openid.mode' => 'monkeydance',\n                                      'openid.identity' => 'http://wagu.unittest/',\n                                      'openid.return_to' => return_to,\n                                    })\n      e = Server::ProtocolError.new(args, \"plucky\")\n      assert(e.has_return_to)\n      expected_args = {\n        'openid.mode' => 'error',\n        'openid.error' => 'plucky',\n      }\n\n      assert(e.which_encoding == Server::ENCODE_URL)\n\n      _, result_args = e.encode_to_url.split('?', 2)\n      result_args = Util.parse_query(result_args)\n      assert_equal(result_args, expected_args)\n    end\n\n    def test_noReturnTo\n      # will be a ProtocolError raised by Decode or\n      # CheckIDRequest.answer\n      args = Message.from_post_args({\n                                      'openid.mode' => 'zebradance',\n                                      'openid.identity' => 'http://wagu.unittest/',\n                                    })\n      e = Server::ProtocolError.new(args, \"waffles\")\n      assert(!e.has_return_to)\n      expected = \"error:waffles\\nmode:error\\n\"\n      assert_equal(e.encode_to_kvform, expected)\n    end\n\n    def test_no_message\n      e = Server::ProtocolError.new(nil, \"no message\")\n      assert(e.get_return_to.nil?)\n      assert_nil(e.which_encoding)\n    end\n\n    def test_which_encoding_no_message\n      e = Server::ProtocolError.new(nil, \"no message\")\n      assert(e.which_encoding.nil?)\n    end\n  end\n\n  class TestDecode < Minitest::Test\n    def setup\n      @claimed_id = 'http://de.legating.de.coder.unittest/'\n      @id_url = \"http://decoder.am.unittest/\"\n      @rt_url = \"http://rp.unittest/foobot/?qux=zam\"\n      @tr_url = \"http://rp.unittest/\"\n      @assoc_handle = \"{assoc}{handle}\"\n      @op_endpoint = 'http://endpoint.unittest/encode'\n      @store = Store::Memory.new()\n      @server = Server::Server.new(@store, @op_endpoint)\n      @decode = Server::Decoder.new(@server).method('decode')\n    end\n\n    def test_none\n      args = {}\n      r = @decode.call(args)\n      assert_nil(r)\n    end\n\n    def test_irrelevant\n      args = {\n        'pony' => 'spotted',\n        'sreg.mutant_power' => 'decaffinator',\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_bad\n      args = {\n        'openid.mode' => 'twos-compliment',\n        'openid.pants' => 'zippered',\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_dictOfLists\n      args = {\n        'openid.mode' => ['checkid_setup'],\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.trust_root' => @tr_url,\n      }\n      begin\n        result = @decode.call(args)\n      rescue ArgumentError => err\n        assert !err.to_s.index('values').nil?\n      else\n        flunk(\"Expected ArgumentError, but got result #{result}\")\n      end\n    end\n\n    def test_checkidImmediate\n      args = {\n        'openid.mode' => 'checkid_immediate',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.trust_root' => @tr_url,\n        # should be ignored\n        'openid.some.extension' => 'junk',\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::CheckIDRequest))\n      assert_equal(r.mode, \"checkid_immediate\")\n      assert_equal(r.immediate, true)\n      assert_equal(r.identity, @id_url)\n      assert_equal(r.trust_root, @tr_url)\n      assert_equal(r.return_to, @rt_url)\n      assert_equal(r.assoc_handle, @assoc_handle)\n    end\n\n    def test_checkidImmediate_constructor\n      r = Server::CheckIDRequest.new(@id_url, @rt_url, nil,\n                                     @rt_url, true, @assoc_handle)\n      assert(r.mode == 'checkid_immediate')\n      assert(r.immediate)\n    end\n\n    def test_checkid_missing_return_to_and_trust_root\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.claimed_id' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n      }\n      assert_raises(Server::ProtocolError) {\n        m = Message.from_post_args(args)\n        Server::CheckIDRequest.from_message(m, @op_endpoint)\n      }\n    end\n\n    def test_checkid_id_select\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => IDENTIFIER_SELECT,\n        'openid.claimed_id' => IDENTIFIER_SELECT,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.realm' => @tr_url,\n      }\n      m = Message.from_post_args(args)\n      req = Server::CheckIDRequest.from_message(m, @op_endpoint)\n      assert(req.id_select)\n    end\n\n    def test_checkid_not_id_select\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.realm' => @tr_url,\n      }\n\n      id_args = [\n                 {'openid.claimed_id' => IDENTIFIER_SELECT,\n                   'openid.identity' => 'http://bogus.com/'},\n\n                 {'openid.claimed_id' => 'http://bogus.com/',\n                   'openid.identity' => 'http://bogus.com/'},\n                ]\n\n      id_args.each { |id|\n        m = Message.from_post_args(args.merge(id))\n        req = Server::CheckIDRequest.from_message(m, @op_endpoint)\n        assert(!req.id_select)\n      }\n    end\n\n    def test_checkidSetup\n      args = {\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.trust_root' => @tr_url,\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::CheckIDRequest))\n      assert_equal(r.mode, \"checkid_setup\")\n      assert_equal(r.immediate, false)\n      assert_equal(r.identity, @id_url)\n      assert_equal(r.trust_root, @tr_url)\n      assert_equal(r.return_to, @rt_url)\n    end\n\n    def test_checkidSetupOpenID2\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.claimed_id' => @claimed_id,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.realm' => @tr_url,\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::CheckIDRequest))\n      assert_equal(r.mode, \"checkid_setup\")\n      assert_equal(r.immediate, false)\n      assert_equal(r.identity, @id_url)\n      assert_equal(r.claimed_id, @claimed_id)\n      assert_equal(r.trust_root, @tr_url)\n      assert_equal(r.return_to, @rt_url)\n    end\n\n    def test_checkidSetupNoClaimedIDOpenID2\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.realm' => @tr_url,\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_checkidSetupNoIdentityOpenID2\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.realm' => @tr_url,\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::CheckIDRequest))\n      assert_equal(r.mode, \"checkid_setup\")\n      assert_equal(r.immediate, false)\n      assert_nil(r.identity)\n      assert_equal(r.trust_root, @tr_url)\n      assert_equal(r.return_to, @rt_url)\n    end\n\n    def test_checkidSetupNoReturnOpenID1\n      # Make sure an OpenID 1 request cannot be decoded if it lacks a\n      # return_to.\n      args = {\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.trust_root' => @tr_url,\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_checkidSetupNoReturnOpenID2\n      # Make sure an OpenID 2 request with no return_to can be decoded,\n      # and make sure a response to such a request raises\n      # NoReturnToError.\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.claimed_id' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.realm' => @tr_url,\n      }\n\n      req = @decode.call(args)\n      assert(req.is_a?(Server::CheckIDRequest))\n\n      assert_raises(Server::NoReturnToError) {\n        req.answer(false)\n      }\n\n      assert_raises(Server::NoReturnToError) {\n        req.encode_to_url('bogus')\n      }\n\n      assert_raises(Server::NoReturnToError) {\n        req.cancel_url\n      }\n    end\n\n    def test_checkidSetupRealmRequiredOpenID2\n      # Make sure that an OpenID 2 request which lacks return_to cannot\n      # be decoded if it lacks a realm.  Spec => This value\n      # (openid.realm) MUST be sent if openid.return_to is omitted.\n      args = {\n        'openid.ns' => OPENID2_NS,\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_checkidSetupBadReturn\n      args = {\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => 'not a url',\n      }\n      begin\n        result = @decode.call(args)\n      rescue Server::ProtocolError => err\n        assert(err.openid_message)\n      else\n        flunk(\"Expected ProtocolError, instead returned with #{result}\")\n      end\n    end\n\n    def test_checkidSetupUntrustedReturn\n      args = {\n        'openid.mode' => 'checkid_setup',\n        'openid.identity' => @id_url,\n        'openid.assoc_handle' => @assoc_handle,\n        'openid.return_to' => @rt_url,\n        'openid.trust_root' => 'http://not-the-return-place.unittest/',\n      }\n      begin\n        result = @decode.call(args)\n      rescue Server::UntrustedReturnURL => err\n        assert(err.openid_message, err.to_s)\n      else\n        flunk(\"Expected UntrustedReturnURL, instead returned with #{result}\")\n      end\n    end\n\n    def test_checkidSetupUntrustedReturn_Constructor\n      assert_raises(Server::UntrustedReturnURL) {\n        Server::CheckIDRequest.new(@id_url, @rt_url, nil,\n                                   'http://not-the-return-place.unittest/',\n                                   false, @assoc_handle)\n      }\n    end\n\n    def test_checkidSetupMalformedReturnURL_Constructor\n      assert_raises(Server::MalformedReturnURL) {\n        Server::CheckIDRequest.new(@id_url, 'bogus://return.url', nil,\n                                   'http://trustroot.com/',\n                                   false, @assoc_handle)\n      }\n    end\n\n    def test_checkAuth\n      args = {\n        'openid.mode' => 'check_authentication',\n        'openid.assoc_handle' => '{dumb}{handle}',\n        'openid.sig' => 'sigblob',\n        'openid.signed' => 'identity,return_to,response_nonce,mode',\n        'openid.identity' => 'signedval1',\n        'openid.return_to' => 'signedval2',\n        'openid.response_nonce' => 'signedval3',\n        'openid.baz' => 'unsigned',\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::CheckAuthRequest))\n      assert_equal(r.mode, 'check_authentication')\n      assert_equal(r.sig, 'sigblob')\n    end\n\n    def test_checkAuthMissingSignature\n      args = {\n        'openid.mode' => 'check_authentication',\n        'openid.assoc_handle' => '{dumb}{handle}',\n        'openid.signed' => 'foo,bar,mode',\n        'openid.foo' => 'signedval1',\n        'openid.bar' => 'signedval2',\n        'openid.baz' => 'unsigned',\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_checkAuthAndInvalidate\n      args = {\n        'openid.mode' => 'check_authentication',\n        'openid.assoc_handle' => '{dumb}{handle}',\n        'openid.invalidate_handle' => '[[SMART_handle]]',\n        'openid.sig' => 'sigblob',\n        'openid.signed' => 'identity,return_to,response_nonce,mode',\n        'openid.identity' => 'signedval1',\n        'openid.return_to' => 'signedval2',\n        'openid.response_nonce' => 'signedval3',\n        'openid.baz' => 'unsigned',\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::CheckAuthRequest))\n      assert_equal(r.invalidate_handle, '[[SMART_handle]]')\n    end\n\n    def test_associateDH\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"Rzup9265tw==\",\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::AssociateRequest))\n      assert_equal(r.mode, \"associate\")\n      assert_equal(r.session.session_type, \"DH-SHA1\")\n      assert_equal(r.assoc_type, \"HMAC-SHA1\")\n      assert(r.session.consumer_pubkey)\n    end\n\n    def test_associateDHMissingKey\n      # Trying DH assoc w/o public key\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n      }\n      # Using DH-SHA1 without supplying dh_consumer_public is an error.\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_associateDHpubKeyNotB64\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"donkeydonkeydonkey\",\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_associateDHModGen\n      # test dh with non-default but valid values for dh_modulus and\n      # dh_gen\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"Rzup9265tw==\",\n        'openid.dh_modulus' => CryptUtil.num_to_base64(ALT_MODULUS),\n        'openid.dh_gen' => CryptUtil.num_to_base64(ALT_GEN) ,\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::AssociateRequest))\n      assert_equal(r.mode, \"associate\")\n      assert_equal(r.session.session_type, \"DH-SHA1\")\n      assert_equal(r.assoc_type, \"HMAC-SHA1\")\n      assert_equal(r.session.dh.modulus, ALT_MODULUS)\n      assert_equal(r.session.dh.generator, ALT_GEN)\n      assert(r.session.consumer_pubkey)\n    end\n\n    def test_associateDHCorruptModGen\n      # test dh with non-default but valid values for dh_modulus and\n      # dh_gen\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"Rzup9265tw==\",\n        'openid.dh_modulus' => 'pizza',\n        'openid.dh_gen' => 'gnocchi',\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_associateDHMissingGen\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"Rzup9265tw==\",\n        'openid.dh_modulus' => 'pizza',\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_associateDHMissingMod\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"Rzup9265tw==\",\n        'openid.dh_gen' => 'pizza',\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    #     def test_associateDHInvalidModGen(self):\n    #         # test dh with properly encoded values that are not a valid\n    #         #   modulus/generator combination.\n    #         args = {\n    #             'openid.mode': 'associate',\n    #             'openid.session_type': 'DH-SHA1',\n    #             'openid.dh_consumer_public': \"Rzup9265tw==\",\n    #             'openid.dh_modulus': cryptutil.longToBase64(9),\n    #             'openid.dh_gen': cryptutil.longToBase64(27) ,\n    #             }\n    #         self.failUnlessRaises(server.ProtocolError, self.decode, args)\n    #     test_associateDHInvalidModGen.todo = \"low-priority feature\"\n\n    def test_associateWeirdSession\n      args = {\n        'openid.mode' => 'associate',\n        'openid.session_type' => 'FLCL6',\n        'openid.dh_consumer_public' => \"YQ==\\n\",\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_associatePlain\n      args = {\n        'openid.mode' => 'associate',\n      }\n      r = @decode.call(args)\n      assert(r.is_a?(Server::AssociateRequest))\n      assert_equal(r.mode, \"associate\")\n      assert_equal(r.session.session_type, \"no-encryption\")\n      assert_equal(r.assoc_type, \"HMAC-SHA1\")\n    end\n\n    def test_nomode\n      args = {\n        'openid.session_type' => 'DH-SHA1',\n        'openid.dh_consumer_public' => \"my public keeey\",\n      }\n      assert_raises(Server::ProtocolError) {\n        @decode.call(args)\n      }\n    end\n\n    def test_invalidns\n      args = {'openid.ns' => 'Vegetables',\n              'openid.mode' => 'associate'}\n      begin\n        @decode.call(args)\n      rescue Server::ProtocolError => err\n        assert(err.openid_message)\n        assert(err.to_s.index('Vegetables'))\n      end\n    end\n  end\n\n  class BogusEncoder < Server::Encoder\n    def encode(response)\n      return \"BOGUS\"\n    end\n  end\n\n  class BogusDecoder < Server::Decoder\n    def decode(query)\n      return \"BOGUS\"\n    end\n  end\n\n  class TestEncode < Minitest::Test\n    def setup\n      @encoder = Server::Encoder.new\n      @encode = @encoder.method('encode')\n      @op_endpoint = 'http://endpoint.unittest/encode'\n      @store = Store::Memory.new\n      @server = Server::Server.new(@store, @op_endpoint)\n    end\n\n    def test_id_res_OpenID2_GET\n      # Check that when an OpenID 2 response does not exceed the OpenID\n      # 1 message size, a GET response (i.e., redirect) is issued.\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false,\n                                   nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'ns' => OPENID2_NS,\n                                                   'mode' => 'id_res',\n                                                   'identity' => request.identity,\n                                                   'claimed_id' => request.identity,\n                                                   'return_to' => request.return_to,\n                                                 })\n\n      assert(!response.render_as_form)\n      assert(response.which_encoding == Server::ENCODE_URL)\n      webresponse = @encode.call(response)\n      assert(webresponse.headers.member?('location'))\n    end\n\n    def test_id_res_OpenID2_POST\n      # Check that when an OpenID 2 response exceeds the OpenID 1\n      # message size, a POST response (i.e., an HTML form) is returned.\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false,\n                                   nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'ns' => OPENID2_NS,\n                                                   'mode' => 'id_res',\n                                                   'identity' => request.identity,\n                                                   'claimed_id' => request.identity,\n                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,\n                                                 })\n\n      assert(response.render_as_form)\n      assert(response.encode_to_url.length > OPENID1_URL_LIMIT)\n      assert(response.which_encoding == Server::ENCODE_HTML_FORM)\n      webresponse = @encode.call(response)\n      assert_equal(webresponse.body, response.to_form_markup)\n    end\n\n    def test_to_form_markup\n       request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false,\n                                   nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'ns' => OPENID2_NS,\n                                                   'mode' => 'id_res',\n                                                   'identity' => request.identity,\n                                                   'claimed_id' => request.identity,\n                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,\n                                                 })\n      form_markup = response.to_form_markup({'foo'=>'bar'})\n      assert(/ foo=\"bar\"/ =~ form_markup, form_markup)\n    end\n\n    def test_to_html\n       request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false,\n                                   nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'ns' => OPENID2_NS,\n                                                   'mode' => 'id_res',\n                                                   'identity' => request.identity,\n                                                   'claimed_id' => request.identity,\n                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,\n                                                 })\n      html = response.to_html\n      assert(html)\n    end\n\n    def test_id_res_OpenID1_exceeds_limit\n      # Check that when an OpenID 1 response exceeds the OpenID 1\n      # message size, a GET response is issued.  Technically, this\n      # shouldn't be permitted by the library, but this test is in place\n      # to preserve the status quo for OpenID 1.\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false,\n                                   nil)\n      request.message = Message.new(OPENID1_NS)\n\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'mode' => 'id_res',\n                                                   'identity' => request.identity,\n                                                   'return_to' => 'x' * OPENID1_URL_LIMIT,\n                                                 })\n\n      assert(!response.render_as_form)\n      assert(response.encode_to_url.length > OPENID1_URL_LIMIT)\n      assert(response.which_encoding == Server::ENCODE_URL)\n      webresponse = @encode.call(response)\n      assert_equal(webresponse.headers['location'], response.encode_to_url)\n    end\n\n    def test_id_res\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false, nil)\n      request.message = Message.new(OPENID1_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'mode' => 'id_res',\n                                                   'identity' => request.identity,\n                                                   'return_to' => request.return_to,\n                                                 })\n      webresponse = @encode.call(response)\n      assert_equal(webresponse.code, Server::HTTP_REDIRECT)\n      assert(webresponse.headers.member?('location'))\n\n      location = webresponse.headers['location']\n      assert(location.start_with?(request.return_to),\n             sprintf(\"%s does not start with %s\",\n                     location, request.return_to))\n      # argh.\n      q2 = Util.parse_query(URI::parse(location).query)\n      expected = response.fields.to_post_args\n      assert_equal(q2, expected)\n    end\n\n    def test_cancel\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false, nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'mode' => 'cancel',\n                                                 })\n      webresponse = @encode.call(response)\n      assert_equal(webresponse.code, Server::HTTP_REDIRECT)\n      assert(webresponse.headers.member?('location'))\n    end\n\n    def test_cancel_to_form\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false, nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'mode' => 'cancel',\n                                                 })\n      form = response.to_form_markup\n      assert(form.index(request.return_to))\n    end\n\n    def test_assocReply\n      msg = Message.new(OPENID2_NS)\n      msg.set_arg(OPENID2_NS, 'session_type', 'no-encryption')\n      request = Server::AssociateRequest.from_message(msg)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_post_args(\n                                               {'openid.assoc_handle' => \"every-zig\"})\n      webresponse = @encode.call(response)\n      body = \"assoc_handle:every-zig\\n\"\n\n      assert_equal(webresponse.code, Server::HTTP_OK)\n      assert_equal(webresponse.headers, {})\n      assert_equal(webresponse.body, body)\n    end\n\n    def test_checkauthReply\n      request = Server::CheckAuthRequest.new('a_sock_monkey',\n                                     'siggggg',\n                                     [])\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'is_valid' => 'true',\n                                                   'invalidate_handle' => 'xXxX:xXXx'\n                                                 })\n      body = \"invalidate_handle:xXxX:xXXx\\nis_valid:true\\n\"\n\n      webresponse = @encode.call(response)\n      assert_equal(webresponse.code, Server::HTTP_OK)\n      assert_equal(webresponse.headers, {})\n      assert_equal(webresponse.body, body)\n    end\n\n    def test_unencodableError\n      args = Message.from_post_args({\n                                      'openid.identity' => 'http://limu.unittest/',\n                                    })\n      e = Server::ProtocolError.new(args, \"wet paint\")\n      assert_raises(Server::EncodingError) {\n        @encode.call(e)\n      }\n    end\n\n    def test_encodableError\n      args = Message.from_post_args({\n                                      'openid.mode' => 'associate',\n                                      'openid.identity' => 'http://limu.unittest/',\n                                    })\n      body=\"error:snoot\\nmode:error\\n\"\n      webresponse = @encode.call(Server::ProtocolError.new(args, \"snoot\"))\n      assert_equal(webresponse.code, Server::HTTP_ERROR)\n      assert_equal(webresponse.headers, {})\n      assert_equal(webresponse.body, body)\n    end\n  end\n\n  class TestSigningEncode < Minitest::Test\n    def setup\n      @_dumb_key = Server::Signatory._dumb_key\n      @_normal_key = Server::Signatory._normal_key\n      @store = Store::Memory.new()\n      @server = Server::Server.new(@store, \"http://signing.unittest/enc\")\n      @request = Server::CheckIDRequest.new(\n                                    'http://bombom.unittest/',\n                                    'http://burr.unittest/999',\n                                    @server.op_endpoint,\n                                    'http://burr.unittest/',\n                                    false, nil)\n      @request.message = Message.new(OPENID2_NS)\n\n      @response = Server::OpenIDResponse.new(@request)\n      @response.fields = Message.from_openid_args({\n                                                    'mode' => 'id_res',\n                                                    'identity' => @request.identity,\n                                                    'return_to' => @request.return_to,\n                                                  })\n      @signatory = Server::Signatory.new(@store)\n      @encoder = Server::SigningEncoder.new(@signatory)\n      @encode = @encoder.method('encode')\n    end\n\n    def test_idres\n      assoc_handle = '{bicycle}{shed}'\n      @store.store_association(\n                               @_normal_key,\n                               Association.from_expires_in(60, assoc_handle,\n                                                           'sekrit', 'HMAC-SHA1'))\n      @request.assoc_handle = assoc_handle\n      webresponse = @encode.call(@response)\n      assert_equal(webresponse.code, Server::HTTP_REDIRECT)\n      assert(webresponse.headers.member?('location'))\n\n      location = webresponse.headers['location']\n      query = Util.parse_query(URI::parse(location).query)\n      assert(query.member?('openid.sig'))\n      assert(query.member?('openid.assoc_handle'))\n      assert(query.member?('openid.signed'))\n    end\n\n    def test_idresDumb\n      webresponse = @encode.call(@response)\n      assert_equal(webresponse.code, Server::HTTP_REDIRECT)\n      assert(webresponse.headers.has_key?('location'))\n\n      location = webresponse.headers['location']\n      query = Util.parse_query(URI::parse(location).query)\n      assert(query.member?('openid.sig'))\n      assert(query.member?('openid.assoc_handle'))\n      assert(query.member?('openid.signed'))\n    end\n\n    def test_forgotStore\n      @encoder.signatory = nil\n      assert_raises(ArgumentError) {\n        @encode.call(@response)\n      }\n    end\n\n    def test_cancel\n      request = Server::CheckIDRequest.new(\n                                   'http://bombom.unittest/',\n                                   'http://burr.unittest/999',\n                                   @server.op_endpoint,\n                                   'http://burr.unittest/',\n                                   false, nil)\n      request.message = Message.new(OPENID2_NS)\n      response = Server::OpenIDResponse.new(request)\n      response.fields.set_arg(OPENID_NS, 'mode', 'cancel')\n      webresponse = @encode.call(response)\n      assert_equal(webresponse.code, Server::HTTP_REDIRECT)\n      assert(webresponse.headers.has_key?('location'))\n      location = webresponse.headers['location']\n      query = Util.parse_query(URI::parse(location).query)\n      assert !query.has_key?('openid.sig')\n    end\n\n    def test_assocReply\n      msg = Message.new(OPENID2_NS)\n      msg.set_arg(OPENID2_NS, 'session_type', 'no-encryption')\n      request = Server::AssociateRequest.from_message(msg)\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({'assoc_handle' => \"every-zig\"})\n      webresponse = @encode.call(response)\n      body = \"assoc_handle:every-zig\\n\"\n      assert_equal(webresponse.code, Server::HTTP_OK)\n      assert_equal(webresponse.headers, {})\n      assert_equal(webresponse.body, body)\n    end\n\n    def test_alreadySigned\n      @response.fields.set_arg(OPENID_NS, 'sig', 'priorSig==')\n      assert_raises(Server::AlreadySigned) {\n        @encode.call(@response)\n      }\n    end\n  end\n\n  class TestCheckID < Minitest::Test\n    def setup\n      @op_endpoint = 'http://endpoint.unittest/'\n      @store = Store::Memory.new()\n      @server = Server::Server.new(@store, @op_endpoint)\n      @request = Server::CheckIDRequest.new(\n                                    'http://bambam.unittest/',\n                                    'http://bar.unittest/999',\n                                    @server.op_endpoint,\n                                    'http://bar.unittest/',\n                                    false)\n      @request.message = Message.new(OPENID2_NS)\n    end\n\n    def test_trustRootInvalid\n      @request.trust_root = \"http://foo.unittest/17\"\n      @request.return_to = \"http://foo.unittest/39\"\n      assert(!@request.trust_root_valid())\n    end\n\n    def test_trustRootInvalid_modified\n      @request.trust_root = \"does://not.parse/\"\n      @request.message = :sentinel\n      begin\n        result = @request.trust_root_valid\n      rescue Server::MalformedTrustRoot => why\n        assert_equal(:sentinel, why.openid_message)\n      else\n        flunk(\"Expected MalformedTrustRoot, got #{result.inspect}\")\n      end\n    end\n\n    def test_trustRootvalid_absent_trust_root\n      @request.trust_root = nil\n      assert(@request.trust_root_valid())\n    end\n\n    def test_trustRootValid\n      @request.trust_root = \"http://foo.unittest/\"\n      @request.return_to = \"http://foo.unittest/39\"\n      assert(@request.trust_root_valid())\n    end\n\n    def test_trustRootValidNoReturnTo\n      request = Server::CheckIDRequest.new(\n                                   'http://bambam.unittest/',\n                                   nil,\n                                   @server.op_endpoint,\n                                   'http://bar.unittest/',\n                                   false)\n\n      assert(request.trust_root_valid())\n    end\n\n    def test_returnToVerified_callsVerify\n      # Make sure that verifyReturnTo is calling the trustroot\n      # function verifyReturnTo\n      # Ensure that exceptions are passed through\n      sentinel = Exception.new()\n\n      __req = @request\n      tc = self\n\n      vrfyExc = Proc.new { |trust_root, return_to|\n        tc.assert_equal(__req.trust_root, trust_root)\n        tc.assert_equal(__req.return_to, return_to)\n        raise sentinel\n      }\n\n      TrustRoot.extend(OverrideMethodMixin)\n\n      TrustRoot.with_method_overridden(:verify_return_to, vrfyExc) do\n        begin\n          @request.return_to_verified()\n          flunk(\"Expected sentinel to be raised, got success\")\n        rescue Exception => e\n          assert(e.equal?(sentinel), [e, sentinel].inspect)\n        end\n      end\n\n      # Ensure that True and False are passed through unchanged\n      constVerify = Proc.new { |val|\n        verify = Proc.new { |trust_root, return_to|\n          tc.assert_equal(__req.trust_root, trust_root)\n          tc.assert_equal(__req.request.return_to, return_to)\n          return val\n        }\n\n        return verify\n      }\n\n      [true, false].each { |val|\n        verifier = constVerify.call(val)\n\n        TrustRoot.with_method_overridden(:verify_return_to, verifier) do\n          assert_equal(val, @request.return_to_verified())\n        end\n      }\n    end\n\n    def _expectAnswer(answer, identity=nil, claimed_id=nil)\n      expected_list = [\n                       ['mode', 'id_res'],\n                       ['return_to', @request.return_to],\n                       ['op_endpoint', @op_endpoint],\n                      ]\n      if identity\n        expected_list << ['identity', identity]\n        if claimed_id\n          expected_list << ['claimed_id', claimed_id]\n        else\n          expected_list << ['claimed_id', identity]\n        end\n      end\n\n      expected_list.each { |k, expected|\n        actual = answer.fields.get_arg(OPENID_NS, k)\n        assert_equal(expected, actual,\n                     sprintf(\"%s: expected %s, got %s\",\n                             k, expected, actual))\n      }\n\n      assert(answer.fields.has_key?(OPENID_NS, 'response_nonce'))\n      assert(answer.fields.get_openid_namespace() == OPENID2_NS)\n\n      # One for nonce, one for ns\n      assert_equal(answer.fields.to_post_args.length,\n                   expected_list.length + 2,\n                   answer.fields.to_post_args.inspect)\n    end\n\n    def test_answerAllow\n      # Check the fields specified by \"Positive Assertions\"\n      #\n      # including mode=id_res, identity, claimed_id, op_endpoint,\n      # return_to\n      answer = @request.answer(true)\n      assert_equal(answer.request, @request)\n      _expectAnswer(answer, @request.identity)\n    end\n\n    def test_answerAllowDelegatedIdentity\n      @request.claimed_id = 'http://delegating.unittest/'\n      answer = @request.answer(true)\n      _expectAnswer(answer, @request.identity,\n                    @request.claimed_id)\n    end\n\n    def test_answerAllowWithoutIdentityReally\n      @request.identity = nil\n      answer = @request.answer(true)\n      assert_equal(answer.request, @request)\n      _expectAnswer(answer)\n    end\n\n    def test_answerAllowAnonymousFail\n      @request.identity = nil\n      # XXX - Check on this, I think this behavior is legal in OpenID\n      # 2.0?\n      assert_raises(ArgumentError) {\n        @request.answer(true, nil, \"=V\")\n      }\n    end\n\n    def test_answerAllowWithIdentity\n      @request.identity = IDENTIFIER_SELECT\n      selected_id = 'http://anon.unittest/9861'\n      answer = @request.answer(true, nil, selected_id)\n      _expectAnswer(answer, selected_id)\n    end\n\n    def test_answerAllowWithNoIdentity\n      @request.identity = IDENTIFIER_SELECT\n      assert_raises(ArgumentError) {\n        @request.answer(true, nil, nil)\n      }\n    end\n\n    def test_immediate_openid1_no_identity\n      @request.message = Message.new(OPENID1_NS)\n      @request.immediate = true\n      @request.mode = 'checkid_immediate'\n      resp = @request.answer(false)\n      assert(resp.fields.get_arg(OPENID_NS, 'mode') == 'id_res')\n    end\n\n    def test_checkid_setup_openid1_no_identity\n      @request.message = Message.new(OPENID1_NS)\n      @request.immediate = false\n      @request.mode = 'checkid_setup'\n      resp = @request.answer(false)\n      assert(resp.fields.get_arg(OPENID_NS, 'mode') == 'cancel')\n    end\n\n    def test_immediate_openid1_no_server_url\n      @request.message = Message.new(OPENID1_NS)\n      @request.immediate = true\n      @request.mode = 'checkid_immediate'\n      @request.op_endpoint = nil\n\n      assert_raises(ArgumentError) {\n        @request.answer(false)\n      }\n    end\n\n    def test_immediate_encode_to_url\n      @request.message = Message.new(OPENID1_NS)\n      @request.immediate = true\n      @request.mode = 'checkid_immediate'\n      @request.trust_root = \"BOGUS\"\n      @request.assoc_handle = \"ASSOC\"\n\n      server_url = \"http://server.com/server\"\n\n      url = @request.encode_to_url(server_url)\n      assert(url.start_with?(server_url))\n\n      _, query = url.split(\"?\", 2)\n      args = Util.parse_query(query)\n\n      m = Message.from_post_args(args)\n      assert(m.get_arg(OPENID_NS, 'trust_root') == \"BOGUS\")\n      assert(m.get_arg(OPENID_NS, 'assoc_handle') == \"ASSOC\")\n      assert(m.get_arg(OPENID_NS, 'mode'), \"checkid_immediate\")\n      assert(m.get_arg(OPENID_NS, 'identity') == @request.identity)\n      assert(m.get_arg(OPENID_NS, 'claimed_id') == @request.claimed_id)\n      assert(m.get_arg(OPENID_NS, 'return_to') == @request.return_to)\n    end\n\n    def test_answerAllowWithDelegatedIdentityOpenID2\n      # Answer an IDENTIFIER_SELECT case with a delegated identifier.\n\n      # claimed_id delegates to selected_id here.\n      @request.identity = IDENTIFIER_SELECT\n      selected_id = 'http://anon.unittest/9861'\n      claimed_id = 'http://monkeyhat.unittest/'\n      answer = @request.answer(true, nil, selected_id, claimed_id)\n      _expectAnswer(answer, selected_id, claimed_id)\n    end\n\n    def test_answerAllowWithDelegatedIdentityOpenID1\n      # claimed_id parameter doesn't exist in OpenID 1.\n      @request.message = Message.new(OPENID1_NS)\n      # claimed_id delegates to selected_id here.\n      @request.identity = IDENTIFIER_SELECT\n      selected_id = 'http://anon.unittest/9861'\n      claimed_id = 'http://monkeyhat.unittest/'\n      assert_raises(Server::VersionError) {\n        @request.answer(true, nil, selected_id, claimed_id)\n      }\n    end\n\n    def test_answerAllowWithAnotherIdentity\n      # XXX - Check on this, I think this behavior is legal in OpenID\n      # 2.0?\n      assert_raises(ArgumentError){\n        @request.answer(true, nil, \"http://pebbles.unittest/\")\n      }\n    end\n\n    def test_answerAllowNoIdentityOpenID1\n      @request.message = Message.new(OPENID1_NS)\n      @request.identity = nil\n      assert_raises(ArgumentError) {\n        @request.answer(true, nil, nil)\n      }\n    end\n\n    def test_answerAllowForgotEndpoint\n      @request.op_endpoint = nil\n      assert_raises(RuntimeError) {\n        @request.answer(true)\n      }\n    end\n\n    def test_checkIDWithNoIdentityOpenID1\n      msg = Message.new(OPENID1_NS)\n      msg.set_arg(OPENID_NS, 'return_to', 'bogus')\n      msg.set_arg(OPENID_NS, 'trust_root', 'bogus')\n      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n      msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')\n\n      assert_raises(Server::ProtocolError) {\n        Server::CheckIDRequest.from_message(msg, @server)\n      }\n    end\n\n    def test_fromMessageClaimedIDWithoutIdentityOpenID2\n      msg = Message.new(OPENID2_NS)\n      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n      msg.set_arg(OPENID_NS, 'return_to', 'http://invalid:8000/rt')\n      msg.set_arg(OPENID_NS, 'claimed_id', 'https://example.myopenid.com')\n\n      assert_raises(Server::ProtocolError) {\n        Server::CheckIDRequest.from_message(msg, @server)\n      }\n    end\n\n    def test_fromMessageIdentityWithoutClaimedIDOpenID2\n      msg = Message.new(OPENID2_NS)\n      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n      msg.set_arg(OPENID_NS, 'return_to', 'http://invalid:8000/rt')\n      msg.set_arg(OPENID_NS, 'identity', 'https://example.myopenid.com')\n\n      assert_raises(Server::ProtocolError) {\n        Server::CheckIDRequest.from_message(msg, @server)\n      }\n    end\n\n    def test_fromMessageWithEmptyTrustRoot\n      return_to = 'http://some.url/foo?bar=baz'\n      msg = Message.from_post_args({\n              'openid.assoc_handle' => '{blah}{blah}{OZivdQ==}',\n              'openid.claimed_id' => 'http://delegated.invalid/',\n              'openid.identity' => 'http://op-local.example.com/',\n              'openid.mode' => 'checkid_setup',\n              'openid.ns' => 'http://openid.net/signon/1.0',\n              'openid.return_to' => return_to,\n              'openid.trust_root' => ''\n              });\n      result = Server::CheckIDRequest.from_message(msg, @server)\n      assert_equal(return_to, result.trust_root)\n    end\n\n    def test_trustRootOpenID1\n      # Ignore openid.realm in OpenID 1\n      msg = Message.new(OPENID1_NS)\n      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n      msg.set_arg(OPENID_NS, 'trust_root', 'http://trustroot.com/')\n      msg.set_arg(OPENID_NS, 'realm', 'http://fake_trust_root/')\n      msg.set_arg(OPENID_NS, 'return_to', 'http://trustroot.com/foo')\n      msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')\n      msg.set_arg(OPENID_NS, 'identity', 'george')\n\n      result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)\n\n      assert(result.trust_root == 'http://trustroot.com/')\n    end\n\n    def test_trustRootOpenID2\n      # Ignore openid.trust_root in OpenID 2\n      msg = Message.new(OPENID2_NS)\n      msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n      msg.set_arg(OPENID_NS, 'realm', 'http://trustroot.com/')\n      msg.set_arg(OPENID_NS, 'trust_root', 'http://fake_trust_root/')\n      msg.set_arg(OPENID_NS, 'return_to', 'http://trustroot.com/foo')\n      msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')\n      msg.set_arg(OPENID_NS, 'identity', 'george')\n      msg.set_arg(OPENID_NS, 'claimed_id', 'george')\n\n      result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)\n\n      assert(result.trust_root == 'http://trustroot.com/')\n    end\n\n    def test_answerAllowNoTrustRoot\n      @request.trust_root = nil\n      answer = @request.answer(true)\n      assert_equal(answer.request, @request)\n      _expectAnswer(answer, @request.identity)\n    end\n\n    def test_answerImmediateDenyOpenID2\n      # Look for mode=setup_needed in checkid_immediate negative\n      # response in OpenID 2 case.\n      #\n      # See specification Responding to Authentication Requests /\n      # Negative Assertions / In Response to Immediate Requests.\n      @request.mode = 'checkid_immediate'\n      @request.immediate = true\n\n      server_url = \"http://setup-url.unittest/\"\n      # crappiting setup_url, you dirty my interface with your presence!\n      answer = @request.answer(false, server_url)\n      assert_equal(answer.request, @request)\n      assert_equal(answer.fields.to_post_args.length, 3, answer.fields)\n      assert_equal(answer.fields.get_openid_namespace, OPENID2_NS)\n      assert_equal(answer.fields.get_arg(OPENID_NS, 'mode'),\n                   'setup_needed')\n      # user_setup_url no longer required.\n    end\n\n    def test_answerImmediateDenyOpenID1\n      # Look for user_setup_url in checkid_immediate negative response\n      # in OpenID 1 case.\n      @request.message = Message.new(OPENID1_NS)\n      @request.mode = 'checkid_immediate'\n      @request.immediate = true\n      @request.claimed_id = 'http://claimed-id.test/'\n      server_url = \"http://setup-url.unittest/\"\n      # crappiting setup_url, you dirty my interface with your presence!\n      answer = @request.answer(false, server_url)\n      assert_equal(answer.request, @request)\n      assert_equal(2, answer.fields.to_post_args.length, answer.fields)\n      assert_equal(OPENID1_NS, answer.fields.get_openid_namespace)\n      assert_equal('id_res', answer.fields.get_arg(OPENID_NS, 'mode'))\n\n      usu = answer.fields.get_arg(OPENID_NS, 'user_setup_url', '')\n      assert(usu.start_with?(server_url))\n      expected_substr = 'openid.claimed_id=http%3A%2F%2Fclaimed-id.test%2F'\n      assert(!usu.index(expected_substr).nil?, usu)\n    end\n\n    def test_answerSetupDeny\n      answer = @request.answer(false)\n      assert_equal(answer.fields.get_args(OPENID_NS), {\n                     'mode' => 'cancel',\n                   })\n    end\n\n    def test_encodeToURL\n      server_url = 'http://openid-server.unittest/'\n      result = @request.encode_to_url(server_url)\n\n      # How to check?  How about a round-trip test.\n      _, result_args = result.split('?', 2)\n      result_args = Util.parse_query(result_args)\n      message = Message.from_post_args(result_args)\n      rebuilt_request = Server::CheckIDRequest.from_message(message,\n                                                    @server.op_endpoint)\n\n      @request.message = message\n\n      @request.instance_variables.each { |var|\n        assert_equal(@request.instance_variable_get(var),\n                     rebuilt_request.instance_variable_get(var), var)\n      }\n    end\n\n    def test_getCancelURL\n      url = @request.cancel_url\n      rt, query_string = url.split('?', -1)\n      assert_equal(@request.return_to, rt)\n      query = Util.parse_query(query_string)\n      assert_equal(query, {'openid.mode' => 'cancel',\n                     'openid.ns' => OPENID2_NS})\n    end\n\n    def test_getCancelURLimmed\n      @request.mode = 'checkid_immediate'\n      @request.immediate = true\n      assert_raises(ArgumentError) {\n        @request.cancel_url\n      }\n    end\n\n    def test_fromMessageWithoutTrustRoot\n        msg = Message.new(OPENID2_NS)\n        msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n        msg.set_arg(OPENID_NS, 'return_to', 'http://real.trust.root/foo')\n        msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')\n        msg.set_arg(OPENID_NS, 'identity', 'george')\n        msg.set_arg(OPENID_NS, 'claimed_id', 'george')\n\n        result = Server::CheckIDRequest.from_message(msg, @server.op_endpoint)\n\n        assert_equal(result.trust_root, 'http://real.trust.root/foo')\n    end\n\n    def test_fromMessageWithoutTrustRootOrReturnTo\n        msg = Message.new(OPENID2_NS)\n        msg.set_arg(OPENID_NS, 'mode', 'checkid_setup')\n        msg.set_arg(OPENID_NS, 'assoc_handle', 'bogus')\n        msg.set_arg(OPENID_NS, 'identity', 'george')\n        msg.set_arg(OPENID_NS, 'claimed_id', 'george')\n\n        assert_raises(Server::ProtocolError) {\n          Server::CheckIDRequest.from_message(msg, @server.op_endpoint)\n        }\n    end\n  end\n\n  class TestCheckIDExtension < Minitest::Test\n\n    def setup\n      @op_endpoint = 'http://endpoint.unittest/ext'\n      @store = Store::Memory.new()\n      @server = Server::Server.new(@store, @op_endpoint)\n      @request = Server::CheckIDRequest.new(\n                                    'http://bambam.unittest/',\n                                    'http://bar.unittest/999',\n                                    @server.op_endpoint,\n                                    'http://bar.unittest/',\n                                    false)\n      @request.message = Message.new(OPENID2_NS)\n      @response = Server::OpenIDResponse.new(@request)\n      @response.fields.set_arg(OPENID_NS, 'mode', 'id_res')\n      @response.fields.set_arg(OPENID_NS, 'blue', 'star')\n    end\n\n    def test_addField\n      namespace = 'something:'\n      @response.fields.set_arg(namespace, 'bright', 'potato')\n      assert_equal(@response.fields.get_args(OPENID_NS),\n                   {'blue' => 'star',\n                     'mode' => 'id_res',\n                   })\n\n      assert_equal(@response.fields.get_args(namespace),\n                   {'bright' => 'potato'})\n    end\n\n    def test_addFields\n      namespace = 'mi5:'\n      args =  {'tangy' => 'suspenders',\n        'bravo' => 'inclusion'}\n      @response.fields.update_args(namespace, args)\n      assert_equal(@response.fields.get_args(OPENID_NS),\n                   {'blue' => 'star',\n                     'mode' => 'id_res',\n                   })\n      assert_equal(@response.fields.get_args(namespace), args)\n    end\n  end\n\n  class MockSignatory\n    attr_accessor :isValid, :assocs\n\n    def initialize(assoc)\n      @isValid = true\n      @assocs = [assoc]\n    end\n\n    def verify(assoc_handle, message)\n      Util.assert(message.has_key?(OPENID_NS, \"sig\"))\n      if self.assocs.member?([true, assoc_handle])\n        return @isValid\n      else\n        return false\n      end\n    end\n\n    def get_association(assoc_handle, dumb)\n      if self.assocs.member?([dumb, assoc_handle])\n        # This isn't a valid implementation for many uses of this\n        # function, mind you.\n        return true\n      else\n        return nil\n      end\n    end\n\n    def invalidate(assoc_handle, dumb)\n      if self.assocs.member?([dumb, assoc_handle])\n        @assocs.delete([dumb, assoc_handle])\n      end\n    end\n  end\n\n  class TestCheckAuth < Minitest::Test\n    def setup\n      @assoc_handle = 'mooooooooo'\n      @message = Message.from_post_args({\n                                          'openid.sig' => 'signarture',\n                                          'one' => 'alpha',\n                                          'two' => 'beta',\n                                        })\n      @request = Server::CheckAuthRequest.new(\n                                      @assoc_handle, @message)\n      @request.message = Message.new(OPENID2_NS)\n\n      @signatory = MockSignatory.new([true, @assoc_handle])\n    end\n\n    def test_to_s\n      @request.to_s\n    end\n\n    def test_valid\n      r = @request.answer(@signatory)\n      assert_equal({'is_valid' => 'true'},\n                   r.fields.get_args(OPENID_NS))\n      assert_equal(r.request, @request)\n    end\n\n    def test_invalid\n      @signatory.isValid = false\n      r = @request.answer(@signatory)\n      assert_equal({'is_valid' => 'false'},\n                   r.fields.get_args(OPENID_NS))\n\n    end\n\n    def test_replay\n      # Don't validate the same response twice.\n      #\n      # From \"Checking the Nonce\"::\n      #\n      #   When using \"check_authentication\", the OP MUST ensure that an\n      #   assertion has not yet been accepted with the same value for\n      #   \"openid.response_nonce\".\n      #\n      # In this implementation, the assoc_handle is only valid once.\n      # And nonces are a signed component of the message, so they can't\n      # be used with another handle without breaking the sig.\n      r = @request.answer(@signatory)\n      r = @request.answer(@signatory)\n      assert_equal({'is_valid' => 'false'},\n                   r.fields.get_args(OPENID_NS))\n    end\n\n    def test_invalidatehandle\n      @request.invalidate_handle = \"bogusHandle\"\n      r = @request.answer(@signatory)\n      assert_equal(r.fields.get_args(OPENID_NS),\n                   {'is_valid' => 'true',\n                     'invalidate_handle' => \"bogusHandle\"})\n      assert_equal(r.request, @request)\n    end\n\n    def test_invalidatehandleNo\n      assoc_handle = 'goodhandle'\n      @signatory.assocs << [false, 'goodhandle']\n      @request.invalidate_handle = assoc_handle\n      r = @request.answer(@signatory)\n      assert_equal(r.fields.get_args(OPENID_NS), {'is_valid' => 'true'})\n    end\n  end\n\n  class TestAssociate < Minitest::Test\n    # TODO: test DH with non-default values for modulus and gen.\n    # (important to do because we actually had it broken for a while.)\n\n    def setup\n      @request = Server::AssociateRequest.from_message(Message.from_post_args({}))\n      @store = Store::Memory.new()\n      @signatory = Server::Signatory.new(@store)\n    end\n\n    def test_dhSHA1\n      @assoc = @signatory.create_association(false, 'HMAC-SHA1')\n      consumer_dh = DiffieHellman.from_defaults()\n      cpub = consumer_dh.public\n      server_dh = DiffieHellman.from_defaults()\n      session = Server::DiffieHellmanSHA1ServerSession.new(server_dh, cpub)\n      @request = Server::AssociateRequest.new(session, 'HMAC-SHA1')\n      @request.message = Message.new(OPENID2_NS)\n      response = @request.answer(@assoc)\n      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n      assert_equal(rfg.call(\"assoc_type\"), \"HMAC-SHA1\")\n      assert_equal(rfg.call(\"assoc_handle\"), @assoc.handle)\n      assert(!rfg.call(\"mac_key\"))\n      assert_equal(rfg.call(\"session_type\"), \"DH-SHA1\")\n      assert(rfg.call(\"enc_mac_key\"))\n      assert(rfg.call(\"dh_server_public\"))\n\n      enc_key = Util.from_base64(rfg.call(\"enc_mac_key\"))\n      spub = CryptUtil.base64_to_num(rfg.call(\"dh_server_public\"))\n      secret = consumer_dh.xor_secret(CryptUtil.method('sha1'),\n                                      spub, enc_key)\n      assert_equal(secret, @assoc.secret)\n    end\n\n    def test_dhSHA256\n      @assoc = @signatory.create_association(false, 'HMAC-SHA256')\n      consumer_dh = DiffieHellman.from_defaults()\n      cpub = consumer_dh.public\n      server_dh = DiffieHellman.from_defaults()\n      session = Server::DiffieHellmanSHA256ServerSession.new(server_dh, cpub)\n      @request = Server::AssociateRequest.new(session, 'HMAC-SHA256')\n      @request.message = Message.new(OPENID2_NS)\n      response = @request.answer(@assoc)\n      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n      assert_equal(rfg.call(\"assoc_type\"), \"HMAC-SHA256\")\n      assert_equal(rfg.call(\"assoc_handle\"), @assoc.handle)\n      assert(!rfg.call(\"mac_key\"))\n      assert_equal(rfg.call(\"session_type\"), \"DH-SHA256\")\n      assert(rfg.call(\"enc_mac_key\"))\n      assert(rfg.call(\"dh_server_public\"))\n\n      enc_key = Util.from_base64(rfg.call(\"enc_mac_key\"))\n      spub = CryptUtil.base64_to_num(rfg.call(\"dh_server_public\"))\n      secret = consumer_dh.xor_secret(CryptUtil.method('sha256'),\n                                      spub, enc_key)\n      assert_equal(secret, @assoc.secret)\n    end\n\n    def test_protoError256\n      s256_session = Consumer::DiffieHellmanSHA256Session.new()\n\n      invalid_s256 = {'openid.assoc_type' => 'HMAC-SHA1',\n        'openid.session_type' => 'DH-SHA256',}\n      invalid_s256.merge!(s256_session.get_request())\n\n      invalid_s256_2 = {'openid.assoc_type' => 'MONKEY-PIRATE',\n        'openid.session_type' => 'DH-SHA256',}\n      invalid_s256_2.merge!(s256_session.get_request())\n\n      bad_request_argss = [\n                           invalid_s256,\n                           invalid_s256_2,\n                          ]\n\n      bad_request_argss.each { |request_args|\n        message = Message.from_post_args(request_args)\n        assert_raises(Server::ProtocolError) {\n          Server::AssociateRequest.from_message(message)\n        }\n      }\n    end\n\n    def test_protoError\n      s1_session = Consumer::DiffieHellmanSHA1Session.new()\n\n      invalid_s1 = {'openid.assoc_type' => 'HMAC-SHA256',\n        'openid.session_type' => 'DH-SHA1',}\n      invalid_s1.merge!(s1_session.get_request())\n\n      invalid_s1_2 = {'openid.assoc_type' => 'ROBOT-NINJA',\n        'openid.session_type' => 'DH-SHA1',}\n      invalid_s1_2.merge!(s1_session.get_request())\n\n      bad_request_argss = [\n                           {'openid.assoc_type' => 'Wha?'},\n                           invalid_s1,\n                           invalid_s1_2,\n                          ]\n\n      bad_request_argss.each { |request_args|\n        message = Message.from_post_args(request_args)\n        assert_raises(Server::ProtocolError) {\n          Server::AssociateRequest.from_message(message)\n        }\n      }\n    end\n\n    def test_protoErrorFields\n\n      contact = 'user@example.invalid'\n      reference = 'Trac ticket number MAX_INT'\n      error = 'poltergeist'\n\n      openid1_args = {\n        'openid.identitiy' => 'invalid',\n        'openid.mode' => 'checkid_setup',\n      }\n\n      openid2_args = openid1_args.dup\n      openid2_args.merge!({'openid.ns' => OPENID2_NS})\n\n      # Check presence of optional fields in both protocol versions\n\n      openid1_msg = Message.from_post_args(openid1_args)\n      p = Server::ProtocolError.new(openid1_msg, error,\n                                    reference, contact)\n      reply = p.to_message()\n\n      assert_equal(reply.get_arg(OPENID_NS, 'reference'), reference)\n      assert_equal(reply.get_arg(OPENID_NS, 'contact'), contact)\n\n      openid2_msg = Message.from_post_args(openid2_args)\n      p = Server::ProtocolError.new(openid2_msg, error,\n                                    reference, contact)\n      reply = p.to_message()\n\n      assert_equal(reply.get_arg(OPENID_NS, 'reference'), reference)\n      assert_equal(reply.get_arg(OPENID_NS, 'contact'), contact)\n    end\n\n    def failUnlessExpiresInMatches(msg, expected_expires_in)\n      expires_in_str = msg.get_arg(OPENID_NS, 'expires_in', NO_DEFAULT)\n      expires_in = expires_in_str.to_i\n\n      # Slop is necessary because the tests can sometimes get run\n      # right on a second boundary\n      slop = 1 # second\n      difference = expected_expires_in - expires_in\n\n      error_message = sprintf('\"expires_in\" value not within %s of expected: ' +\n                              'expected=%s, actual=%s', slop, expected_expires_in,\n                              expires_in)\n      assert((0 <= difference and difference <= slop), error_message)\n    end\n\n    def test_plaintext\n      @assoc = @signatory.create_association(false, 'HMAC-SHA1')\n      response = @request.answer(@assoc)\n      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n\n      assert_equal(rfg.call(\"assoc_type\"), \"HMAC-SHA1\")\n      assert_equal(rfg.call(\"assoc_handle\"), @assoc.handle)\n\n      failUnlessExpiresInMatches(response.fields,\n                                 @signatory.secret_lifetime)\n\n      assert_equal(\n                   rfg.call(\"mac_key\"), Util.to_base64(@assoc.secret))\n      assert(!rfg.call(\"session_type\"))\n      assert(!rfg.call(\"enc_mac_key\"))\n      assert(!rfg.call(\"dh_server_public\"))\n    end\n\n    def test_plaintext_v2\n        # The main difference between this and the v1 test is that\n        # session_type is always returned in v2.\n        args = {\n            'openid.ns' => OPENID2_NS,\n            'openid.mode' => 'associate',\n            'openid.assoc_type' => 'HMAC-SHA1',\n            'openid.session_type' => 'no-encryption',\n            }\n        @request = Server::AssociateRequest.from_message(\n          Message.from_post_args(args))\n\n        assert(!@request.message.is_openid1())\n\n        @assoc = @signatory.create_association(false, 'HMAC-SHA1')\n        response = @request.answer(@assoc)\n        rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n\n        assert_equal(rfg.call(\"assoc_type\"), \"HMAC-SHA1\")\n        assert_equal(rfg.call(\"assoc_handle\"), @assoc.handle)\n\n        failUnlessExpiresInMatches(\n            response.fields, @signatory.secret_lifetime)\n\n        assert_equal(\n            rfg.call(\"mac_key\"), Util.to_base64(@assoc.secret))\n\n        assert_equal(rfg.call(\"session_type\"), \"no-encryption\")\n        assert(!rfg.call(\"enc_mac_key\"))\n        assert(!rfg.call(\"dh_server_public\"))\n    end\n\n    def test_plaintext256\n      @assoc = @signatory.create_association(false, 'HMAC-SHA256')\n      response = @request.answer(@assoc)\n      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n\n      assert_equal(rfg.call(\"assoc_type\"), \"HMAC-SHA1\")\n      assert_equal(rfg.call(\"assoc_handle\"), @assoc.handle)\n\n      failUnlessExpiresInMatches(\n                                 response.fields, @signatory.secret_lifetime)\n\n      assert_equal(\n                   rfg.call(\"mac_key\"), Util.to_base64(@assoc.secret))\n      assert(!rfg.call(\"session_type\"))\n      assert(!rfg.call(\"enc_mac_key\"))\n      assert(!rfg.call(\"dh_server_public\"))\n    end\n\n    def test_unsupportedPrefer\n      allowed_assoc = 'COLD-PET-RAT'\n      allowed_sess = 'FROG-BONES'\n      message = 'This is a unit test'\n\n      # Set an OpenID 2 message so answerUnsupported doesn't raise\n      # ProtocolError.\n      @request.message = Message.new(OPENID2_NS)\n\n      response = @request.answer_unsupported(message,\n                                             allowed_assoc,\n                                             allowed_sess)\n      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n      assert_equal(rfg.call('error_code'), 'unsupported-type')\n      assert_equal(rfg.call('assoc_type'), allowed_assoc)\n      assert_equal(rfg.call('error'), message)\n      assert_equal(rfg.call('session_type'), allowed_sess)\n    end\n\n    def test_unsupported\n      message = 'This is a unit test'\n\n      # Set an OpenID 2 message so answerUnsupported doesn't raise\n      # ProtocolError.\n      @request.message = Message.new(OPENID2_NS)\n\n      response = @request.answer_unsupported(message)\n      rfg = lambda { |f| response.fields.get_arg(OPENID_NS, f) }\n      assert_equal(rfg.call('error_code'), 'unsupported-type')\n      assert_nil(rfg.call('assoc_type'))\n      assert_equal(rfg.call('error'), message)\n      assert_nil(rfg.call('session_type'))\n    end\n\n    def test_openid1_unsupported_explode\n      # answer_unsupported on an associate request should explode if\n      # the request was an OpenID 1 request.\n      m = Message.new(OPENID1_NS)\n\n      assert_raises(Server::ProtocolError) {\n        @request.answer_unsupported(m)\n      }\n    end\n  end\n\n  class UnhandledError < Exception\n  end\n\n  class TestServer < Minitest::Test\n    include TestUtil\n\n    def setup\n      @store = Store::Memory.new()\n      @server = Server::Server.new(@store, \"http://server.unittest/endpt\")\n      # catchlogs_setup()\n    end\n\n    def test_failed_dispatch\n      request = Server::OpenIDRequest.new()\n      request.mode = \"monkeymode\"\n      request.message = Message.new(OPENID1_NS)\n      assert_raises(RuntimeError) {\n        @server.handle_request(request)\n      }\n    end\n\n    def test_decode_request\n      @server.decoder = BogusDecoder.new(@server)\n      assert(@server.decode_request({}) == \"BOGUS\")\n    end\n\n    def test_encode_response\n      @server.encoder = BogusEncoder.new\n      assert(@server.encode_response(nil) == \"BOGUS\")\n    end\n\n    def test_dispatch\n      @server.extend(InstanceDefExtension)\n      @server.instance_def(:openid_monkeymode) do |request|\n        raise UnhandledError\n      end\n\n      request = Server::OpenIDRequest.new()\n      request.mode = \"monkeymode\"\n      request.message = Message.new(OPENID1_NS)\n      assert_raises(UnhandledError) {\n        @server.handle_request(request)\n      }\n    end\n\n    def test_associate\n      request = Server::AssociateRequest.from_message(Message.from_post_args({}))\n      response = @server.openid_associate(request)\n      assert(response.fields.has_key?(OPENID_NS, \"assoc_handle\"),\n             sprintf(\"No assoc_handle here: %s\", response.fields.inspect))\n    end\n\n    def test_associate2\n      # Associate when the server has no allowed association types\n      #\n      # Gives back an error with error_code and no fallback session or\n      # assoc types.\n      @server.negotiator.allowed_types = []\n\n      # Set an OpenID 2 message so answerUnsupported doesn't raise\n      # ProtocolError.\n      msg = Message.from_post_args({\n                                     'openid.ns' => OPENID2_NS,\n                                     'openid.session_type' => 'no-encryption',\n                                   })\n\n      request = Server::AssociateRequest.from_message(msg)\n\n      response = @server.openid_associate(request)\n      assert(response.fields.has_key?(OPENID_NS, \"error\"))\n      assert(response.fields.has_key?(OPENID_NS, \"error_code\"))\n      assert(!response.fields.has_key?(OPENID_NS, \"assoc_handle\"))\n      assert(!response.fields.has_key?(OPENID_NS, \"assoc_type\"))\n      assert(!response.fields.has_key?(OPENID_NS, \"session_type\"))\n    end\n\n    def test_associate3\n      # Request an assoc type that is not supported when there are\n      # supported types.\n      #\n      # Should give back an error message with a fallback type.\n      @server.negotiator.allowed_types = [['HMAC-SHA256', 'DH-SHA256']]\n\n      msg = Message.from_post_args({\n                                     'openid.ns' => OPENID2_NS,\n                                     'openid.session_type' => 'no-encryption',\n                                   })\n\n      request = Server::AssociateRequest.from_message(msg)\n      response = @server.openid_associate(request)\n\n      assert(response.fields.has_key?(OPENID_NS, \"error\"))\n      assert(response.fields.has_key?(OPENID_NS, \"error_code\"))\n      assert(!response.fields.has_key?(OPENID_NS, \"assoc_handle\"))\n\n      assert_equal(response.fields.get_arg(OPENID_NS, \"assoc_type\"),\n                   'HMAC-SHA256')\n      assert_equal(response.fields.get_arg(OPENID_NS, \"session_type\"),\n                   'DH-SHA256')\n    end\n\n    def test_associate4\n      # DH-SHA256 association session\n      @server.negotiator.allowed_types = [['HMAC-SHA256', 'DH-SHA256']]\n\n      query = {\n        'openid.dh_consumer_public' =>\n        'ALZgnx8N5Lgd7pCj8K86T/DDMFjJXSss1SKoLmxE72kJTzOtG6I2PaYrHX' +\n        'xku4jMQWSsGfLJxwCZ6280uYjUST/9NWmuAfcrBfmDHIBc3H8xh6RBnlXJ' +\n        '1WxJY3jHd5k1/ZReyRZOxZTKdF/dnIqwF8ZXUwI6peV0TyS/K1fOfF/s',\n\n        'openid.assoc_type' => 'HMAC-SHA256',\n        'openid.session_type' => 'DH-SHA256',\n      }\n\n      message = Message.from_post_args(query)\n      request = Server::AssociateRequest.from_message(message)\n      response = @server.openid_associate(request)\n      assert(response.fields.has_key?(OPENID_NS, \"assoc_handle\"))\n    end\n\n    def test_no_encryption_openid1\n      # Make sure no-encryption associate requests for OpenID 1 are\n      # logged.\n      assert_log_matches(/Continuing anyway./) {\n        m = Message.from_openid_args({\n                                       'session_type' => 'no-encryption',\n                                     })\n\n        Server::AssociateRequest.from_message(m)\n      }\n    end\n\n    def test_missingSessionTypeOpenID2\n      # Make sure session_type is required in OpenID 2\n      msg = Message.from_post_args({\n                                     'openid.ns' => OPENID2_NS,\n                                   })\n\n      assert_raises(Server::ProtocolError) {\n        Server::AssociateRequest.from_message(msg)\n      }\n    end\n\n    def test_checkAuth\n      request = Server::CheckAuthRequest.new('arrrrrf', '0x3999', [])\n      request.message = Message.new(OPENID2_NS)\n      response = nil\n      silence_logging {\n        response = @server.openid_check_authentication(request)\n      }\n      assert(response.fields.has_key?(OPENID_NS, \"is_valid\"))\n    end\n  end\n\n  class TestingRequest < Server::OpenIDRequest\n    attr_accessor :assoc_handle, :namespace\n  end\n\n  class TestSignatory < Minitest::Test\n    include TestUtil\n\n    def setup\n      @store = Store::Memory.new()\n      @signatory = Server::Signatory.new(@store)\n      @_dumb_key = @signatory.class._dumb_key\n      @_normal_key = @signatory.class._normal_key\n      # CatchLogs.setUp(self)\n    end\n\n    def test_get_association_nil\n      assert_raises(ArgumentError) {\n        @signatory.get_association(nil, false)\n      }\n    end\n\n    def test_sign\n      request = TestingRequest.new()\n      assoc_handle = '{assoc}{lookatme}'\n      @store.store_association(\n                               @_normal_key,\n                               Association.from_expires_in(60, assoc_handle,\n                                                           'sekrit', 'HMAC-SHA1'))\n      request.assoc_handle = assoc_handle\n      request.namespace = OPENID1_NS\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'foo' => 'amsigned',\n                                                   'bar' => 'notsigned',\n                                                   'azu' => 'alsosigned',\n                                                 })\n      sresponse = @signatory.sign(response)\n      assert_equal(\n            sresponse.fields.get_arg(OPENID_NS, 'assoc_handle'),\n            assoc_handle)\n      assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),\n                   'assoc_handle,azu,bar,foo,signed')\n      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))\n      # assert(!@messages, @messages)\n    end\n\n    def test_signDumb\n      request = TestingRequest.new()\n      request.assoc_handle = nil\n      request.namespace = OPENID2_NS\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'foo' => 'amsigned',\n                                                   'bar' => 'notsigned',\n                                                   'azu' => 'alsosigned',\n                                                   'ns' => OPENID2_NS,\n                                                 })\n      sresponse = @signatory.sign(response)\n      assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')\n      assert(assoc_handle)\n      assoc = @signatory.get_association(assoc_handle, true)\n      assert(assoc)\n      assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),\n                   'assoc_handle,azu,bar,foo,ns,signed')\n      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))\n      # assert(!@messages, @messages)\n    end\n\n    def test_signExpired\n      # Sign a response to a message with an expired handle (using\n      # invalidate_handle).\n      #\n      # From \"Verifying with an Association\":\n      #\n      #   If an authentication request included an association handle\n      #   for an association between the OP and the Relying party, and\n      #   the OP no longer wishes to use that handle (because it has\n      #   expired or the secret has been compromised, for instance),\n      #   the OP will send a response that must be verified directly\n      #   with the OP, as specified in Section 11.3.2. In that\n      #   instance, the OP will include the field\n      #   \"openid.invalidate_handle\" set to the association handle\n      #   that the Relying Party included with the original request.\n      request = TestingRequest.new()\n      request.namespace = OPENID2_NS\n      assoc_handle = '{assoc}{lookatme}'\n      @store.store_association(\n                               @_normal_key,\n                               Association.from_expires_in(-10, assoc_handle,\n                                                           'sekrit', 'HMAC-SHA1'))\n      assert(@store.get_association(@_normal_key, assoc_handle))\n\n      request.assoc_handle = assoc_handle\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'foo' => 'amsigned',\n                                                   'bar' => 'notsigned',\n                                                   'azu' => 'alsosigned',\n                                                 })\n      sresponse = nil\n      silence_logging {\n        sresponse = @signatory.sign(response)\n      }\n\n      new_assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')\n      assert(new_assoc_handle)\n      assert(new_assoc_handle != assoc_handle)\n\n      assert_equal(\n            sresponse.fields.get_arg(OPENID_NS, 'invalidate_handle'),\n            assoc_handle)\n\n      assert_equal(sresponse.fields.get_arg(OPENID_NS, 'signed'),\n                   'assoc_handle,azu,bar,foo,invalidate_handle,signed')\n      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))\n\n      # make sure the expired association is gone\n      assert(!@store.get_association(@_normal_key, assoc_handle),\n             \"expired association is still retrievable.\")\n\n      # make sure the new key is a dumb mode association\n      assert(@store.get_association(@_dumb_key, new_assoc_handle))\n      assert(!@store.get_association(@_normal_key, new_assoc_handle))\n      # assert(@messages)\n    end\n\n    def test_signInvalidHandle\n      request = TestingRequest.new()\n      request.namespace = OPENID2_NS\n      assoc_handle = '{bogus-assoc}{notvalid}'\n\n      request.assoc_handle = assoc_handle\n      response = Server::OpenIDResponse.new(request)\n      response.fields = Message.from_openid_args({\n                                                   'foo' => 'amsigned',\n                                                   'bar' => 'notsigned',\n                                                   'azu' => 'alsosigned',\n                                                 })\n      sresponse = @signatory.sign(response)\n\n      new_assoc_handle = sresponse.fields.get_arg(OPENID_NS, 'assoc_handle')\n      assert(new_assoc_handle)\n      assert(new_assoc_handle != assoc_handle)\n\n      assert_equal(\n            sresponse.fields.get_arg(OPENID_NS, 'invalidate_handle'),\n            assoc_handle)\n\n      assert_equal(\n            sresponse.fields.get_arg(OPENID_NS, 'signed'),\n                   'assoc_handle,azu,bar,foo,invalidate_handle,signed')\n      assert(sresponse.fields.get_arg(OPENID_NS, 'sig'))\n\n      # make sure the new key is a dumb mode association\n      assert(@store.get_association(@_dumb_key, new_assoc_handle))\n      assert(!@store.get_association(@_normal_key, new_assoc_handle))\n      # @failIf(@messages, @messages)\n    end\n\n    def test_verify\n      assoc_handle = '{vroom}{zoom}'\n      assoc = Association.from_expires_in(\n            60, assoc_handle, 'sekrit', 'HMAC-SHA1')\n\n      @store.store_association(@_dumb_key, assoc)\n\n      signed = Message.from_post_args({\n                                        'openid.foo' => 'bar',\n                                        'openid.apple' => 'orange',\n                                        'openid.assoc_handle' => assoc_handle,\n                                        'openid.signed' => 'apple,assoc_handle,foo,signed',\n                                        'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco=',\n                                      })\n\n      verified = @signatory.verify(assoc_handle, signed)\n      assert(verified)\n      # assert(!@messages, @messages)\n    end\n\n    def test_verifyBadSig\n      assoc_handle = '{vroom}{zoom}'\n      assoc = Association.from_expires_in(\n            60, assoc_handle, 'sekrit', 'HMAC-SHA1')\n\n      @store.store_association(@_dumb_key, assoc)\n\n      signed = Message.from_post_args({\n            'openid.foo' => 'bar',\n            'openid.apple' => 'orange',\n            'openid.assoc_handle' => assoc_handle,\n            'openid.signed' => 'apple,assoc_handle,foo,signed',\n            'openid.sig' => 'uXoT1qm62/BB09Xbj98TQ8mlBco=BOGUS'\n            })\n\n      verified = @signatory.verify(assoc_handle, signed)\n      # @failIf(@messages, @messages)\n      assert(!verified)\n    end\n\n    def test_verifyBadHandle\n      assoc_handle = '{vroom}{zoom}'\n      signed = Message.from_post_args({\n            'foo' => 'bar',\n            'apple' => 'orange',\n            'openid.sig' => \"Ylu0KcIR7PvNegB/K41KpnRgJl0=\",\n            })\n\n      verified = nil\n      silence_logging {\n        verified = @signatory.verify(assoc_handle, signed)\n      }\n\n      assert !verified\n    end\n\n    def test_verifyAssocMismatch\n      # Attempt to validate sign-all message with a signed-list assoc.\n      assoc_handle = '{vroom}{zoom}'\n      assoc = Association.from_expires_in(\n            60, assoc_handle, 'sekrit', 'HMAC-SHA1')\n\n      @store.store_association(@_dumb_key, assoc)\n\n      signed = Message.from_post_args({\n            'foo' => 'bar',\n            'apple' => 'orange',\n            'openid.sig' => \"d71xlHtqnq98DonoSgoK/nD+QRM=\",\n            })\n\n      verified = nil\n      silence_logging {\n        verified = @signatory.verify(assoc_handle, signed)\n      }\n\n      assert !verified\n    end\n\n    def test_getAssoc\n      assoc_handle = makeAssoc(true)\n      assoc = @signatory.get_association(assoc_handle, true)\n      assert assoc\n      assert_equal assoc.handle, assoc_handle\n    end\n\n    def test_getAssocExpired\n      assoc_handle = makeAssoc(true, -10)\n      assoc = nil\n      silence_logging {\n        assoc = @signatory.get_association(assoc_handle, true)\n      }\n      assert !assoc\n    end\n\n    def test_getAssocInvalid\n      ah = 'no-such-handle'\n      silence_logging {\n        assert_nil(@signatory.get_association(ah, false))\n      }\n      # assert(!@messages, @messages)\n    end\n\n    def test_getAssocDumbVsNormal\n      # getAssociation(dumb=False) cannot get a dumb assoc\n      assoc_handle = makeAssoc(true)\n      silence_logging {\n        assert_nil(@signatory.get_association(assoc_handle, false))\n      }\n      # @failIf(@messages, @messages)\n    end\n\n    def test_getAssocNormalVsDumb\n      # getAssociation(dumb=True) cannot get a shared assoc\n      #\n      # From \"Verifying Directly with the OpenID Provider\"::\n      #\n      #   An OP MUST NOT verify signatures for associations that have shared\n      #   MAC keys.\n      assoc_handle = makeAssoc(false)\n      silence_logging {\n        assert_nil(@signatory.get_association(assoc_handle, true))\n      }\n      # @failIf(@messages, @messages)\n    end\n\n    def test_createAssociation\n      assoc = @signatory.create_association(false)\n      silence_logging {\n        assert(@signatory.get_association(assoc.handle, false))\n      }\n      # @failIf(@messages, @messages)\n    end\n\n    def makeAssoc(dumb, lifetime=60)\n      assoc_handle = '{bling}'\n      assoc = Association.from_expires_in(lifetime, assoc_handle,\n                                          'sekrit', 'HMAC-SHA1')\n\n      silence_logging {\n        @store.store_association(((dumb and @_dumb_key) or @_normal_key), assoc)\n      }\n\n      return assoc_handle\n    end\n\n    def test_invalidate\n      assoc_handle = '-squash-'\n      assoc = Association.from_expires_in(60, assoc_handle,\n                                          'sekrit', 'HMAC-SHA1')\n\n      silence_logging {\n        @store.store_association(@_dumb_key, assoc)\n        assoc = @signatory.get_association(assoc_handle, true)\n        assert(assoc)\n        assoc = @signatory.get_association(assoc_handle, true)\n        assert(assoc)\n        @signatory.invalidate(assoc_handle, true)\n        assoc = @signatory.get_association(assoc_handle, true)\n        assert(!assoc)\n      }\n      # @failIf(@messages, @messages)\n    end\n  end\n\n  class RunthroughTestCase < Minitest::Test\n    def setup\n      @store = Store::Memory.new\n      @server = Server::Server.new(@store, \"http://example.com/openid/server\")\n    end\n\n    def test_openid1_assoc_checkid\n      assoc_args = {'openid.mode' => 'associate',\n                    'openid.assoc_type' => 'HMAC-SHA1'}\n      areq = @server.decode_request(assoc_args)\n      aresp = @server.handle_request(areq)\n\n      amess = aresp.fields\n      assert(amess.is_openid1)\n      ahandle = amess.get_arg(OPENID_NS, 'assoc_handle')\n      assert(ahandle)\n      assoc = @store.get_association('http://localhost/|normal', ahandle)\n      assert(assoc.is_a?(Association))\n\n\n      checkid_args = {'openid.mode' => 'checkid_setup',\n                      'openid.return_to' => 'http://example.com/openid/consumer',\n                      'openid.assoc_handle' => ahandle,\n                      'openid.identity' => 'http://foo.com/'}\n\n      cireq = @server.decode_request(checkid_args)\n      ciresp = cireq.answer(true)\n\n      signed_resp = @server.signatory.sign(ciresp)\n\n      assert_equal(assoc.get_message_signature(signed_resp.fields),\n                   signed_resp.fields.get_arg(OPENID_NS, 'sig'))\n\n      assert(assoc.check_message_signature(signed_resp.fields))\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/test_sreg.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/extensions/sreg'\nrequire 'openid/message'\nrequire 'openid/server'\n\nmodule OpenID\n  module SReg\n    module SRegTest\n      SOME_DATA = {\n        'nickname'=>'linusaur',\n        'postcode'=>'12345',\n        'country'=>'US',\n        'gender'=>'M',\n        'fullname'=>'Leonhard Euler',\n        'email'=>'president@whitehouse.gov',\n        'dob'=>'0000-00-00',\n        'language'=>'en-us',\n      }\n\n      class SRegTest < Minitest::Test\n\n        def test_is11\n          assert_equal(NS_URI, NS_URI_1_1)\n        end\n\n        def test_check_field_name\n          DATA_FIELDS.keys.each{|field_name|\n            OpenID::check_sreg_field_name(field_name)\n          }\n          assert_raises(ArgumentError) { OpenID::check_sreg_field_name('invalid') }\n          assert_raises(ArgumentError) { OpenID::check_sreg_field_name(nil) }\n        end\n\n        def test_unsupported\n          endpoint = FakeEndpoint.new([])\n          assert(!OpenID::supports_sreg?(endpoint))\n          assert_equal([NS_URI_1_1,NS_URI_1_0], endpoint.checked_uris)\n        end\n\n        def test_supported_1_1\n          endpoint = FakeEndpoint.new([NS_URI_1_1])\n          assert(OpenID::supports_sreg?(endpoint))\n          assert_equal([NS_URI_1_1], endpoint.checked_uris)\n        end\n\n        def test_supported_1_0\n          endpoint = FakeEndpoint.new([NS_URI_1_0])\n          assert(OpenID::supports_sreg?(endpoint))\n          assert_equal([NS_URI_1_1,NS_URI_1_0], endpoint.checked_uris)\n        end\n\n      end\n\n      class FakeEndpoint < Object\n        attr_accessor :checked_uris\n        def initialize(supported)\n          @supported = supported\n          @checked_uris = []\n        end\n\n        def uses_extension(namespace_uri)\n          @checked_uris << namespace_uri\n          return @supported.member?(namespace_uri)\n        end\n      end\n\n      class FakeMessage < Object\n        attr_accessor :namespaces\n        attr_accessor :openid1\n        def initialize\n          @openid1 = false\n          @namespaces = NamespaceMap.new\n        end\n\n        def is_openid1\n          return @openid1\n        end\n\n      end\n\n      class GetNSTest < Minitest::Test\n        def setup\n          @msg = FakeMessage.new\n        end\n\n        def test_openid2_empty\n          ns_uri = OpenID::get_sreg_ns(@msg)\n          assert_equal('sreg', @msg.namespaces.get_alias(ns_uri))\n          assert_equal(NS_URI, ns_uri)\n        end\n\n        def test_openid1_empty\n          @msg.openid1 = true\n          ns_uri = OpenID::get_sreg_ns(@msg)\n          assert_equal('sreg', @msg.namespaces.get_alias(ns_uri))\n          assert_equal(NS_URI, ns_uri)\n        end\n\n        def test_openid1defined_1_0\n          @msg.openid1 = true\n          @msg.namespaces.add(NS_URI_1_0)\n          ns_uri = OpenID::get_sreg_ns(@msg)\n          assert_equal(NS_URI_1_0, ns_uri)\n        end\n\n        def test_openid1_defined_1_0_override_alias\n          [true, false].each{|openid_version|\n            [NS_URI_1_0, NS_URI_1_1].each{|sreg_version|\n              ['sreg', 'bogus'].each{|name|\n                setup\n                @msg.openid1 = openid_version\n                @msg.namespaces.add_alias(sreg_version, name)\n                ns_uri = OpenID::get_sreg_ns(@msg)\n                assert_equal(name, @msg.namespaces.get_alias(ns_uri))\n                assert_equal(sreg_version, ns_uri)\n              }\n            }\n          }\n        end\n\n        def test_openid1_defined_badly\n          @msg.openid1 = true\n          @msg.namespaces.add_alias('http://invalid/', 'sreg')\n          assert_raises(NamespaceError) { OpenID::get_sreg_ns(@msg) }\n        end\n\n        def test_openid2_defined_badly\n          @msg.namespaces.add_alias('http://invalid/', 'sreg')\n          assert_raises(NamespaceError) { OpenID::get_sreg_ns(@msg) }\n        end\n\n        def test_openid2_defined_1_0\n          @msg.namespaces.add(NS_URI_1_0)\n          ns_uri = OpenID::get_sreg_ns(@msg)\n          assert_equal(NS_URI_1_0, ns_uri)\n        end\n\n        def test_openid1_sreg_ns_from_args\n          args = {\n            'sreg.optional'=> 'nickname',\n            'sreg.required'=> 'dob',\n          }\n\n          m = Message.from_openid_args(args)\n\n          assert_equal('nickname', m.get_arg(NS_URI_1_1, 'optional'))\n          assert_equal('dob', m.get_arg(NS_URI_1_1, 'required'))\n        end\n\n      end\n\n      class SRegRequestTest < Minitest::Test\n        def test_construct_empty\n          req = Request.new\n          assert_equal([], req.optional)\n          assert_equal([], req.required)\n          assert_nil(req.policy_url)\n          assert_equal(NS_URI, req.ns_uri)\n        end\n\n        def test_construct_fields\n          req = Request.new(['nickname'],['gender'],'http://policy', 'http://sreg.ns_uri')\n          assert_equal(['gender'], req.optional)\n          assert_equal(['nickname'], req.required)\n          assert_equal('http://policy', req.policy_url)\n          assert_equal('http://sreg.ns_uri', req.ns_uri)\n        end\n\n        def test_construct_bad_fields\n          assert_raises(ArgumentError) {Request.new(['elvis'])}\n        end\n\n        def test_from_openid_request_message_copied\n          message = Message.from_openid_args({\"sreg.required\" => \"nickname\"})\n          openid_req = Server::OpenIDRequest.new\n          openid_req.message = message\n          sreg_req = Request.from_openid_request(openid_req)\n          # check that the message is copied by looking at sreg namespace\n          assert_equal(NS_URI_1_1, message.namespaces.get_namespace_uri('sreg'))\n          assert_equal(NS_URI, sreg_req.ns_uri)\n          assert_equal(['nickname'], sreg_req.required)\n        end\n\n        def test_from_openid_request_ns_1_0\n          message = Message.from_openid_args({'ns.sreg' => NS_URI_1_0,\n                                               \"sreg.required\" => \"nickname\"})\n          openid_req = Server::OpenIDRequest.new\n          openid_req.message = message\n          sreg_req = Request.from_openid_request(openid_req)\n          assert_equal(NS_URI_1_0, sreg_req.ns_uri)\n          assert_equal(['nickname'], sreg_req.required)\n        end\n\n        def test_from_openid_request_no_sreg\n          message = Message.new\n          openid_req = Server::OpenIDRequest.new\n          openid_req.message = message\n          sreg_req = Request.from_openid_request(openid_req)\n          assert(sreg_req.nil?)\n        end\n\n        def test_parse_extension_args_empty\n          req = Request.new\n          req.parse_extension_args({})\n        end\n\n        def test_parse_extension_args_extra_ignored\n          req = Request.new\n          req.parse_extension_args({'extra' => 'stuff'})\n        end\n\n        def test_parse_extension_args_non_strict\n          req = Request.new\n          req.parse_extension_args({'required' => 'stuff'})\n          assert_equal([], req.required)\n        end\n\n        def test_parse_extension_args_strict\n          req = Request.new\n          assert_raises(ArgumentError) {\n            req.parse_extension_args({'required' => 'stuff'}, true)\n          }\n        end\n\n        def test_parse_extension_args_policy\n          req = Request.new\n          req.parse_extension_args({'policy_url' => 'http://policy'}, true)\n          assert_equal('http://policy', req.policy_url)\n        end\n\n        def test_parse_extension_args_required_empty\n          req = Request.new\n          req.parse_extension_args({'required' => ''}, true)\n          assert_equal([], req.required)\n        end\n\n        def test_parse_extension_args_optional_empty\n          req = Request.new\n          req.parse_extension_args({'optional' => ''},true)\n          assert_equal([], req.optional)\n        end\n\n        def test_parse_extension_args_optional_single\n          req = Request.new\n          req.parse_extension_args({'optional' => 'nickname'},true)\n          assert_equal(['nickname'], req.optional)\n        end\n\n        def test_parse_extension_args_optional_list\n          req = Request.new\n          req.parse_extension_args({'optional' => 'nickname,email'},true)\n          assert_equal(['nickname','email'], req.optional)\n        end\n\n        def test_parse_extension_args_optional_list_bad_nonstrict\n          req = Request.new\n          req.parse_extension_args({'optional' => 'nickname,email,beer'})\n          assert_equal(['nickname','email'], req.optional)\n        end\n\n        def test_parse_extension_args_optional_list_bad_strict\n          req = Request.new\n          assert_raises(ArgumentError) {\n            req.parse_extension_args({'optional' => 'nickname,email,beer'}, true)\n          }\n        end\n\n        def test_parse_extension_args_both_nonstrict\n          req = Request.new\n          req.parse_extension_args({'optional' => 'nickname', 'required' => 'nickname'})\n          assert_equal(['nickname'], req.required)\n          assert_equal([], req.optional)\n        end\n\n        def test_parse_extension_args_both_strict\n          req = Request.new\n          assert_raises(ArgumentError) {\n            req.parse_extension_args({'optional' => 'nickname', 'required' => 'nickname'},true)\n          }\n        end\n\n        def test_parse_extension_args_both_list\n          req = Request.new\n          req.parse_extension_args({'optional' => 'nickname,email', 'required' => 'country,postcode'},true)\n          assert_equal(['nickname','email'], req.optional)\n          assert_equal(['country','postcode'], req.required)\n        end\n\n        def test_all_requested_fields\n          req = Request.new\n          assert_equal([], req.all_requested_fields)\n          req.request_field('nickname')\n          assert_equal(['nickname'], req.all_requested_fields)\n          req.request_field('gender', true)\n          requested = req.all_requested_fields.sort\n          assert_equal(['gender', 'nickname'], requested)\n        end\n\n        def test_were_fields_requested\n          req = Request.new\n          assert(!req.were_fields_requested?)\n          req.request_field('nickname')\n          assert(req.were_fields_requested?)\n        end\n\n        def test_member\n          req = Request.new\n          DATA_FIELDS.keys.each {|f|\n            assert(!req.member?(f))\n          }\n          assert(!req.member?('something else'))\n          req.request_field('nickname')\n          DATA_FIELDS.keys.each {|f|\n            assert_equal(f == 'nickname',req.member?(f))\n          }\n        end\n\n        def test_request_field_bogus\n          req = Request.new\n          fields = DATA_FIELDS.keys\n          fields.each {|f| req.request_field(f) }\n          assert_equal(fields, req.optional)\n          assert_equal([], req.required)\n\n          # By default, adding the same fields over again has no effect\n          fields.each {|f| req.request_field(f) }\n          assert_equal(fields, req.optional)\n          assert_equal([], req.required)\n\n          # Requesting a field as required overrides requesting it as optional\n          expected = fields[1..-1]\n          overridden = fields[0]\n          req.request_field(overridden, true)\n          assert_equal(expected, req.optional)\n          assert_equal([overridden], req.required)\n\n          fields.each {|f| req.request_field(f, true) }\n          assert_equal(fields, req.required)\n          assert_equal([], req.optional)\n        end\n\n        def test_request_fields_type\n          req = Request.new\n          assert_raises(ArgumentError) { req.request_fields('nickname') }\n        end\n\n        def test_request_fields\n          req = Request.new\n          fields = DATA_FIELDS.keys\n\n          req.request_fields(fields)\n          assert_equal(fields, req.optional)\n          assert_equal([], req.required)\n\n          # By default, adding the same fields over again has no effect\n          req.request_fields(fields)\n          assert_equal(fields, req.optional)\n          assert_equal([], req.required)\n\n          # required overrides optional\n          expected = fields[1..-1]\n          overridden = fields[0]\n          req.request_fields([overridden], true)\n          assert_equal(expected, req.optional)\n          assert_equal([overridden], req.required)\n\n          req.request_fields(fields, true)\n          assert_equal(fields, req.required)\n          assert_equal([], req.optional)\n\n          # optional does not override required\n          req.request_fields(fields)\n          assert_equal(fields, req.required)\n          assert_equal([], req.optional)\n        end\n\n        def test_get_extension_args\n          req = Request.new\n          assert_equal({}, req.get_extension_args)\n\n          req.request_field('nickname')\n          assert_equal({'optional' => 'nickname'}, req.get_extension_args)\n\n          req.request_field('email')\n          assert_equal({'optional' => 'nickname,email'}, req.get_extension_args)\n\n          req.request_field('gender', true)\n          assert_equal({'optional' => 'nickname,email',\n                         'required' => 'gender'}, req.get_extension_args)\n\n          req.request_field('dob', true)\n          assert_equal({'optional' => 'nickname,email',\n                         'required' => 'gender,dob'}, req.get_extension_args)\n\n          req.policy_url = 'http://policy'\n          assert_equal({'optional' => 'nickname,email',\n                         'required' => 'gender,dob',\n                         'policy_url' => 'http://policy'},\n                       req.get_extension_args)\n\n        end\n      end\n\n      class DummySuccessResponse\n        attr_accessor :message\n        def initialize(message, signed_stuff)\n          @message = message\n          @signed_stuff = signed_stuff\n        end\n        def get_signed_ns(ns_uri)\n          return @signed_stuff\n        end\n      end\n\n\n      class SRegResponseTest < Minitest::Test\n        def test_construct\n          resp = Response.new(SOME_DATA)\n          assert_equal(SOME_DATA, resp.get_extension_args)\n          assert_equal(NS_URI, resp.ns_uri)\n          resp2 = Response.new({}, \"http://foo\")\n          assert_equal({}, resp2.get_extension_args)\n          assert_equal('http://foo', resp2.ns_uri)\n        end\n\n        def test_from_success_response_signed\n          message = Message.from_openid_args({\n                                               'sreg.nickname'=>'The Mad Stork',\n                                             })\n          success_resp = DummySuccessResponse.new(message, {})\n          sreg_resp = Response.from_success_response(success_resp)\n          assert_equal({}, sreg_resp.get_extension_args)\n        end\n\n        def test_from_success_response_unsigned\n          message = Message.from_openid_args({\n                                               'ns.sreg' => NS_URI,\n                                               'sreg.nickname' => 'The Mad Stork',\n                                             })\n          success_resp = DummySuccessResponse.new(message, {})\n          sreg_resp = Response.from_success_response(success_resp, false)\n          assert_equal({'nickname' => 'The Mad Stork'},\n                       sreg_resp.get_extension_args)\n        end\n      end\n\n      class SendFieldsTest < Minitest::Test\n        # class SendFieldsTest < Object\n        def test_send_fields\n          # create a request message with simple reg fields\n          sreg_req = Request.new(['nickname', 'email'], ['fullname'])\n          req_msg = Message.new\n          req_msg.update_args(NS_URI, sreg_req.get_extension_args)\n          req = Server::OpenIDRequest.new\n          req.message = req_msg\n\n          # -> checkid_* request\n\n          # create a response\n          resp_msg = Message.new\n          resp = Server::OpenIDResponse.new(req)\n          resp.fields = resp_msg\n          sreg_resp = Response.extract_response(sreg_req, SOME_DATA)\n          resp.add_extension(sreg_resp)\n\n          # <- id_res response\n\n          # extract sent fields\n          sreg_data_resp = resp_msg.get_args(NS_URI)\n          assert_equal({'nickname' => 'linusaur',\n                         'email'=>'president@whitehouse.gov',\n                         'fullname'=>'Leonhard Euler',\n                       }, sreg_data_resp)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_stores.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/store/interface'\nrequire 'openid/store/filesystem'\nrequire 'openid/store/memcache'\nrequire 'openid/store/memory'\nrequire 'openid/util'\nrequire 'openid/store/nonce'\nrequire 'openid/association'\n\nmodule OpenID\n  module Store\n    module StoreTestCase\n      @@allowed_handle = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'()*+,-./:;<=>?@[\\\\]^_`{|}~'\n      @@allowed_nonce = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\n      def _gen_nonce\n        OpenID::CryptUtil.random_string(8, @@allowed_nonce)\n      end\n\n      def _gen_handle(n)\n        OpenID::CryptUtil.random_string(n, @@allowed_handle)\n      end\n\n      def _gen_secret(n, chars=nil)\n        OpenID::CryptUtil.random_string(n, chars)\n      end\n\n      def _gen_assoc(issued, lifetime=600)\n        secret = _gen_secret(20)\n        handle = _gen_handle(128)\n        OpenID::Association.new(handle, secret, Time.now + issued, lifetime,\n                                'HMAC-SHA1')\n      end\n\n      def _check_retrieve(url, handle=nil, expected=nil)\n        ret_assoc = @store.get_association(url, handle)\n\n        if expected.nil?\n          assert_nil(ret_assoc)\n        else\n          assert_equal(expected, ret_assoc)\n          assert_equal(expected.handle, ret_assoc.handle)\n          assert_equal(expected.secret, ret_assoc.secret)\n        end\n      end\n\n      def _check_remove(url, handle, expected)\n        present = @store.remove_association(url, handle)\n        assert_equal(expected, present)\n      end\n\n      def test_store\n        assoc = _gen_assoc(issued=0)\n\n        # Make sure that a missing association returns no result\n        _check_retrieve(server_url)\n\n        # Check that after storage, getting returns the same result\n        @store.store_association(server_url, assoc)\n        _check_retrieve(server_url, nil, assoc)\n\n        # more than once\n        _check_retrieve(server_url, nil, assoc)\n\n        # Storing more than once has no ill effect\n        @store.store_association(server_url, assoc)\n        _check_retrieve(server_url, nil, assoc)\n\n        # Removing an association that does not exist returns not present\n        _check_remove(server_url, assoc.handle + 'x', false)\n\n        # Removing an association that does not exist returns not present\n        _check_remove(server_url + 'x', assoc.handle, false)\n\n        # Removing an association that is present returns present\n        _check_remove(server_url, assoc.handle, true)\n\n        # but not present on subsequent calls\n        _check_remove(server_url, assoc.handle, false)\n\n        # Put assoc back in the store\n        @store.store_association(server_url, assoc)\n\n        # More recent and expires after assoc\n        assoc2 = _gen_assoc(issued=1)\n        @store.store_association(server_url, assoc2)\n\n        # After storing an association with a different handle, but the\n        # same server_url, the handle with the later expiration is returned.\n        _check_retrieve(server_url, nil, assoc2)\n\n        # We can still retrieve the older association\n        _check_retrieve(server_url, assoc.handle, assoc)\n\n        # Plus we can retrieve the association with the later expiration\n        # explicitly\n        _check_retrieve(server_url, assoc2.handle, assoc2)\n\n        # More recent, and expires earlier than assoc2 or assoc. Make sure\n        # that we're picking the one with the latest issued date and not\n        # taking into account the expiration.\n        assoc3 = _gen_assoc(issued=2, 100)\n        @store.store_association(server_url, assoc3)\n\n        _check_retrieve(server_url, nil, assoc3)\n        _check_retrieve(server_url, assoc.handle, assoc)\n        _check_retrieve(server_url, assoc2.handle, assoc2)\n        _check_retrieve(server_url, assoc3.handle, assoc3)\n\n        _check_remove(server_url, assoc2.handle, true)\n\n        _check_retrieve(server_url, nil, assoc3)\n        _check_retrieve(server_url, assoc.handle, assoc)\n        _check_retrieve(server_url, assoc2.handle, nil)\n        _check_retrieve(server_url, assoc3.handle, assoc3)\n\n        _check_remove(server_url, assoc2.handle, false)\n        _check_remove(server_url, assoc3.handle, true)\n\n        ret_assoc = @store.get_association(server_url, nil)\n        unexpected = [assoc2.handle, assoc3.handle]\n        assert ret_assoc.nil? || !unexpected.member?(ret_assoc.handle)\n\n        _check_retrieve(server_url, assoc.handle, assoc)\n        _check_retrieve(server_url, assoc2.handle, nil)\n        _check_retrieve(server_url, assoc3.handle, nil)\n\n        _check_remove(server_url, assoc2.handle, false)\n        _check_remove(server_url, assoc.handle, true)\n        _check_remove(server_url, assoc3.handle, false)\n\n        _check_retrieve(server_url, nil, nil)\n        _check_retrieve(server_url, assoc.handle, nil)\n        _check_retrieve(server_url, assoc2.handle, nil)\n        _check_retrieve(server_url, assoc3.handle, nil)\n\n        _check_remove(server_url, assoc2.handle, false)\n        _check_remove(server_url, assoc.handle, false)\n        _check_remove(server_url, assoc3.handle, false)\n      end\n\n      def test_assoc_cleanup\n        assocValid1 = _gen_assoc(-3600, 7200)\n        assocValid2 = _gen_assoc(-5)\n        assocExpired1 = _gen_assoc(-7200, 3600)\n        assocExpired2 = _gen_assoc(-7200, 3600)\n\n        @store.cleanup_associations\n        @store.store_association(server_url + '1', assocValid1)\n        @store.store_association(server_url + '1', assocExpired1)\n        @store.store_association(server_url + '2', assocExpired2)\n        @store.store_association(server_url + '3', assocValid2)\n\n        cleaned = @store.cleanup_associations()\n        assert_equal(2, cleaned, \"cleaned up associations\")\n      end\n\n      def _check_use_nonce(nonce, expected, server_url, msg='')\n        stamp, salt = Nonce::split_nonce(nonce)\n        actual = @store.use_nonce(server_url, stamp, salt)\n        assert_equal(expected, actual, msg)\n      end\n\n      def server_url\n        \"http://www.myopenid.com/openid\"\n      end\n\n      def test_nonce\n        [server_url, ''].each{|url|\n          nonce1 = Nonce::mk_nonce\n\n          _check_use_nonce(nonce1, true, url, \"#{url}: nonce allowed by default\")\n          _check_use_nonce(nonce1, false, url, \"#{url}: nonce not allowed twice\")\n          _check_use_nonce(nonce1, false, url, \"#{url}: nonce not allowed third time\")\n\n          # old nonces shouldn't pass\n          old_nonce = Nonce::mk_nonce(3600)\n          _check_use_nonce(old_nonce, false, url, \"Old nonce #{old_nonce.inspect} passed\")\n\n        }\n      end\n\n      def test_nonce_cleanup\n        now = Time.now.to_i\n        old_nonce1 = Nonce::mk_nonce(now - 20000)\n        old_nonce2 = Nonce::mk_nonce(now - 10000)\n        recent_nonce = Nonce::mk_nonce(now - 600)\n\n        orig_skew = Nonce.skew\n        Nonce.skew = 0\n        @store.cleanup_nonces\n        Nonce.skew = 1000000\n        ts, salt = Nonce::split_nonce(old_nonce1)\n        assert(@store.use_nonce(server_url, ts, salt), \"oldnonce1\")\n        ts, salt = Nonce::split_nonce(old_nonce2)\n        assert(@store.use_nonce(server_url, ts, salt), \"oldnonce2\")\n        ts, salt = Nonce::split_nonce(recent_nonce)\n        assert(@store.use_nonce(server_url, ts, salt), \"recent_nonce\")\n\n        Nonce.skew = 1000\n        cleaned = @store.cleanup_nonces\n        assert_equal(2, cleaned, \"Cleaned #{cleaned} nonces\")\n\n        Nonce.skew = 100000\n        ts, salt = Nonce::split_nonce(old_nonce1)\n        assert(@store.use_nonce(server_url, ts, salt), \"oldnonce1 after cleanup\")\n        ts, salt = Nonce::split_nonce(old_nonce2)\n        assert(@store.use_nonce(server_url, ts, salt), \"oldnonce2 after cleanup\")\n        ts, salt = Nonce::split_nonce(recent_nonce)\n        assert(!@store.use_nonce(server_url, ts, salt), \"recent_nonce after cleanup\")\n\n        Nonce.skew = orig_skew\n\n      end\n    end\n\n    class FileStoreTestCase < Minitest::Test\n      include StoreTestCase\n\n      def setup\n        raise \"filestoretest directory exists\" if File.exist?('filestoretest')\n        @store = Filesystem.new('filestoretest')\n      end\n\n      def teardown\n        Kernel.system('rm -r filestoretest')\n      end\n    end\n\n    class MemoryStoreTestCase < Minitest::Test\n      include StoreTestCase\n\n      def setup\n        @store = Memory.new\n      end\n    end\n\n    begin\n      ::TESTING_MEMCACHE\n    rescue NameError\n    else\n      class MemcacheStoreTestCase < Minitest::Test\n        include StoreTestCase\n        def setup\n          store_uniq = OpenID::CryptUtil.random_string(6, \"0123456789\")\n          store_namespace = \"openid-store-#{store_uniq}:\"\n          @store = Memcache.new(::TESTING_MEMCACHE, store_namespace)\n        end\n\n        def test_nonce_cleanup\n        end\n\n        def test_assoc_cleanup\n        end\n      end\n    end\n\n    class AbstractStoreTestCase < Minitest::Test\n      def test_abstract_class\n        # the abstract made concrete\n        abc = Interface.new()\n        server_url = \"http://server.com/\"\n        association = OpenID::Association.new(\"foo\", \"bar\", Time.now, Time.now + 10, \"dummy\")\n\n        assert_raises(NotImplementedError) {\n          abc.store_association(server_url, association)\n        }\n\n        assert_raises(NotImplementedError) {\n          abc.get_association(server_url)\n        }\n\n        assert_raises(NotImplementedError) {\n          abc.remove_association(server_url, association.handle)\n        }\n\n        assert_raises(NotImplementedError) {\n          abc.use_nonce(server_url, Time.now.to_i, \"foo\")\n        }\n\n        assert_raises(NotImplementedError) {\n          abc.cleanup_nonces()\n        }\n\n        assert_raises(NotImplementedError) {\n          abc.cleanup_associations()\n        }\n\n        assert_raises(NotImplementedError) {\n          abc.cleanup()\n        }\n\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_trustroot.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'openid/trustroot'\n\nclass TrustRootTest < Minitest::Test\n  include OpenID::TestDataMixin\n\n  def _test_sanity(case_, sanity, desc)\n    tr = OpenID::TrustRoot::TrustRoot.parse(case_)\n    if sanity == 'sane'\n      assert !tr.nil?\n      assert tr.sane?, [case_, desc].join(' ')\n      assert OpenID::TrustRoot::TrustRoot.check_sanity(case_), [case_, desc].join(' ')\n    elsif sanity == 'insane'\n      assert !tr.sane?, [case_, desc].join(' ')\n      assert !OpenID::TrustRoot::TrustRoot.check_sanity(case_), [case_, desc].join(' ')\n    else\n      assert tr.nil?, case_\n    end\n  end\n\n  def _test_match(trust_root, url, expected_match)\n    tr = OpenID::TrustRoot::TrustRoot.parse(trust_root)\n    actual_match = tr.validate_url(url)\n    if expected_match\n      assert actual_match, [trust_root, url].join(' ')\n      assert OpenID::TrustRoot::TrustRoot.check_url(trust_root, url)\n    else\n      assert !actual_match, [expected_match, actual_match, trust_root, url].join(' ')\n      assert !OpenID::TrustRoot::TrustRoot.check_url(trust_root, url)\n    end\n  end\n\n  def test_trustroots\n    data = read_data_file('trustroot.txt', false)\n\n    parts = data.split('=' * 40 + \"\\n\").collect { |i| i.strip() }\n    assert(parts[0] == '')\n    _, ph, pdat, mh, mdat = parts\n\n    getTests(['bad', 'insane', 'sane'], ph, pdat).each { |tc|\n      sanity, desc, case_ = tc\n      _test_sanity(case_, sanity, desc)\n    }\n\n    getTests([true, false], mh, mdat).each { |tc|\n      match, _, case_ = tc\n      trust_root, url = case_.split()\n      _test_match(trust_root, url, match)\n    }\n  end\n\n  def getTests(grps, head, dat)\n    tests = []\n    top = head.strip()\n    gdat = dat.split('-' * 40 + \"\\n\").collect { |i| i.strip() }\n    assert gdat[0] == ''\n    assert gdat.length == (grps.length * 2 + 1)\n    i = 1\n    grps.each { |x|\n      n, desc = gdat[i].split(': ')\n      cases = gdat[i + 1].split(\"\\n\")\n      assert(cases.length == n.to_i, \"Number of cases differs from header count\")\n      cases.each { |case_|\n        tests += [[x, top + ' - ' + desc, case_]]\n      }\n      i += 2\n    }\n\n    return tests\n  end\n\n  def test_return_to_matches\n    data = [\n            [[], nil, false],\n            [[], \"\", false],\n            [[], \"http://bogus/return_to\", false],\n            [[\"http://bogus/\"], nil, false],\n            [[\"://broken/\"], nil, false],\n            [[\"://broken/\"], \"http://broken/\", false],\n            [[\"http://*.broken/\"], \"http://foo.broken/\", false],\n            [[\"http://x.broken/\"], \"http://foo.broken/\", false],\n            [[\"http://first/\", \"http://second/path/\"], \"http://second/?query=x\", false],\n\n            [[\"http://broken/\"], \"http://broken/\", true],\n            [[\"http://first/\", \"http://second/\"], \"http://second/?query=x\", true],\n           ]\n\n    data.each { |case_|\n      allowed_return_urls, return_to, expected_result = case_\n      actual_result = OpenID::TrustRoot::return_to_matches(allowed_return_urls,\n                                                           return_to)\n      assert(expected_result == actual_result)\n    }\n  end\n\n  def test_build_discovery_url\n    data = [\n            [\"http://foo.com/path\", \"http://foo.com/path\"],\n            [\"http://foo.com/path?foo=bar\", \"http://foo.com/path?foo=bar\"],\n            [\"http://*.bogus.com/path\", \"http://www.bogus.com/path\"],\n            [\"http://*.bogus.com:122/path\", \"http://www.bogus.com:122/path\"],\n           ]\n\n    data.each { |case_|\n      trust_root, expected_disco_url = case_\n      tr = OpenID::TrustRoot::TrustRoot.parse(trust_root)\n      actual_disco_url = tr.build_discovery_url()\n      assert actual_disco_url == expected_disco_url\n    }\n  end\nend\n"
  },
  {
    "path": "test/test_ui.rb",
    "content": "require 'openid/extensions/ui'\nrequire 'openid/message'\nrequire 'openid/server'\nrequire 'minitest/autorun'\n\nmodule OpenID\n  module UITest\n    class UIRequestTestCase < Minitest::Test\n\n      def setup\n        @req = UI::Request.new\n      end\n\n      def test_construct\n        assert_nil @req.mode\n        assert_nil @req.icon\n        assert_nil @req.lang\n        assert_equal 'ui', @req.ns_alias\n\n        req2 = UI::Request.new(\"popup\", true, \"ja-JP\")\n        assert_equal \"popup\", req2.mode\n        assert_equal true, req2.icon\n        assert_equal \"ja-JP\", req2.lang\n      end\n\n      def test_add_mode\n        @req.mode = \"popup\"\n        assert_equal \"popup\", @req.mode\n      end\n\n      def test_add_icon\n        @req.icon = true\n        assert_equal true, @req.icon\n      end\n\n      def test_add_lang\n        @req.lang = \"ja-JP\"\n        assert_equal \"ja-JP\", @req.lang\n      end\n\n      def test_get_extension_args\n        assert_equal({}, @req.get_extension_args)\n        @req.mode = \"popup\"\n        assert_equal({'mode' => 'popup'}, @req.get_extension_args)\n        @req.icon = true\n        assert_equal({'mode' => 'popup', 'icon' => true}, @req.get_extension_args)\n        @req.lang = \"ja-JP\"\n        assert_equal({'mode' => 'popup', 'icon' => true, 'lang' => 'ja-JP'}, @req.get_extension_args)\n      end\n\n      def test_parse_extension_args\n        args = {'mode' => 'popup', 'icon' => true, 'lang' => 'ja-JP'}\n        @req.parse_extension_args args\n        assert_equal \"popup\", @req.mode\n        assert_equal true, @req.icon\n        assert_equal \"ja-JP\", @req.lang\n      end\n\n      def test_parse_extension_args_empty\n        @req.parse_extension_args({})\n        assert_nil @req.mode\n        assert_nil @req.icon\n        assert_nil @req.lang\n      end\n\n      def test_from_openid_request\n        openid_req_msg = Message.from_openid_args(\n          'mode' => 'checkid_setup',\n          'ns' => OPENID2_NS,\n          'ns.ui' => UI::NS_URI,\n          'ui.mode' => 'popup',\n          'ui.icon' => true,\n          'ui.lang' => 'ja-JP'\n        )\n        oid_req = Server::OpenIDRequest.new\n        oid_req.message = openid_req_msg\n        req = UI::Request.from_openid_request oid_req\n        assert_equal \"popup\", req.mode\n        assert_equal true, req.icon\n        assert_equal \"ja-JP\", req.lang\n      end\n\n      def test_from_openid_request_no_ui_params\n        message = Message.new\n        openid_req = Server::OpenIDRequest.new\n        openid_req.message = message\n        ui_req = UI::Request.from_openid_request openid_req\n        assert ui_req.nil?\n      end\n\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_urinorm.rb",
    "content": "require \"minitest/autorun\"\nrequire \"testutil\"\nrequire \"openid/urinorm\"\n\nclass URINormTestCase < Minitest::Test\n  include OpenID::TestDataMixin\n\n  def test_normalize\n    lines = read_data_file('urinorm.txt')\n\n    while lines.length > 0\n\n      case_name = lines.shift.strip\n      actual = lines.shift.strip\n      expected = lines.shift.strip\n      lines.shift #=> newline\n\n      if expected == 'fail'\n        begin\n          OpenID::URINorm.urinorm(actual)\n        rescue URI::InvalidURIError\n          assert true\n        else\n          raise 'Should have gotten URI error'\n        end\n      else\n        normalized = OpenID::URINorm.urinorm(actual)\n        assert_equal(expected, normalized, case_name)\n      end\n    end\n  end\n\nend\n\n"
  },
  {
    "path": "test/test_util.rb",
    "content": "# encoding: ASCII-8BIT\nrequire \"minitest/autorun\"\nrequire \"openid/util\"\n\nmodule OpenID\n  class UtilTestCase < Minitest::Test\n\n    def test_base64\n      cases = [\n               \"\",\n               \"\\000\",\n               \"\\001\",\n               \"\\000\" * 100,\n               (0...256).collect{ |i| i.chr }.join('')\n              ]\n\n      cases.each do |c|\n        encoded = Util.to_base64(c)\n        decoded = Util.from_base64(encoded)\n        assert(c == decoded)\n      end\n\n    end\n\n    def test_base64_valid\n      [[\"foos\", \"~\\212,\"],\n       [\"++++\", \"\\373\\357\\276\"],\n       [\"/+==\", \"\\377\"],\n       [\"\", \"\"],\n       [\"FOOSBALL\", \"\\024\\343\\222\\004\\002\\313\"],\n       [\"FoosBL==\", \"\\026\\212,\\004\"],\n       [\"Foos\\nBall\", \"\\026\\212,\\005\\251e\"],\n       [\"Foo\\r\\ns\\nBall\", \"\\026\\212,\\005\\251e\"]\n      ].each do | input, expected |\n        assert_equal(expected, Util.from_base64(input))\n      end\n    end\n\n    def test_base64_invalid\n      ['!',\n       'Foos!',\n       'Balls',\n       'B===',\n       'Foos Ball',\n       '=foo',\n      ].each do |invalid_input|\n        assert_raises(ArgumentError) do\n          Util.from_base64(invalid_input)\n        end\n      end\n    end\n\n    def test_append_args()\n      simple = 'http://www.example.com/'\n\n      cases = [\n               ['empty list',\n                [simple, []],\n                simple],\n\n               ['empty dict',\n                [simple, {}],\n                simple],\n\n               ['one list',\n                [simple, [['a', 'b']]],\n                simple + '?a=b'],\n\n               ['one dict',\n                [simple, {'a' => 'b'}],\n                simple + '?a=b'],\n\n               ['two list (same)',\n                [simple, [['a', 'b'], ['a', 'c']]],\n                simple + '?a=b&a=c'],\n\n               ['two list',\n                [simple, [['a', 'b'], ['b', 'c']]],\n                simple + '?a=b&b=c'],\n\n               ['two list (order)',\n                [simple, [['b', 'c'], ['a', 'b']]],\n                simple + '?b=c&a=b'],\n\n               ['two dict [order]',\n                [simple, {'b' => 'c', 'a' => 'b'}],\n                simple + '?a=b&b=c'],\n\n               ['args exist [empty]',\n                [simple + '?stuff=bother', []],\n                simple + '?stuff=bother'],\n\n               ['escape',\n                [simple, [['=', '=']]],\n                simple + '?%3D=%3D'],\n\n               ['escape [URL]',\n                [simple, [['this_url', simple]]],\n                simple + '?this_url=http%3A%2F%2Fwww.example.com%2F'],\n\n               ['use dots',\n                [simple, [['openid.stuff', 'bother']]],\n                simple + '?openid.stuff=bother'],\n\n               ['args exist',\n                [simple + '?stuff=bother', [['ack', 'ack']]],\n                simple + '?stuff=bother&ack=ack'],\n\n               ['args exist',\n                [simple + '?stuff=bother', [['ack', 'ack']]],\n                simple + '?stuff=bother&ack=ack'],\n\n               ['args exist [dict]',\n                [simple + '?stuff=bother', {'ack' => 'ack'}],\n                simple + '?stuff=bother&ack=ack'],\n\n               ['args exist [dict 2]',\n                [simple + '?stuff=bother', {'ack' => 'ack', 'zebra' => 'lion'}],\n                simple + '?stuff=bother&ack=ack&zebra=lion'],\n\n               ['three args [dict]',\n                [simple, {'stuff' => 'bother', 'ack' => 'ack', 'zebra' => 'lion'}],\n                simple + '?ack=ack&stuff=bother&zebra=lion'],\n\n               ['three args [list]',\n                [simple, [['stuff', 'bother'], ['ack', 'ack'], ['zebra', 'lion']]],\n                simple + '?stuff=bother&ack=ack&zebra=lion'],\n              ]\n\n      cases.each { |name, args, expected|\n        url, pairs = args\n        actual = Util.append_args(url, pairs)\n        msg = \"[#{name}] Expected: #{expected}, actual: #{actual}\"\n        assert_equal(expected, actual, msg)\n      }\n\n    end\n\n    def test_parse_query\n      assert_equal({'foo'=>'bar'}, Util.parse_query('foo=bar'))\n    end\n\n  end\nend\n"
  },
  {
    "path": "test/test_xrds.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'openid/yadis/xrds'\n\nmodule OpenID\n  module Yadis\n\n    module XRDSTestMixin\n      include TestDataMixin\n\n      XRD_FILE = 'valid-populated-xrds.xml'\n      NOXRDS_FILE = 'not-xrds.xml'\n      NOXRD_FILE = 'no-xrd.xml'\n\n      XRDS_DATA_DIR = TEST_DATA_DIR.join('test_xrds')\n\n      def read_xrds_data_file(filename)\n        read_data_file(filename, false, XRDS_DATA_DIR)\n      end\n    end\n\n    class ParseXRDSTestCase < Minitest::Test\n      include XRDSTestMixin\n\n      # Check that parsing succeeds at all.\n      def test_parse\n        result = Yadis.parseXRDS(read_xrds_data_file(XRD_FILE))\n        refute_nil result\n      end\n\n      def test_parse_no_xrds_xml\n        xmldoc = read_xrds_data_file(NOXRDS_FILE)\n        assert_raises(Yadis::XRDSError) {\n          Yadis.parseXRDS(xmldoc)\n        }\n      end\n\n      def test_parse_no_xrds_empty\n        assert_raises(Yadis::XRDSError) {\n          Yadis.parseXRDS('')\n        }\n      end\n\n      def test_is_xrds\n        isnt = REXML::Document.new(read_xrds_data_file(NOXRDS_FILE))\n        should_be = Yadis.parseXRDS(read_xrds_data_file(XRD_FILE))\n        assert_equal false, Yadis::is_xrds?(isnt)\n        assert Yadis::is_xrds?(should_be)\n      end\n    end\n\n    class GetYadisXRDTestCase < Minitest::Test\n      include XRDSTestMixin\n\n      # XXX: Test to make sure this really gets the _right_ XRD.\n      def test_get_xrd\n        doc = Yadis.parseXRDS(read_xrds_data_file(XRD_FILE))\n        result = Yadis::get_yadis_xrd(doc)\n        refute_nil result\n        assert_equal 'XRD', result.name\n        assert_equal Yadis::XRD_NS_2_0, result.namespace\n      end\n\n      def test_no_xrd\n        xmldoc = read_xrds_data_file(NOXRD_FILE)\n        doc = Yadis.parseXRDS(xmldoc)\n        assert_raises(Yadis::XRDSError) {\n          Yadis.get_yadis_xrd(doc)\n        }\n      end\n    end\n\n    class EachServiceTestCase < Minitest::Test\n      include XRDSTestMixin\n\n      def test_get_xrd\n        doc = Yadis.parseXRDS(read_xrds_data_file(XRD_FILE))\n        count = 0\n        result = Yadis::each_service(doc) { |e|\n          assert_equal 'Service', e.name\n          count += 1\n        }\n        refute_nil result\n        assert_equal 5, count\n      end\n\n      def test_no_xrd\n        xmldoc = read_xrds_data_file(NOXRD_FILE)\n        doc = Yadis.parseXRDS(xmldoc)\n        assert_raises(Yadis::XRDSError) {\n          Yadis.each_service(doc)\n        }\n      end\n\n      def test_equal_j3h\n        doc = Yadis.parseXRDS(read_xrds_data_file('=j3h.2007.11.14.xrds'))\n        count = 0\n        result = Yadis::each_service(doc) { |e|\n          assert_equal 'Service', e.name\n          count += 1\n        }\n        refute_nil result\n        assert_equal 2, count\n      end\n    end\n\n    # XXX: test prioSort!\n\n    class ExpandServiceTestCase < Minitest::Test\n      @@service_xml = <<END\n<Service>\n<Type>urn://foo</Type>\n<Type>urn://bar</Type>\n<URI priority='2'>http://2.invalid/</URI>\n<URI>http://0.invalid/</URI>\n<URI priority='1'>http://1.invalid/</URI>\n</Service>\nEND\n\n      # XXX - not sorted!\n      def test_expand_service\n        service_element = REXML::Document.new(@@service_xml).root\n        result = Yadis::expand_service(service_element)\n        assert_equal 3, result.length\n        types, uri, result_element = result[0]\n        assert_same service_element, result_element\n        assert_equal 'http://0.invalid/', uri\n        assert_equal ['urn://foo', 'urn://bar'], types\n        types, uri, result_element = result[1]\n        assert_equal 'http://1.invalid/', uri\n        types, uri, result_element = result[2]\n        assert_equal 'http://2.invalid/', uri\n      end\n    end\n\n    class PrioSortTestCase < Minitest::Test\n      def new_uri(priority)\n        e = REXML::Element.new(\"URI\")\n        e.add_attribute(\"priority\", priority.to_s) unless e.nil?\n        return e\n      end\n\n      def test_sorting\n        l = [\n             e7 = new_uri(7),\n             e1 = new_uri(1),\n             e0 = new_uri(nil),\n             e2 = new_uri(2),\n            ]\n        sorted = Yadis::prio_sort(l)\n        assert_same e0, sorted[0]\n        assert_same e1, sorted[1]\n        assert_same e2, sorted[2]\n        assert_same e7, sorted[3]\n      end\n    end\n\n    class GetCanonicalIDTestCase < Minitest::Test\n      include XRDSTestMixin\n\n      def test_multisegment_xri\n        xmldoc = Yadis.parseXRDS(read_xrds_data_file('subsegments.xrds'))\n        Yadis.get_canonical_id('xri://=nishitani*masaki', xmldoc)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_xri.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/yadis/xri'\n\nmodule OpenID\n\n  module Yadis\n\n    class XriDiscoveryTestCase < Minitest::Test\n\n      def test_isXRI?\n        assert_equal(:xri, XRI.identifier_scheme('=john.smith'))\n        assert_equal(:xri, XRI.identifier_scheme('@smiths/john'))\n        assert_equal(:xri, XRI.identifier_scheme('xri://=john'))\n        assert_equal(:xri, XRI.identifier_scheme('@ootao*test1'))\n        assert_equal(:uri, XRI.identifier_scheme('smoker.myopenid.com'))\n        assert_equal(:uri, XRI.identifier_scheme('http://smoker.myopenid.com'))\n        assert_equal(:uri, XRI.identifier_scheme('https://smoker.myopenid.com'))\n      end\n    end\n\n    class XriEscapingTestCase < Minitest::Test\n      def test_escaping_percents\n        assert_equal('@example/abc%252Fd/ef', \n                     XRI.escape_for_iri('@example/abc%2Fd/ef'))\n      end\n\n      def test_escaping_xref\n        # no escapes\n        assert_equal('@example/foo/(@bar)',\n                     XRI.escape_for_iri('@example/foo/(@bar)'))\n        # escape slashes\n        assert_equal('@example/foo/(@bar%2Fbaz)',\n                     XRI.escape_for_iri('@example/foo/(@bar/baz)'))\n        # escape query ? and fragment #\n        assert_equal('@example/foo/(@baz%3Fp=q%23r)?i=j#k',\n                     XRI.escape_for_iri('@example/foo/(@baz?p=q#r)?i=j#k'))\n      end\n    end\n\n    class XriTransformationTestCase < Minitest::Test\n      def test_to_iri_normal\n        assert_equal('xri://@example', XRI.to_iri_normal('@example'))\n      end\n      # iri_to_url:\n      #   various ucschar to hex\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_xrires.rb",
    "content": "require 'minitest/autorun'\nrequire 'openid/yadis/xrires'\n\nmodule OpenID\n  module Yadis\n\n    class XRDSFetcher\n      def initialize(results)\n        @results = results\n      end\n\n      def fetch(url, body=nil, headers=nil, redirect_limit=nil)\n        if !@results.empty?\n          return @results.shift\n        end\n\n        nil\n      end\n    end\n\n    class ProxyQueryTestCase < Minitest::Test\n      def setup\n        @proxy_url = 'http://xri.example.com/'\n        @proxy = XRI::ProxyResolver.new(@proxy_url)\n        @servicetype = 'xri://+i-service*(+forwarding)*($v*1.0)'\n        @servicetype_enc = 'xri%3A%2F%2F%2Bi-service%2A%28%2Bforwarding%29%2A%28%24v%2A1.0%29'\n      end\n\n      def test_proxy_url\n        st = @servicetype\n        ste = @servicetype_enc\n        args_esc = [\"_xrd_r=application%2Fxrds%2Bxml\", \"_xrd_t=#{ste}\"]\n        pqu = @proxy.method('query_url')\n        h = @proxy_url\n\n        assert_match h + '=foo?', pqu.call('=foo', st)\n        assert_match args_esc[0], pqu.call('=foo', st)\n        assert_match args_esc[1], pqu.call('=foo', st)\n\n        assert_match h + '=foo/bar?baz&', pqu.call('=foo/bar?baz', st)\n        assert_match args_esc[0], pqu.call('=foo/bar?baz', st)\n        assert_match args_esc[1], pqu.call('=foo/bar?baz', st)\n\n        assert_match h + '=foo/bar?baz=quux&', pqu.call('=foo/bar?baz=quux', st)\n        assert_match args_esc[0], pqu.call('=foo/bar?baz=quux', st)\n        assert_match args_esc[1], pqu.call('=foo/bar?baz=quux', st)\n\n        assert_match h + '=foo/bar?mi=fa&so=la&', pqu.call('=foo/bar?mi=fa&so=la', st)\n        assert_match args_esc[0], pqu.call('=foo/bar?mi=fa&so=la', st)\n        assert_match args_esc[1], pqu.call('=foo/bar?mi=fa&so=la', st)\n\n        # With no service endpoint selection.\n        args_esc = \"_xrd_r=application%2Fxrds%2Bxml%3Bsep%3Dfalse\"\n\n        assert_match h + '=foo?', pqu.call('=foo', nil)\n        assert_match args_esc, pqu.call('=foo', nil)\n      end\n\n      def test_proxy_url_qmarks\n        st = @servicetype\n        ste = @servicetype_enc\n        args_esc = [\"_xrd_r=application%2Fxrds%2Bxml\", \"_xrd_t=#{ste}\"]\n        pqu = @proxy.method('query_url')\n        h = @proxy_url\n\n        assert_match h + '=foo/bar??', pqu.call('=foo/bar?', st)\n        assert_match args_esc[0], pqu.call('=foo/bar?', st)\n        assert_match args_esc[1], pqu.call('=foo/bar?', st)\n\n        assert_match h + '=foo/bar????', pqu.call('=foo/bar???', st)\n        assert_match args_esc[0], pqu.call('=foo/bar???', st)\n        assert_match args_esc[1], pqu.call('=foo/bar???', st)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_yadis_discovery.rb",
    "content": "require 'minitest/autorun'\nrequire 'testutil'\nrequire 'uri'\nrequire 'openid/yadis/discovery'\nrequire 'openid/fetchers'\nrequire 'openid/util'\nrequire 'discoverdata'\n\nmodule OpenID\n\n  module YadisDiscovery\n    include FetcherMixin\n    include DiscoverData\n\n    STATUS_HEADER_RE = /Status: (\\d+) .*?$/m\n\n    def self.mkResponse(data)\n      status_mo = data.scan(STATUS_HEADER_RE)\n      headers_str, body = data.split(\"\\n\\n\", 2)\n      headers = {}\n      headers_str.split(\"\\n\", -1).each { |line|\n        k, v = line.split(':', 2)\n        k = k.strip().downcase\n        v = v.strip()\n        headers[k] = v\n      }\n      status = status_mo[0][0].to_i\n      return HTTPResponse._from_raw_data(status, body,\n                                         headers)\n    end\n\n    class TestFetcher\n      include DiscoverData\n\n      def initialize(base_url)\n        @base_url = base_url\n      end\n\n      def fetch(url, headers, body, redirect_limit=nil)\n        current_url = url\n        while true\n          parsed = URI::parse(current_url)\n          # parsed[2][1:]\n          path = parsed.path[1..-1]\n          begin\n            data = generateSample(path, @base_url)\n          rescue ArgumentError\n            return HTTPResponse._from_raw_data(404, '', {},\n                                               current_url)\n          end\n\n          response = YadisDiscovery.mkResponse(data)\n          if [\"301\", \"302\", \"303\", \"307\"].member?(response.code)\n            current_url = response['location']\n          else\n            response.final_url = current_url\n            return response\n          end\n        end\n      end\n    end\n\n    class MockFetcher\n      def initialize\n        @count = 0\n      end\n\n      def fetch(uri, headers=nil, body=nil, redirect_limit=nil)\n        @count += 1\n        if @count == 1\n          headers = {\n            'X-XRDS-Location'.downcase => 'http://unittest/404',\n          }\n          return HTTPResponse._from_raw_data(200, '', headers, uri)\n        else\n          return HTTPResponse._from_raw_data(404, '', {}, uri)\n        end\n      end\n    end\n\n    class TestSecondGet < Minitest::Test\n      include FetcherMixin\n\n      def test_404\n        uri = \"http://something.unittest/\"\n        assert_raises(DiscoveryFailure) {\n          with_fetcher(MockFetcher.new) { Yadis.discover(uri) }\n        }\n      end\n    end\n\n    class DiscoveryTestCase\n      include DiscoverData\n      include FetcherMixin\n\n      def initialize(testcase, input_name, id_name, result_name, success)\n        @base_url = 'http://invalid.unittest/'\n        @testcase = testcase\n        @input_name = input_name\n        @id_name = id_name\n        @result_name = result_name\n        @success = success\n      end\n\n      def setup\n        @input_url, @expected = generateResult(@base_url,\n                                               @input_name,\n                                               @id_name,\n                                               @result_name,\n                                               @success)\n      end\n\n      def do_discovery\n        with_fetcher(TestFetcher.new(@base_url)) do\n          Yadis.discover(@input_url)\n        end\n      end\n\n      def runCustomTest\n        setup\n\n        if @expected.respond_to?(\"ancestors\") and @expected.ancestors.member?(DiscoveryFailure)\n          @testcase.assert_raises(DiscoveryFailure) {\n            do_discovery\n          }\n        else\n          result = do_discovery\n          @testcase.assert_equal(@input_url, result.request_uri)\n\n          msg = sprintf(\"Identity URL mismatch: actual = %s, expected = %s\",\n                        result.normalized_uri, @expected.normalized_uri)\n          @testcase.assert_equal(@expected.normalized_uri, result.normalized_uri, msg)\n\n          msg = sprintf(\"Content mismatch: actual = %s, expected = %s\",\n                        result.response_text, @expected.response_text)\n          @testcase.assert_equal(@expected.response_text, result.response_text, msg)\n\n          expected_keys = @expected.instance_variables\n          expected_keys.sort!\n\n          actual_keys = result.instance_variables\n          actual_keys.sort!\n\n          @testcase.assert_equal(actual_keys, expected_keys)\n\n          @expected.instance_variables.each { |k|\n            exp_v = @expected.instance_variable_get(k)\n            act_v = result.instance_variable_get(k)\n            @testcase.assert_equal(act_v, exp_v, [k, exp_v, act_v])\n          }\n        end\n      end\n    end\n\n    class NoContentTypeFetcher\n      def fetch(url, body=nil, headers=nil, redirect_limit=nil)\n        return OpenID::HTTPResponse._from_raw_data(200, \"\", {}, nil)\n      end\n    end\n\n    class BlankContentTypeFetcher\n      def fetch(url, body=nil, headers=nil, redirect_limit=nil)\n        return OpenID::HTTPResponse._from_raw_data(200, \"\", {\"Content-Type\" => \"\"}, nil)\n      end\n    end\n\n    class TestYadisDiscovery < Minitest::Test\n      include FetcherMixin\n\n      def test_yadis_discovery\n        DiscoverData::TESTLIST.each { |success, input_name, id_name, result_name|\n          test = DiscoveryTestCase.new(self, input_name, id_name, result_name, success)\n          test.runCustomTest\n        }\n      end\n\n      def test_is_xrds_yadis_location\n        result = Yadis::DiscoveryResult.new('http://request.uri/')\n        result.normalized_uri = \"http://normalized/\"\n        result.xrds_uri = \"http://normalized/xrds\"\n\n        assert(result.is_xrds)\n      end\n\n      def test_is_xrds_content_type\n        result = Yadis::DiscoveryResult.new('http://request.uri/')\n        result.normalized_uri = result.xrds_uri = \"http://normalized/\"\n        result.content_type = Yadis::YADIS_CONTENT_TYPE\n\n        assert(result.is_xrds)\n      end\n\n      def test_is_xrds_neither\n        result = Yadis::DiscoveryResult.new('http://request.uri/')\n        result.normalized_uri = result.xrds_uri = \"http://normalized/\"\n        result.content_type = \"another/content-type\"\n\n        assert(!result.is_xrds)\n      end\n\n      def test_no_content_type\n        with_fetcher(NoContentTypeFetcher.new) do\n          result = Yadis.discover(\"http://bogus\")\n          assert_nil(result.content_type)\n        end\n      end\n\n      def test_blank_content_type\n        with_fetcher(BlankContentTypeFetcher.new) do\n          result = Yadis.discover(\"http://bogus\")\n          assert_equal(\"\", result.content_type)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/testutil.rb",
    "content": "require \"pathname\"\n\nif defined? Minitest::Test\n  # We're on Minitest 5+. Nothing to do here.\nelse\n  # Minitest 4 doesn't have Minitest::Test yet.\n  Minitest::Test = MiniTest::Unit::TestCase\nend\n\nmodule OpenID\n  module TestDataMixin\n    TESTS_DIR = Pathname.new(__FILE__).dirname\n    TEST_DATA_DIR = Pathname.new('data')\n\n    def read_data_file(filename, lines=true, data_dir=TEST_DATA_DIR)\n      fname = TESTS_DIR.join(data_dir, filename)\n\n      if lines\n        fname.readlines\n      else\n        fname.read\n      end\n    end\n  end\n\n  module FetcherMixin\n    def with_fetcher(fetcher)\n      original_fetcher = OpenID.fetcher\n      begin\n        OpenID.fetcher = fetcher\n        return yield\n      ensure\n        OpenID.fetcher = original_fetcher\n      end\n    end\n  end\n\n  module Const\n    def const(symbol, value)\n      (class << self;self;end).instance_eval do\n        define_method(symbol) { value }\n      end\n    end\n  end\n\n  class MockResponse\n    attr_reader :code, :body\n\n    def initialize(code, body)\n      @code = code.to_s\n      @body = body\n    end\n  end\n\n  module ProtocolErrorMixin\n    def assert_protocol_error(str_prefix)\n      begin\n        result = yield\n      rescue ProtocolError => why\n        message = \"Expected prefix #{str_prefix.inspect}, got \"\\\n                  \"#{why.message.inspect}\"\n        assert(why.message.start_with?(str_prefix), message)\n      else\n        fail(\"Expected ProtocolError. Got #{result.inspect}\")\n      end\n    end\n  end\n\n  module OverrideMethodMixin\n    def with_method_overridden(method_name, proc)\n      original = method(method_name)\n      begin\n        # TODO: find a combination of undef calls which prevent the warning\n        verbose, $VERBOSE = $VERBOSE, false\n        define_method(method_name, proc)\n        module_function(method_name)\n        $VERBOSE = verbose\n        yield\n      ensure\n        if original.respond_to? :owner\n          original.owner.send(:undef_method, method_name)\n          original.owner.send :define_method, method_name, original\n        else\n          define_method(method_name, original)\n          module_function(method_name)\n        end\n      end\n    end\n  end\n\n  # To use:\n  # > x = Object.new\n  # > x.extend(InstanceDefExtension)\n  # > x.instance_def(:monkeys) do\n  # >   \"bananas\"\n  # > end\n  # > x.monkeys\n  #\n  module InstanceDefExtension\n    def instance_def(method_name, &proc)\n      (class << self;self;end).instance_eval do\n        # TODO: find a combination of undef calls which prevent the warning\n        verbose, $VERBOSE = $VERBOSE, false\n        define_method(method_name, proc)\n        $VERBOSE = verbose\n      end\n    end\n  end\n\n  GOODSIG = '[A Good Signature]'\n\n  class GoodAssoc\n    attr_accessor :handle, :expires_in\n\n    def initialize(handle='-blah-')\n      @handle = handle\n      @expires_in = 3600\n    end\n\n    def check_message_signature(msg)\n      msg.get_arg(OPENID_NS, 'sig') == GOODSIG\n    end\n  end\n\n  class HTTPResponse\n    def self._from_raw_data(status, body=\"\", headers={}, final_url=nil)\n      resp = Net::HTTPResponse.new('1.1', status.to_s, 'NONE')\n      me = self._from_net_response(resp, final_url)\n      me.initialize_http_header headers\n      me.body = body\n      return me\n    end\n  end\nend\n"
  },
  {
    "path": "test/util.rb",
    "content": "# Utilities that are only used in the testing code\nrequire 'stringio'\n\nmodule OpenID\n  module TestUtil\n    def assert_log_matches(*regexes)\n      begin\n        old_logger = Util.logger\n        log_output = StringIO.new\n        Util.logger = Logger.new(log_output)\n        result = yield\n      ensure\n        Util.logger = old_logger\n      end\n      log_output.rewind\n      log_lines = log_output.readlines\n      assert_equal(regexes.length, log_lines.length,\n                   [regexes, log_lines].inspect)\n      log_lines.zip(regexes) do |line, regex|\n        assert_match(regex, line)\n      end\n      result\n    end\n\n    def assert_log_line_count(num_lines)\n      begin\n        old_logger = Util.logger\n        log_output = StringIO.new\n        Util.logger = Logger.new(log_output)\n        result = yield\n      ensure\n        Util.logger = old_logger\n      end\n      log_output.rewind\n      log_lines = log_output.readlines\n      assert_equal(num_lines, log_lines.length)\n      result\n    end\n\n    def silence_logging\n      begin\n        old_logger = Util.logger\n        log_output = StringIO.new\n        Util.logger = Logger.new(log_output)\n        result = yield\n      ensure\n        Util.logger = old_logger\n      end\n      result\n    end\n  end\nend\n\n"
  }
]