webrick 1.3.1 → 1.4.0.beta1

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.

Potentially problematic release.


This version of webrick might be problematic. Click here for more details.

Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/lib/webrick.rb +6 -6
  3. data/lib/webrick/accesslog.rb +9 -1
  4. data/lib/webrick/cgi.rb +51 -2
  5. data/lib/webrick/compat.rb +2 -1
  6. data/lib/webrick/config.rb +42 -5
  7. data/lib/webrick/cookie.rb +68 -6
  8. data/lib/webrick/htmlutils.rb +4 -2
  9. data/lib/webrick/httpauth.rb +1 -0
  10. data/lib/webrick/httpauth/authenticator.rb +13 -8
  11. data/lib/webrick/httpauth/basicauth.rb +3 -3
  12. data/lib/webrick/httpauth/digestauth.rb +25 -9
  13. data/lib/webrick/httpauth/htdigest.rb +8 -4
  14. data/lib/webrick/httpauth/htgroup.rb +1 -0
  15. data/lib/webrick/httpauth/htpasswd.rb +7 -3
  16. data/lib/webrick/httpauth/userdb.rb +1 -0
  17. data/lib/webrick/httpproxy.rb +47 -14
  18. data/lib/webrick/httprequest.rb +142 -16
  19. data/lib/webrick/httpresponse.rb +96 -24
  20. data/lib/webrick/https.rb +24 -1
  21. data/lib/webrick/httpserver.rb +20 -4
  22. data/lib/webrick/httpservlet.rb +1 -0
  23. data/lib/webrick/httpservlet/abstract.rb +2 -1
  24. data/lib/webrick/httpservlet/cgi_runner.rb +1 -0
  25. data/lib/webrick/httpservlet/cgihandler.rb +19 -5
  26. data/lib/webrick/httpservlet/erbhandler.rb +2 -1
  27. data/lib/webrick/httpservlet/filehandler.rb +87 -34
  28. data/lib/webrick/httpservlet/prochandler.rb +14 -0
  29. data/lib/webrick/httpstatus.rb +24 -10
  30. data/lib/webrick/httputils.rb +129 -13
  31. data/lib/webrick/httpversion.rb +28 -1
  32. data/lib/webrick/log.rb +22 -2
  33. data/lib/webrick/server.rb +203 -60
  34. data/lib/webrick/ssl.rb +80 -5
  35. data/lib/webrick/utils.rb +97 -67
  36. data/lib/webrick/version.rb +6 -1
  37. metadata +59 -69
  38. data/README.txt +0 -21
  39. data/sample/webrick/demo-app.rb +0 -66
  40. data/sample/webrick/demo-multipart.cgi +0 -12
  41. data/sample/webrick/demo-servlet.rb +0 -6
  42. data/sample/webrick/demo-urlencoded.cgi +0 -12
  43. data/sample/webrick/hello.cgi +0 -11
  44. data/sample/webrick/hello.rb +0 -8
  45. data/sample/webrick/httpd.rb +0 -23
  46. data/sample/webrick/httpproxy.rb +0 -25
  47. data/sample/webrick/httpsd.rb +0 -33
  48. data/test/openssl/utils.rb +0 -313
  49. data/test/ruby/envutil.rb +0 -208
  50. data/test/webrick/test_cgi.rb +0 -134
  51. data/test/webrick/test_cookie.rb +0 -131
  52. data/test/webrick/test_filehandler.rb +0 -285
  53. data/test/webrick/test_httpauth.rb +0 -167
  54. data/test/webrick/test_httpproxy.rb +0 -282
  55. data/test/webrick/test_httprequest.rb +0 -411
  56. data/test/webrick/test_httpresponse.rb +0 -49
  57. data/test/webrick/test_httpserver.rb +0 -305
  58. data/test/webrick/test_httputils.rb +0 -96
  59. data/test/webrick/test_httpversion.rb +0 -40
  60. data/test/webrick/test_server.rb +0 -67
  61. data/test/webrick/test_utils.rb +0 -64
  62. data/test/webrick/utils.rb +0 -58
  63. data/test/webrick/webrick.cgi +0 -36
  64. data/test/webrick/webrick_long_filename.cgi +0 -36
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpauth/basicauth.rb -- HTTP basic access authentication
3
4
  #
@@ -34,7 +35,7 @@ module WEBrick
34
35
  class BasicAuth
35
36
  include Authenticator
36
37
 
37
- AuthScheme = "Basic"
38
+ AuthScheme = "Basic" # :nodoc:
38
39
 
39
40
  ##
40
41
  # Used by UserDB to create a basic password entry
@@ -89,8 +90,7 @@ module WEBrick
89
90
  end
90
91
 
91
92
  ##
92
- # Returns a challenge response which asks for for authentication
93
- # information
93
+ # Returns a challenge response which asks for authentication information
94
94
 
95
95
  def challenge(req, res)
96
96
  res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpauth/digestauth.rb -- HTTP digest access authentication
3
4
  #
@@ -45,9 +46,22 @@ module WEBrick
45
46
  class DigestAuth
46
47
  include Authenticator
47
48
 
48
- AuthScheme = "Digest"
49
- OpaqueInfo = Struct.new(:time, :nonce, :nc)
50
- attr_reader :algorithm, :qop
49
+ AuthScheme = "Digest" # :nodoc:
50
+
51
+ ##
52
+ # Struct containing the opaque portion of the digest authentication
53
+
54
+ OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
55
+
56
+ ##
57
+ # Digest authentication algorithm
58
+
59
+ attr_reader :algorithm
60
+
61
+ ##
62
+ # Quality of protection. RFC 2617 defines "auth" and "auth-int"
63
+
64
+ attr_reader :qop
51
65
 
52
66
  ##
53
67
  # Used by UserDB to create a digest password entry
@@ -97,7 +111,7 @@ module WEBrick
97
111
  @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
98
112
  @opaques = {}
99
113
  @last_nonce_expire = Time.now
100
- @mutex = Mutex.new
114
+ @mutex = Thread::Mutex.new
101
115
  end
102
116
 
103
117
  ##
@@ -115,8 +129,7 @@ module WEBrick
115
129
  end
116
130
 
117
131
  ##
118
- # Returns a challenge response which asks for for authentication
119
- # information
132
+ # Returns a challenge response which asks for authentication information
120
133
 
121
134
  def challenge(req, res, stale=false)
122
135
  nonce = generate_next_nonce(req)
@@ -142,6 +155,8 @@ module WEBrick
142
155
 
143
156
  private
144
157
 
158
+ # :stopdoc:
159
+
145
160
  MustParams = ['username','realm','nonce','uri','response']
146
161
  MustParamsAuth = ['cnonce','nc']
147
162
 
@@ -189,7 +204,7 @@ module WEBrick
189
204
 
190
205
  password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
191
206
  unless password
192
- error('%s: the user is not allowd.', auth_req['username'])
207
+ error('%s: the user is not allowed.', auth_req['username'])
193
208
  return false
194
209
  end
195
210
 
@@ -297,7 +312,7 @@ module WEBrick
297
312
  def generate_next_nonce(req)
298
313
  now = "%012d" % req.request_time.to_i
299
314
  pk = hexdigest(now, @instance_key)[0,32]
300
- nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
315
+ nonce = [now + ":" + pk].pack("m0") # it has 60 length of chars.
301
316
  nonce
302
317
  end
303
318
 
@@ -375,6 +390,7 @@ module WEBrick
375
390
  @h.hexdigest(args.join(":"))
376
391
  end
377
392
 
393
+ # :startdoc:
378
394
  end
379
395
 
380
396
  ##
@@ -384,7 +400,7 @@ module WEBrick
384
400
  include ProxyAuthenticator
385
401
 
386
402
  private
387
- def check_uri(req, auth_req)
403
+ def check_uri(req, auth_req) # :nodoc:
388
404
  return true
389
405
  end
390
406
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpauth/htdigest.rb -- Apache compatible htdigest file
3
4
  #
@@ -37,7 +38,7 @@ module WEBrick
37
38
  @path = path
38
39
  @mtime = Time.at(0)
39
40
  @digest = Hash.new
40
- @mutex = Mutex::new
41
+ @mutex = Thread::Mutex::new
41
42
  @auth_type = DigestAuth
42
43
  open(@path,"a").close unless File::exist?(@path)
43
44
  reload
@@ -70,13 +71,16 @@ module WEBrick
70
71
 
71
72
  def flush(output=nil)
72
73
  output ||= @path
73
- tmp = Tempfile.new("htpasswd", File::dirname(output))
74
+ tmp = Tempfile.create("htpasswd", File::dirname(output))
75
+ renamed = false
74
76
  begin
75
77
  each{|item| tmp.puts(item.join(":")) }
76
78
  tmp.close
77
79
  File::rename(tmp.path, output)
78
- rescue
79
- tmp.close(true)
80
+ renamed = true
81
+ ensure
82
+ tmp.close
83
+ File.unlink(tmp.path) if !renamed
80
84
  end
81
85
  end
82
86
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpauth/htgroup.rb -- Apache compatible htgroup file
3
4
  #
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpauth/htpasswd -- Apache compatible htpasswd file
3
4
  #
@@ -75,13 +76,16 @@ module WEBrick
75
76
 
76
77
  def flush(output=nil)
77
78
  output ||= @path
78
- tmp = Tempfile.new("htpasswd", File::dirname(output))
79
+ tmp = Tempfile.create("htpasswd", File::dirname(output))
80
+ renamed = false
79
81
  begin
80
82
  each{|item| tmp.puts(item.join(":")) }
81
83
  tmp.close
82
84
  File::rename(tmp.path, output)
83
- rescue
84
- tmp.close(true)
85
+ renamed = true
86
+ ensure
87
+ tmp.close
88
+ File.unlink(tmp.path) if !renamed
85
89
  end
86
90
  end
87
91
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #--
2
3
  # httpauth/userdb.rb -- UserDB mix-in module.
3
4
  #
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpproxy.rb -- HTTPProxy Class
3
4
  #
@@ -12,19 +13,18 @@
12
13
  require "webrick/httpserver"
13
14
  require "net/http"
14
15
 
15
- Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
16
-
17
16
  module WEBrick
18
- NullReader = Object.new
19
- class << NullReader
17
+
18
+ NullReader = Object.new # :nodoc:
19
+ class << NullReader # :nodoc:
20
20
  def read(*args)
21
21
  nil
22
22
  end
23
23
  alias gets read
24
24
  end
25
25
 
26
- FakeProxyURI = Object.new
27
- class << FakeProxyURI
26
+ FakeProxyURI = Object.new # :nodoc:
27
+ class << FakeProxyURI # :nodoc:
28
28
  def method_missing(meth, *args)
29
29
  if %w(scheme host port path query userinfo).member?(meth.to_s)
30
30
  return nil
@@ -33,8 +33,38 @@ module WEBrick
33
33
  end
34
34
  end
35
35
 
36
+ # :startdoc:
37
+
36
38
  ##
37
39
  # An HTTP Proxy server which proxies GET, HEAD and POST requests.
40
+ #
41
+ # To create a simple proxy server:
42
+ #
43
+ # require 'webrick'
44
+ # require 'webrick/httpproxy'
45
+ #
46
+ # proxy = WEBrick::HTTPProxyServer.new Port: 8000
47
+ #
48
+ # trap 'INT' do proxy.shutdown end
49
+ # trap 'TERM' do proxy.shutdown end
50
+ #
51
+ # proxy.start
52
+ #
53
+ # See ::new for proxy-specific configuration items.
54
+ #
55
+ # == Modifying proxied responses
56
+ #
57
+ # To modify content the proxy server returns use the +:ProxyContentHandler+
58
+ # option:
59
+ #
60
+ # handler = proc do |req, res|
61
+ # if res['content-type'] == 'text/plain' then
62
+ # res.body << "\nThis content was proxied!\n"
63
+ # end
64
+ # end
65
+ #
66
+ # proxy =
67
+ # WEBrick::HTTPProxyServer.new Port: 8000, ProxyContentHandler: handler
38
68
 
39
69
  class HTTPProxyServer < HTTPServer
40
70
 
@@ -46,7 +76,7 @@ module WEBrick
46
76
  # request
47
77
  # :ProxyVia:: Appended to the via header
48
78
  # :ProxyURI:: The proxy server's URI
49
- # :ProxyContentHandler:: Called with a request and resopnse and allows
79
+ # :ProxyContentHandler:: Called with a request and response and allows
50
80
  # modification of the response
51
81
  # :ProxyTimeout:: Sets the proxy timeouts to 30 seconds for open and 60
52
82
  # seconds for read operations
@@ -57,6 +87,7 @@ module WEBrick
57
87
  @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
58
88
  end
59
89
 
90
+ # :stopdoc:
60
91
  def service(req, res)
61
92
  if req.request_method == "CONNECT"
62
93
  do_CONNECT(req, res)
@@ -112,7 +143,7 @@ module WEBrick
112
143
  if proxy = proxy_uri(req, res)
113
144
  proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
114
145
  if proxy.userinfo
115
- credentials = "Basic " + [proxy.userinfo].pack("m").delete("\n")
146
+ credentials = "Basic " + [proxy.userinfo].pack("m0")
116
147
  end
117
148
  host, port = proxy.host, proxy.port
118
149
  end
@@ -126,12 +157,12 @@ module WEBrick
126
157
  os << proxy_request_line << CRLF
127
158
  @logger.debug("CONNECT: > #{proxy_request_line}")
128
159
  if credentials
129
- @logger.debug("CONNECT: sending a credentials")
160
+ @logger.debug("CONNECT: sending credentials")
130
161
  os << "Proxy-Authorization: " << credentials << CRLF
131
162
  end
132
163
  os << CRLF
133
164
  proxy_status_line = os.gets(LF)
134
- @logger.debug("CONNECT: read a Status-Line form the upstream server")
165
+ @logger.debug("CONNECT: read Status-Line from the upstream server")
135
166
  @logger.debug("CONNECT: < #{proxy_status_line}")
136
167
  if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
137
168
  while line = os.gets(LF)
@@ -154,7 +185,7 @@ module WEBrick
154
185
  res.send_response(ua)
155
186
  access_log(@config, req, res)
156
187
 
157
- # Should clear request-line not to send the sesponse twice.
188
+ # Should clear request-line not to send the response twice.
158
189
  # see: HTTPServer#run
159
190
  req.parse(NullReader) rescue nil
160
191
  end
@@ -171,7 +202,7 @@ module WEBrick
171
202
  ua.syswrite(buf)
172
203
  end
173
204
  end
174
- rescue => ex
205
+ rescue
175
206
  os.close
176
207
  @logger.debug("CONNECT #{host}:#{port}: closed")
177
208
  end
@@ -263,7 +294,7 @@ module WEBrick
263
294
  if upstream = proxy_uri(req, res)
264
295
  if upstream.userinfo
265
296
  header['proxy-authorization'] =
266
- "Basic " + [upstream.userinfo].pack("m").delete("\n")
297
+ "Basic " + [upstream.userinfo].pack("m0")
267
298
  end
268
299
  return upstream
269
300
  end
@@ -282,7 +313,7 @@ module WEBrick
282
313
  http.start do
283
314
  if @config[:ProxyTimeout]
284
315
  ################################## these issues are
285
- http.open_timeout = 30 # secs # necessary (maybe bacause
316
+ http.open_timeout = 30 # secs # necessary (maybe because
286
317
  http.read_timeout = 60 # secs # Ruby's bug, but why?)
287
318
  ##################################
288
319
  end
@@ -301,5 +332,7 @@ module WEBrick
301
332
  set_via(res)
302
333
  res.body = response.body
303
334
  end
335
+
336
+ # :stopdoc:
304
337
  end
305
338
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httprequest.rb -- HTTPRequest Class
3
4
  #
@@ -17,31 +18,137 @@ require 'webrick/cookie'
17
18
  module WEBrick
18
19
 
19
20
  ##
20
- # An HTTP request.
21
+ # An HTTP request. This is consumed by service and do_* methods in
22
+ # WEBrick servlets
23
+
21
24
  class HTTPRequest
22
25
 
23
- BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
26
+ BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ] # :nodoc:
24
27
 
25
28
  # :section: Request line
29
+
30
+ ##
31
+ # The complete request line such as:
32
+ #
33
+ # GET / HTTP/1.1
34
+
26
35
  attr_reader :request_line
27
- attr_reader :request_method, :unparsed_uri, :http_version
36
+
37
+ ##
38
+ # The request method, GET, POST, PUT, etc.
39
+
40
+ attr_reader :request_method
41
+
42
+ ##
43
+ # The unparsed URI of the request
44
+
45
+ attr_reader :unparsed_uri
46
+
47
+ ##
48
+ # The HTTP version of the request
49
+
50
+ attr_reader :http_version
28
51
 
29
52
  # :section: Request-URI
30
- attr_reader :request_uri, :path
31
- attr_accessor :script_name, :path_info, :query_string
53
+
54
+ ##
55
+ # The parsed URI of the request
56
+
57
+ attr_reader :request_uri
58
+
59
+ ##
60
+ # The request path
61
+
62
+ attr_reader :path
63
+
64
+ ##
65
+ # The script name (CGI variable)
66
+
67
+ attr_accessor :script_name
68
+
69
+ ##
70
+ # The path info (CGI variable)
71
+
72
+ attr_accessor :path_info
73
+
74
+ ##
75
+ # The query from the URI of the request
76
+
77
+ attr_accessor :query_string
32
78
 
33
79
  # :section: Header and entity body
34
- attr_reader :raw_header, :header, :cookies
35
- attr_reader :accept, :accept_charset
36
- attr_reader :accept_encoding, :accept_language
80
+
81
+ ##
82
+ # The raw header of the request
83
+
84
+ attr_reader :raw_header
85
+
86
+ ##
87
+ # The parsed header of the request
88
+
89
+ attr_reader :header
90
+
91
+ ##
92
+ # The parsed request cookies
93
+
94
+ attr_reader :cookies
95
+
96
+ ##
97
+ # The Accept header value
98
+
99
+ attr_reader :accept
100
+
101
+ ##
102
+ # The Accept-Charset header value
103
+
104
+ attr_reader :accept_charset
105
+
106
+ ##
107
+ # The Accept-Encoding header value
108
+
109
+ attr_reader :accept_encoding
110
+
111
+ ##
112
+ # The Accept-Language header value
113
+
114
+ attr_reader :accept_language
37
115
 
38
116
  # :section:
117
+
118
+ ##
119
+ # The remote user (CGI variable)
120
+
39
121
  attr_accessor :user
40
- attr_reader :addr, :peeraddr
122
+
123
+ ##
124
+ # The socket address of the server
125
+
126
+ attr_reader :addr
127
+
128
+ ##
129
+ # The socket address of the client
130
+
131
+ attr_reader :peeraddr
132
+
133
+ ##
134
+ # Hash of request attributes
135
+
41
136
  attr_reader :attributes
137
+
138
+ ##
139
+ # Is this a keep-alive connection?
140
+
42
141
  attr_reader :keep_alive
142
+
143
+ ##
144
+ # The local time this request was received
145
+
43
146
  attr_reader :request_time
44
147
 
148
+ ##
149
+ # Creates a new HTTP request. WEBrick::Config::HTTP is the default
150
+ # configuration.
151
+
45
152
  def initialize(config)
46
153
  @config = config
47
154
  @buffer_size = @config[:InputBufferSize]
@@ -78,6 +185,10 @@ module WEBrick
78
185
  @forwarded_server = @forwarded_for = nil
79
186
  end
80
187
 
188
+ ##
189
+ # Parses a request from +socket+. This is called internally by
190
+ # WEBrick::HTTPServer.
191
+
81
192
  def parse(socket=nil)
82
193
  @socket = socket
83
194
  begin
@@ -126,16 +237,21 @@ module WEBrick
126
237
  end
127
238
  end
128
239
 
240
+ ##
129
241
  # Generate HTTP/1.1 100 continue response if the client expects it,
130
242
  # otherwise does nothing.
131
- def continue
243
+
244
+ def continue # :nodoc:
132
245
  if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
133
246
  @socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
134
247
  @header.delete('expect')
135
248
  end
136
249
  end
137
250
 
138
- def body(&block)
251
+ ##
252
+ # Returns the request body.
253
+
254
+ def body(&block) # :yields: body_chunk
139
255
  block ||= Proc.new{|chunk| @body << chunk }
140
256
  read_body(@socket, block)
141
257
  @body.empty? ? nil : @body
@@ -237,11 +353,14 @@ module WEBrick
237
353
  ret
238
354
  end
239
355
 
240
- def fixup()
356
+ ##
357
+ # Consumes any remaining body and updates keep-alive status
358
+
359
+ def fixup() # :nodoc:
241
360
  begin
242
361
  body{|chunk| } # read remaining body
243
362
  rescue HTTPStatus::Error => ex
244
- @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
363
+ @logger.error("HTTPRequest#fixup: #{ex.class} occurred.")
245
364
  @keep_alive = false
246
365
  rescue => ex
247
366
  @logger.error(ex)
@@ -251,7 +370,8 @@ module WEBrick
251
370
 
252
371
  # This method provides the metavariables defined by the revision 3
253
372
  # of "The WWW Common Gateway Interface Version 1.1"
254
- # http://Web.Golux.Com/coar/cgi/
373
+ # To browse the current document of CGI Version 1.1, see below:
374
+ # http://tools.ietf.org/html/rfc3875
255
375
 
256
376
  def meta_vars
257
377
  meta = Hash.new
@@ -290,6 +410,8 @@ module WEBrick
290
410
 
291
411
  private
292
412
 
413
+ # :stopdoc:
414
+
293
415
  MAX_URI_LENGTH = 2083 # :nodoc:
294
416
 
295
417
  def read_request_line(socket)
@@ -400,7 +522,7 @@ module WEBrick
400
522
  }
401
523
  rescue Errno::ECONNRESET
402
524
  return nil
403
- rescue TimeoutError
525
+ rescue Timeout::Error
404
526
  raise HTTPStatus::RequestTimeout
405
527
  end
406
528
  end
@@ -445,7 +567,9 @@ module WEBrick
445
567
  if @forwarded_server = self["x-forwarded-server"]
446
568
  @forwarded_server = @forwarded_server.split(",", 2).first
447
569
  end
448
- @forwarded_proto = self["x-forwarded-proto"]
570
+ if @forwarded_proto = self["x-forwarded-proto"]
571
+ @forwarded_proto = @forwarded_proto.split(",", 2).first
572
+ end
449
573
  if host_port = self["x-forwarded-host"]
450
574
  host_port = host_port.split(",", 2).first
451
575
  @forwarded_host, tmp = host_port.split(":", 2)
@@ -457,5 +581,7 @@ module WEBrick
457
581
  @forwarded_for = addrs.first
458
582
  end
459
583
  end
584
+
585
+ # :startdoc:
460
586
  end
461
587
  end