entp-ruby-openid 2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. data/CHANGELOG +215 -0
  2. data/INSTALL +47 -0
  3. data/LICENSE +210 -0
  4. data/NOTICE +2 -0
  5. data/README +85 -0
  6. data/UPGRADE +127 -0
  7. data/admin/runtests.rb +45 -0
  8. data/examples/README +32 -0
  9. data/examples/active_record_openid_store/README +58 -0
  10. data/examples/active_record_openid_store/XXX_add_open_id_store_to_db.rb +24 -0
  11. data/examples/active_record_openid_store/XXX_upgrade_open_id_store.rb +26 -0
  12. data/examples/active_record_openid_store/init.rb +8 -0
  13. data/examples/active_record_openid_store/lib/association.rb +10 -0
  14. data/examples/active_record_openid_store/lib/nonce.rb +3 -0
  15. data/examples/active_record_openid_store/lib/open_id_setting.rb +4 -0
  16. data/examples/active_record_openid_store/lib/openid_ar_store.rb +57 -0
  17. data/examples/active_record_openid_store/test/store_test.rb +212 -0
  18. data/examples/discover +49 -0
  19. data/examples/rails_openid/README +153 -0
  20. data/examples/rails_openid/Rakefile +10 -0
  21. data/examples/rails_openid/app/controllers/application.rb +4 -0
  22. data/examples/rails_openid/app/controllers/consumer_controller.rb +125 -0
  23. data/examples/rails_openid/app/controllers/login_controller.rb +45 -0
  24. data/examples/rails_openid/app/controllers/server_controller.rb +265 -0
  25. data/examples/rails_openid/app/helpers/application_helper.rb +3 -0
  26. data/examples/rails_openid/app/helpers/login_helper.rb +2 -0
  27. data/examples/rails_openid/app/helpers/server_helper.rb +9 -0
  28. data/examples/rails_openid/app/views/consumer/index.rhtml +81 -0
  29. data/examples/rails_openid/app/views/layouts/server.rhtml +68 -0
  30. data/examples/rails_openid/app/views/login/index.rhtml +56 -0
  31. data/examples/rails_openid/app/views/server/decide.rhtml +26 -0
  32. data/examples/rails_openid/config/boot.rb +19 -0
  33. data/examples/rails_openid/config/database.yml +74 -0
  34. data/examples/rails_openid/config/environment.rb +54 -0
  35. data/examples/rails_openid/config/environments/development.rb +19 -0
  36. data/examples/rails_openid/config/environments/production.rb +19 -0
  37. data/examples/rails_openid/config/environments/test.rb +19 -0
  38. data/examples/rails_openid/config/routes.rb +24 -0
  39. data/examples/rails_openid/doc/README_FOR_APP +2 -0
  40. data/examples/rails_openid/public/404.html +8 -0
  41. data/examples/rails_openid/public/500.html +8 -0
  42. data/examples/rails_openid/public/dispatch.cgi +12 -0
  43. data/examples/rails_openid/public/dispatch.fcgi +26 -0
  44. data/examples/rails_openid/public/dispatch.rb +12 -0
  45. data/examples/rails_openid/public/favicon.ico +0 -0
  46. data/examples/rails_openid/public/images/openid_login_bg.gif +0 -0
  47. data/examples/rails_openid/public/javascripts/controls.js +750 -0
  48. data/examples/rails_openid/public/javascripts/dragdrop.js +584 -0
  49. data/examples/rails_openid/public/javascripts/effects.js +854 -0
  50. data/examples/rails_openid/public/javascripts/prototype.js +1785 -0
  51. data/examples/rails_openid/public/robots.txt +1 -0
  52. data/examples/rails_openid/script/about +3 -0
  53. data/examples/rails_openid/script/breakpointer +3 -0
  54. data/examples/rails_openid/script/console +3 -0
  55. data/examples/rails_openid/script/destroy +3 -0
  56. data/examples/rails_openid/script/generate +3 -0
  57. data/examples/rails_openid/script/performance/benchmarker +3 -0
  58. data/examples/rails_openid/script/performance/profiler +3 -0
  59. data/examples/rails_openid/script/plugin +3 -0
  60. data/examples/rails_openid/script/process/reaper +3 -0
  61. data/examples/rails_openid/script/process/spawner +3 -0
  62. data/examples/rails_openid/script/process/spinner +3 -0
  63. data/examples/rails_openid/script/runner +3 -0
  64. data/examples/rails_openid/script/server +3 -0
  65. data/examples/rails_openid/test/functional/login_controller_test.rb +18 -0
  66. data/examples/rails_openid/test/functional/server_controller_test.rb +18 -0
  67. data/examples/rails_openid/test/test_helper.rb +28 -0
  68. data/lib/hmac/hmac.rb +112 -0
  69. data/lib/hmac/sha1.rb +11 -0
  70. data/lib/hmac/sha2.rb +25 -0
  71. data/lib/openid.rb +22 -0
  72. data/lib/openid/association.rb +249 -0
  73. data/lib/openid/consumer.rb +395 -0
  74. data/lib/openid/consumer/associationmanager.rb +344 -0
  75. data/lib/openid/consumer/checkid_request.rb +186 -0
  76. data/lib/openid/consumer/discovery.rb +497 -0
  77. data/lib/openid/consumer/discovery_manager.rb +123 -0
  78. data/lib/openid/consumer/html_parse.rb +134 -0
  79. data/lib/openid/consumer/idres.rb +523 -0
  80. data/lib/openid/consumer/responses.rb +150 -0
  81. data/lib/openid/cryptutil.rb +115 -0
  82. data/lib/openid/dh.rb +89 -0
  83. data/lib/openid/extension.rb +39 -0
  84. data/lib/openid/extensions/ax.rb +539 -0
  85. data/lib/openid/extensions/oauth.rb +91 -0
  86. data/lib/openid/extensions/pape.rb +179 -0
  87. data/lib/openid/extensions/sreg.rb +277 -0
  88. data/lib/openid/extras.rb +11 -0
  89. data/lib/openid/fetchers.rb +258 -0
  90. data/lib/openid/kvform.rb +136 -0
  91. data/lib/openid/kvpost.rb +58 -0
  92. data/lib/openid/message.rb +553 -0
  93. data/lib/openid/protocolerror.rb +12 -0
  94. data/lib/openid/server.rb +1544 -0
  95. data/lib/openid/store.rb +10 -0
  96. data/lib/openid/store/filesystem.rb +272 -0
  97. data/lib/openid/store/interface.rb +75 -0
  98. data/lib/openid/store/memcache.rb +109 -0
  99. data/lib/openid/store/memory.rb +84 -0
  100. data/lib/openid/store/nonce.rb +68 -0
  101. data/lib/openid/trustroot.rb +349 -0
  102. data/lib/openid/urinorm.rb +75 -0
  103. data/lib/openid/util.rb +119 -0
  104. data/lib/openid/version.rb +3 -0
  105. data/lib/openid/yadis.rb +15 -0
  106. data/lib/openid/yadis/accept.rb +148 -0
  107. data/lib/openid/yadis/constants.rb +21 -0
  108. data/lib/openid/yadis/discovery.rb +153 -0
  109. data/lib/openid/yadis/filters.rb +205 -0
  110. data/lib/openid/yadis/htmltokenizer.rb +305 -0
  111. data/lib/openid/yadis/parsehtml.rb +45 -0
  112. data/lib/openid/yadis/services.rb +42 -0
  113. data/lib/openid/yadis/xrds.rb +155 -0
  114. data/lib/openid/yadis/xri.rb +90 -0
  115. data/lib/openid/yadis/xrires.rb +91 -0
  116. data/test/data/test_discover/openid_utf8.html +11 -0
  117. data/test/support/test_data_mixin.rb +127 -0
  118. data/test/support/test_util.rb +53 -0
  119. data/test/support/yadis_data.rb +131 -0
  120. data/test/support/yadis_data/accept.txt +124 -0
  121. data/test/support/yadis_data/dh.txt +29 -0
  122. data/test/support/yadis_data/example-xrds.xml +14 -0
  123. data/test/support/yadis_data/linkparse.txt +587 -0
  124. data/test/support/yadis_data/n2b64 +650 -0
  125. data/test/support/yadis_data/test1-discover.txt +137 -0
  126. data/test/support/yadis_data/test1-parsehtml.txt +152 -0
  127. data/test/support/yadis_data/test_discover/malformed_meta_tag.html +19 -0
  128. data/test/support/yadis_data/test_discover/openid.html +11 -0
  129. data/test/support/yadis_data/test_discover/openid2.html +11 -0
  130. data/test/support/yadis_data/test_discover/openid2_xrds.xml +12 -0
  131. data/test/support/yadis_data/test_discover/openid2_xrds_no_local_id.xml +11 -0
  132. data/test/support/yadis_data/test_discover/openid_1_and_2.html +11 -0
  133. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds.xml +16 -0
  134. data/test/support/yadis_data/test_discover/openid_1_and_2_xrds_bad_delegate.xml +17 -0
  135. data/test/support/yadis_data/test_discover/openid_and_yadis.html +12 -0
  136. data/test/support/yadis_data/test_discover/openid_no_delegate.html +10 -0
  137. data/test/support/yadis_data/test_discover/openid_utf8.html +11 -0
  138. data/test/support/yadis_data/test_discover/yadis_0entries.xml +12 -0
  139. data/test/support/yadis_data/test_discover/yadis_2_bad_local_id.xml +15 -0
  140. data/test/support/yadis_data/test_discover/yadis_2entries_delegate.xml +22 -0
  141. data/test/support/yadis_data/test_discover/yadis_2entries_idp.xml +21 -0
  142. data/test/support/yadis_data/test_discover/yadis_another_delegate.xml +14 -0
  143. data/test/support/yadis_data/test_discover/yadis_idp.xml +12 -0
  144. data/test/support/yadis_data/test_discover/yadis_idp_delegate.xml +13 -0
  145. data/test/support/yadis_data/test_discover/yadis_no_delegate.xml +11 -0
  146. data/test/support/yadis_data/test_xrds/=j3h.2007.11.14.xrds +25 -0
  147. data/test/support/yadis_data/test_xrds/README +12 -0
  148. data/test/support/yadis_data/test_xrds/delegated-20060809-r1.xrds +34 -0
  149. data/test/support/yadis_data/test_xrds/delegated-20060809-r2.xrds +34 -0
  150. data/test/support/yadis_data/test_xrds/delegated-20060809.xrds +34 -0
  151. data/test/support/yadis_data/test_xrds/no-xrd.xml +7 -0
  152. data/test/support/yadis_data/test_xrds/not-xrds.xml +2 -0
  153. data/test/support/yadis_data/test_xrds/prefixsometimes.xrds +34 -0
  154. data/test/support/yadis_data/test_xrds/ref.xrds +109 -0
  155. data/test/support/yadis_data/test_xrds/sometimesprefix.xrds +34 -0
  156. data/test/support/yadis_data/test_xrds/spoof1.xrds +25 -0
  157. data/test/support/yadis_data/test_xrds/spoof2.xrds +25 -0
  158. data/test/support/yadis_data/test_xrds/spoof3.xrds +37 -0
  159. data/test/support/yadis_data/test_xrds/status222.xrds +9 -0
  160. data/test/support/yadis_data/test_xrds/subsegments.xrds +58 -0
  161. data/test/support/yadis_data/test_xrds/valid-populated-xrds.xml +39 -0
  162. data/test/support/yadis_data/trustroot.txt +153 -0
  163. data/test/support/yadis_data/urinorm.txt +79 -0
  164. data/test/test_accept.rb +170 -0
  165. data/test/test_association.rb +268 -0
  166. data/test/test_associationmanager.rb +918 -0
  167. data/test/test_ax.rb +690 -0
  168. data/test/test_checkid_request.rb +293 -0
  169. data/test/test_consumer.rb +260 -0
  170. data/test/test_cryptutil.rb +119 -0
  171. data/test/test_dh.rb +85 -0
  172. data/test/test_discover.rb +848 -0
  173. data/test/test_discovery_manager.rb +259 -0
  174. data/test/test_extension.rb +46 -0
  175. data/test/test_extras.rb +35 -0
  176. data/test/test_fetchers.rb +554 -0
  177. data/test/test_filters.rb +269 -0
  178. data/test/test_helper.rb +4 -0
  179. data/test/test_idres.rb +961 -0
  180. data/test/test_kvform.rb +164 -0
  181. data/test/test_kvpost.rb +64 -0
  182. data/test/test_linkparse.rb +100 -0
  183. data/test/test_message.rb +1115 -0
  184. data/test/test_nonce.rb +89 -0
  185. data/test/test_oauth.rb +176 -0
  186. data/test/test_openid_yadis.rb +177 -0
  187. data/test/test_pape.rb +248 -0
  188. data/test/test_parsehtml.rb +79 -0
  189. data/test/test_responses.rb +63 -0
  190. data/test/test_server.rb +2455 -0
  191. data/test/test_sreg.rb +479 -0
  192. data/test/test_stores.rb +292 -0
  193. data/test/test_trustroot.rb +111 -0
  194. data/test/test_urinorm.rb +34 -0
  195. data/test/test_util.rb +145 -0
  196. data/test/test_xrds.rb +167 -0
  197. data/test/test_xri.rb +48 -0
  198. data/test/test_xrires.rb +67 -0
  199. data/test/test_yadis_discovery.rb +218 -0
  200. metadata +268 -0
@@ -0,0 +1,11 @@
1
+ class String
2
+ def starts_with?(other)
3
+ head = self[0, other.length]
4
+ head == other
5
+ end
6
+
7
+ def ends_with?(other)
8
+ tail = self[-1 * other.length, other.length]
9
+ tail == other
10
+ end
11
+ end
@@ -0,0 +1,258 @@
1
+ require 'net/http'
2
+ require 'openid'
3
+ require 'openid/util'
4
+
5
+ begin
6
+ require 'net/https'
7
+ rescue LoadError
8
+ OpenID::Util.log('WARNING: no SSL support found. Will not be able ' +
9
+ 'to fetch HTTPS URLs!')
10
+ require 'net/http'
11
+ end
12
+
13
+ MAX_RESPONSE_KB = 1024
14
+
15
+ module Net
16
+ class HTTP
17
+ def post_connection_check(hostname)
18
+ check_common_name = true
19
+ cert = @socket.io.peer_cert
20
+ cert.extensions.each { |ext|
21
+ next if ext.oid != "subjectAltName"
22
+ ext.value.split(/,\s+/).each{ |general_name|
23
+ if /\ADNS:(.*)/ =~ general_name
24
+ check_common_name = false
25
+ reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
26
+ return true if /\A#{reg}\z/i =~ hostname
27
+ elsif /\AIP Address:(.*)/ =~ general_name
28
+ check_common_name = false
29
+ return true if $1 == hostname
30
+ end
31
+ }
32
+ }
33
+ if check_common_name
34
+ cert.subject.to_a.each{ |oid, value|
35
+ if oid == "CN"
36
+ reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
37
+ return true if /\A#{reg}\z/i =~ hostname
38
+ end
39
+ }
40
+ end
41
+ raise OpenSSL::SSL::SSLError, "hostname does not match"
42
+ end
43
+ end
44
+ end
45
+
46
+ module OpenID
47
+ # Our HTTPResponse class extends Net::HTTPResponse with an additional
48
+ # method, final_url.
49
+ class HTTPResponse
50
+ attr_accessor :final_url
51
+
52
+ attr_accessor :_response
53
+
54
+ def self._from_net_response(response, final_url, headers=nil)
55
+ me = self.new
56
+ me._response = response
57
+ me.final_url = final_url
58
+ return me
59
+ end
60
+
61
+ def method_missing(method, *args)
62
+ @_response.send(method, *args)
63
+ end
64
+
65
+ def body=(s)
66
+ @_response.instance_variable_set('@body', s)
67
+ # XXX Hack to work around ruby's HTTP library behavior. @body
68
+ # is only returned if it has been read from the response
69
+ # object's socket, but since we're not using a socket in this
70
+ # case, we need to set the @read flag to true to avoid a bug in
71
+ # Net::HTTPResponse.stream_check when @socket is nil.
72
+ @_response.instance_variable_set('@read', true)
73
+ end
74
+ end
75
+
76
+ class FetchingError < OpenIDError
77
+ end
78
+
79
+ class HTTPRedirectLimitReached < FetchingError
80
+ end
81
+
82
+ class SSLFetchingError < FetchingError
83
+ end
84
+
85
+ @fetcher = nil
86
+
87
+ def self.fetch(url, body=nil, headers=nil,
88
+ redirect_limit=StandardFetcher::REDIRECT_LIMIT)
89
+ return fetcher.fetch(url, body, headers, redirect_limit)
90
+ end
91
+
92
+ def self.fetcher
93
+ if @fetcher.nil?
94
+ @fetcher = StandardFetcher.new
95
+ end
96
+
97
+ return @fetcher
98
+ end
99
+
100
+ def self.fetcher=(fetcher)
101
+ @fetcher = fetcher
102
+ end
103
+
104
+ # Set the default fetcher to use the HTTP proxy defined in the environment
105
+ # variable 'http_proxy'.
106
+ def self.fetcher_use_env_http_proxy
107
+ proxy_string = ENV['http_proxy']
108
+ return unless proxy_string
109
+
110
+ proxy_uri = URI.parse(proxy_string)
111
+ @fetcher = StandardFetcher.new(proxy_uri.host, proxy_uri.port,
112
+ proxy_uri.user, proxy_uri.password)
113
+ end
114
+
115
+ class StandardFetcher
116
+
117
+ USER_AGENT = "ruby-openid/#{OpenID::VERSION} (#{RUBY_PLATFORM})"
118
+
119
+ REDIRECT_LIMIT = 5
120
+ TIMEOUT = 60
121
+
122
+ attr_accessor :ca_file
123
+ attr_accessor :timeout
124
+
125
+ # I can fetch through a HTTP proxy; arguments are as for Net::HTTP::Proxy.
126
+ def initialize(proxy_addr=nil, proxy_port=nil,
127
+ proxy_user=nil, proxy_pass=nil)
128
+ @ca_file = nil
129
+ @proxy = Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user, proxy_pass)
130
+ @timeout = TIMEOUT
131
+ end
132
+
133
+ def supports_ssl?(conn)
134
+ return conn.respond_to?(:use_ssl=)
135
+ end
136
+
137
+ def make_http(uri)
138
+ http = @proxy.new(uri.host, uri.port)
139
+ http.read_timeout = @timeout
140
+ http.open_timeout = @timeout
141
+ return http
142
+ end
143
+
144
+ def set_verified(conn, verify)
145
+ if verify
146
+ conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
147
+ else
148
+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
149
+ end
150
+ end
151
+
152
+ def make_connection(uri)
153
+ conn = make_http(uri)
154
+
155
+ if !conn.is_a?(Net::HTTP)
156
+ raise RuntimeError, sprintf("Expected Net::HTTP object from make_http; got %s",
157
+ conn.class)
158
+ end
159
+
160
+ if uri.scheme == 'https'
161
+ if supports_ssl?(conn)
162
+
163
+ conn.use_ssl = true
164
+
165
+ if @ca_file
166
+ set_verified(conn, true)
167
+ conn.ca_file = @ca_file
168
+ else
169
+ Util.log("WARNING: making https request to #{uri} without verifying " +
170
+ "server certificate; no CA path was specified.")
171
+ set_verified(conn, false)
172
+ end
173
+ else
174
+ raise RuntimeError, "SSL support not found; cannot fetch #{uri}"
175
+ end
176
+ end
177
+
178
+ return conn
179
+ end
180
+
181
+ def fetch(url, body=nil, headers=nil, redirect_limit=REDIRECT_LIMIT)
182
+ unparsed_url = url.dup
183
+ url = URI::parse(url)
184
+ if url.nil?
185
+ raise FetchingError, "Invalid URL: #{unparsed_url}"
186
+ end
187
+
188
+ headers ||= {}
189
+ headers['User-agent'] ||= USER_AGENT
190
+
191
+ begin
192
+ conn = make_connection(url)
193
+ response = nil
194
+
195
+ response = conn.start {
196
+ # Check the certificate against the URL's hostname
197
+ if supports_ssl?(conn) and conn.use_ssl?
198
+ conn.post_connection_check(url.host)
199
+ end
200
+
201
+ if body.nil?
202
+ conn.request_get(url.request_uri, headers)
203
+ else
204
+ headers["Content-type"] ||= "application/x-www-form-urlencoded"
205
+ conn.request_post(url.request_uri, body, headers)
206
+ end
207
+ }
208
+ setup_encoding(response)
209
+ rescue Timeout::Error => why
210
+ raise FetchingError, "Error fetching #{url}: #{why}"
211
+ rescue RuntimeError => why
212
+ raise why
213
+ rescue OpenSSL::SSL::SSLError => why
214
+ raise SSLFetchingError, "Error connecting to SSL URL #{url}: #{why}"
215
+ rescue FetchingError => why
216
+ raise why
217
+ rescue Exception => why
218
+ raise FetchingError, "Error fetching #{url}: #{why}"
219
+ end
220
+
221
+ case response
222
+ when Net::HTTPRedirection
223
+ if redirect_limit <= 0
224
+ raise HTTPRedirectLimitReached.new(
225
+ "Too many redirects, not fetching #{response['location']}")
226
+ end
227
+ begin
228
+ return fetch(response['location'], body, headers, redirect_limit - 1)
229
+ rescue HTTPRedirectLimitReached => e
230
+ raise e
231
+ rescue FetchingError => why
232
+ raise FetchingError, "Error encountered in redirect from #{url}: #{why}"
233
+ end
234
+ else
235
+ return HTTPResponse._from_net_response(response, unparsed_url)
236
+ end
237
+ end
238
+
239
+ private
240
+ def setup_encoding(response)
241
+ return unless defined?(::Encoding::ASCII_8BIT)
242
+ charset = response.type_params["charset"]
243
+ return if charset.nil?
244
+ encoding = nil
245
+ begin
246
+ encoding = Encoding.find(charset)
247
+ rescue ArgumentError
248
+ end
249
+ encoding ||= Encoding::ASCII_8BIT
250
+ body = response.body
251
+ if body.respond_to?(:force_encoding)
252
+ body.force_encoding(encoding)
253
+ else
254
+ body.set_encoding(encoding)
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,136 @@
1
+
2
+ module OpenID
3
+
4
+ class KVFormError < Exception
5
+ end
6
+
7
+ module Util
8
+
9
+ def Util.seq_to_kv(seq, strict=false)
10
+ # Represent a sequence of pairs of strings as newline-terminated
11
+ # key:value pairs. The pairs are generated in the order given.
12
+ #
13
+ # @param seq: The pairs
14
+ #
15
+ # returns a string representation of the sequence
16
+ err = lambda { |msg|
17
+ msg = "seq_to_kv warning: #{msg}: #{seq.inspect}"
18
+ if strict
19
+ raise KVFormError, msg
20
+ else
21
+ Util.log(msg)
22
+ end
23
+ }
24
+
25
+ lines = []
26
+ seq.each { |k, v|
27
+ if !k.is_a?(String)
28
+ err.call("Converting key to string: #{k.inspect}")
29
+ k = k.to_s
30
+ end
31
+
32
+ if !k.index("\n").nil?
33
+ raise KVFormError, "Invalid input for seq_to_kv: key contains newline: #{k.inspect}"
34
+ end
35
+
36
+ if !k.index(":").nil?
37
+ raise KVFormError, "Invalid input for seq_to_kv: key contains colon: #{k.inspect}"
38
+ end
39
+
40
+ if k.strip() != k
41
+ err.call("Key has whitespace at beginning or end: #{k.inspect}")
42
+ end
43
+
44
+ if !v.is_a?(String)
45
+ err.call("Converting value to string: #{v.inspect}")
46
+ v = v.to_s
47
+ end
48
+
49
+ if !v.index("\n").nil?
50
+ raise KVFormError, "Invalid input for seq_to_kv: value contains newline: #{v.inspect}"
51
+ end
52
+
53
+ if v.strip() != v
54
+ err.call("Value has whitespace at beginning or end: #{v.inspect}")
55
+ end
56
+
57
+ lines << k + ":" + v + "\n"
58
+ }
59
+
60
+ return lines.join("")
61
+ end
62
+
63
+ def Util.kv_to_seq(data, strict=false)
64
+ # After one parse, seq_to_kv and kv_to_seq are inverses, with no
65
+ # warnings:
66
+ #
67
+ # seq = kv_to_seq(s)
68
+ # seq_to_kv(kv_to_seq(seq)) == seq
69
+ err = lambda { |msg|
70
+ msg = "kv_to_seq warning: #{msg}: #{data.inspect}"
71
+ if strict
72
+ raise KVFormError, msg
73
+ else
74
+ Util.log(msg)
75
+ end
76
+ }
77
+
78
+ lines = data.split("\n")
79
+ if data.length == 0
80
+ return []
81
+ end
82
+
83
+ if data[-1].chr != "\n"
84
+ err.call("Does not end in a newline")
85
+ # We don't expect the last element of lines to be an empty
86
+ # string because split() doesn't behave that way.
87
+ end
88
+
89
+ pairs = []
90
+ line_num = 0
91
+ lines.each { |line|
92
+ line_num += 1
93
+
94
+ # Ignore blank lines
95
+ if line.strip() == ""
96
+ next
97
+ end
98
+
99
+ pair = line.split(':', 2)
100
+ if pair.length == 2
101
+ k, v = pair
102
+ k_s = k.strip()
103
+ if k_s != k
104
+ msg = "In line #{line_num}, ignoring leading or trailing whitespace in key #{k.inspect}"
105
+ err.call(msg)
106
+ end
107
+
108
+ if k_s.length == 0
109
+ err.call("In line #{line_num}, got empty key")
110
+ end
111
+
112
+ v_s = v.strip()
113
+ if v_s != v
114
+ msg = "In line #{line_num}, ignoring leading or trailing whitespace in value #{v.inspect}"
115
+ err.call(msg)
116
+ end
117
+
118
+ pairs << [k_s, v_s]
119
+ else
120
+ err.call("Line #{line_num} does not contain a colon")
121
+ end
122
+ }
123
+
124
+ return pairs
125
+ end
126
+
127
+ def Util.dict_to_kv(d)
128
+ return seq_to_kv(d.entries.sort)
129
+ end
130
+
131
+ def Util.kv_to_dict(s)
132
+ seq = kv_to_seq(s)
133
+ return Hash[*seq.flatten]
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,58 @@
1
+ require "openid/message"
2
+ require "openid/fetchers"
3
+
4
+ module OpenID
5
+ # Exception that is raised when the server returns a 400 response
6
+ # code to a direct request.
7
+ class ServerError < OpenIDError
8
+ attr_reader :error_text, :error_code, :message
9
+
10
+ def initialize(error_text, error_code, message)
11
+ super(error_text)
12
+ @error_text = error_text
13
+ @error_code = error_code
14
+ @message = message
15
+ end
16
+
17
+ def self.from_message(msg)
18
+ error_text = msg.get_arg(OPENID_NS, 'error',
19
+ '<no error message supplied>')
20
+ error_code = msg.get_arg(OPENID_NS, 'error_code')
21
+ return self.new(error_text, error_code, msg)
22
+ end
23
+ end
24
+
25
+ class KVPostNetworkError < OpenIDError
26
+ end
27
+ class HTTPStatusError < OpenIDError
28
+ end
29
+
30
+ class Message
31
+ def self.from_http_response(response, server_url)
32
+ msg = self.from_kvform(response.body)
33
+ case response.code.to_i
34
+ when 200
35
+ return msg
36
+ when 206
37
+ return msg
38
+ when 400
39
+ raise ServerError.from_message(msg)
40
+ else
41
+ error_message = "bad status code from server #{server_url}: "\
42
+ "#{response.code}"
43
+ raise HTTPStatusError.new(error_message)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Send the message to the server via HTTP POST and receive and parse
49
+ # a response in KV Form
50
+ def self.make_kv_post(request_message, server_url)
51
+ begin
52
+ http_response = self.fetch(server_url, request_message.to_url_encoded)
53
+ rescue Exception
54
+ raise KVPostNetworkError.new("Unable to contact OpenID server: #{$!.to_s}")
55
+ end
56
+ return Message.from_http_response(http_response, server_url)
57
+ end
58
+ end