rubysl-webrick 1.0.0 → 2.0.0

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 (39) hide show
  1. checksums.yaml +14 -6
  2. data/.travis.yml +5 -6
  3. data/lib/rubysl/webrick/version.rb +1 -1
  4. data/lib/rubysl/webrick/webrick.rb +199 -2
  5. data/lib/webrick/accesslog.rb +96 -5
  6. data/lib/webrick/cgi.rb +80 -29
  7. data/lib/webrick/compat.rb +20 -0
  8. data/lib/webrick/config.rb +59 -5
  9. data/lib/webrick/cookie.rb +66 -5
  10. data/lib/webrick/htmlutils.rb +4 -1
  11. data/lib/webrick/httpauth.rb +53 -3
  12. data/lib/webrick/httpauth/authenticator.rb +53 -16
  13. data/lib/webrick/httpauth/basicauth.rb +45 -2
  14. data/lib/webrick/httpauth/digestauth.rb +82 -17
  15. data/lib/webrick/httpauth/htdigest.rb +38 -1
  16. data/lib/webrick/httpauth/htgroup.rb +32 -0
  17. data/lib/webrick/httpauth/htpasswd.rb +40 -2
  18. data/lib/webrick/httpauth/userdb.rb +27 -4
  19. data/lib/webrick/httpproxy.rb +197 -112
  20. data/lib/webrick/httprequest.rb +268 -50
  21. data/lib/webrick/httpresponse.rb +170 -33
  22. data/lib/webrick/https.rb +26 -3
  23. data/lib/webrick/httpserver.rb +75 -7
  24. data/lib/webrick/httpservlet/abstract.rb +88 -6
  25. data/lib/webrick/httpservlet/cgi_runner.rb +5 -4
  26. data/lib/webrick/httpservlet/cgihandler.rb +37 -18
  27. data/lib/webrick/httpservlet/erbhandler.rb +40 -7
  28. data/lib/webrick/httpservlet/filehandler.rb +116 -28
  29. data/lib/webrick/httpservlet/prochandler.rb +17 -4
  30. data/lib/webrick/httpstatus.rb +86 -18
  31. data/lib/webrick/httputils.rb +131 -23
  32. data/lib/webrick/httpversion.rb +28 -2
  33. data/lib/webrick/log.rb +72 -5
  34. data/lib/webrick/server.rb +158 -33
  35. data/lib/webrick/ssl.rb +78 -9
  36. data/lib/webrick/utils.rb +151 -5
  37. data/lib/webrick/version.rb +5 -1
  38. data/rubysl-webrick.gemspec +0 -1
  39. metadata +12 -24
@@ -1,11 +1,11 @@
1
- #
1
+ #
2
2
  # prochandler.rb -- ProcHandler Class
3
- #
3
+ #
4
4
  # Author: IPR -- Internet Programming with Ruby -- writers
5
5
  # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
6
  # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
7
  # reserved.
8
- #
8
+ #
9
9
  # $IPR: prochandler.rb,v 1.7 2002/09/21 12:23:42 gotoyuzo Exp $
10
10
 
11
11
  require 'webrick/httpservlet/abstract.rb'
@@ -13,10 +13,22 @@ require 'webrick/httpservlet/abstract.rb'
13
13
  module WEBrick
14
14
  module HTTPServlet
15
15
 
16
+ ##
17
+ # Mounts a proc at a path that accepts a request and response.
18
+ #
19
+ # Instead of mounting this servlet with WEBrick::HTTPServer#mount use
20
+ # WEBrick::HTTPServer#mount_proc:
21
+ #
22
+ # server.mount_proc '/' do |req, res|
23
+ # res.body = 'it worked!'
24
+ # res.status = 200
25
+ # end
26
+
16
27
  class ProcHandler < AbstractServlet
28
+ # :stopdoc:
17
29
  def get_instance(server, *options)
18
30
  self
19
- end
31
+ end
20
32
 
21
33
  def initialize(proc)
22
34
  @proc = proc
@@ -27,6 +39,7 @@ module WEBrick
27
39
  end
28
40
 
29
41
  alias do_POST do_GET
42
+ # :startdoc:
30
43
  end
31
44
 
32
45
  end
@@ -1,4 +1,4 @@
1
- #
1
+ #--
2
2
  # httpstatus.rb -- HTTPStatus Class
3
3
  #
4
4
  # Author: IPR -- Internet Programming with Ruby -- writers
@@ -10,19 +10,50 @@
10
10
 
11
11
  module WEBrick
12
12
 
13
+ ##
14
+ # This module is used to manager HTTP status codes.
15
+ #
16
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for more
17
+ # information.
13
18
  module HTTPStatus
14
19
 
15
- class Status < StandardError; end
20
+ ##
21
+ # Root of the HTTP status class hierarchy
22
+ class Status < StandardError
23
+ def initialize(*args) # :nodoc:
24
+ args[0] = AccessLog.escape(args[0]) unless args.empty?
25
+ super(*args)
26
+ end
27
+ class << self
28
+ attr_reader :code, :reason_phrase # :nodoc:
29
+ end
30
+
31
+ # Returns the HTTP status code
32
+ def code() self::class::code end
33
+
34
+ # Returns the HTTP status description
35
+ def reason_phrase() self::class::reason_phrase end
36
+
37
+ alias to_i code # :nodoc:
38
+ end
39
+
40
+ # Root of the HTTP info statuses
16
41
  class Info < Status; end
42
+ # Root of the HTTP sucess statuses
17
43
  class Success < Status; end
44
+ # Root of the HTTP redirect statuses
18
45
  class Redirect < Status; end
46
+ # Root of the HTTP error statuses
19
47
  class Error < Status; end
48
+ # Root of the HTTP client error statuses
20
49
  class ClientError < Error; end
50
+ # Root of the HTTP server error statuses
21
51
  class ServerError < Error; end
22
-
52
+
23
53
  class EOFError < StandardError; end
24
54
 
25
- StatusMessage = {
55
+ # HTTP status codes and descriptions
56
+ StatusMessage = { # :nodoc:
26
57
  100 => 'Continue',
27
58
  101 => 'Switching Protocols',
28
59
  200 => 'OK',
@@ -32,6 +63,7 @@ module WEBrick
32
63
  204 => 'No Content',
33
64
  205 => 'Reset Content',
34
65
  206 => 'Partial Content',
66
+ 207 => 'Multi-Status',
35
67
  300 => 'Multiple Choices',
36
68
  301 => 'Moved Permanently',
37
69
  302 => 'Found',
@@ -57,17 +89,30 @@ module WEBrick
57
89
  415 => 'Unsupported Media Type',
58
90
  416 => 'Request Range Not Satisfiable',
59
91
  417 => 'Expectation Failed',
92
+ 422 => 'Unprocessable Entity',
93
+ 423 => 'Locked',
94
+ 424 => 'Failed Dependency',
95
+ 426 => 'Upgrade Required',
96
+ 428 => 'Precondition Required',
97
+ 429 => 'Too Many Requests',
98
+ 431 => 'Request Header Fields Too Large',
60
99
  500 => 'Internal Server Error',
61
100
  501 => 'Not Implemented',
62
101
  502 => 'Bad Gateway',
63
102
  503 => 'Service Unavailable',
64
103
  504 => 'Gateway Timeout',
65
- 505 => 'HTTP Version Not Supported'
104
+ 505 => 'HTTP Version Not Supported',
105
+ 507 => 'Insufficient Storage',
106
+ 511 => 'Network Authentication Required',
66
107
  }
67
108
 
68
- CodeToError = {}
109
+ # Maps a status code to the corresponding Status class
110
+ CodeToError = {} # :nodoc:
69
111
 
112
+ # Creates a status or error class for each status code and
113
+ # populates the CodeToError map.
70
114
  StatusMessage.each{|code, message|
115
+ message.freeze
71
116
  var_name = message.gsub(/[ \-]/,'_').upcase
72
117
  err_name = message.gsub(/[ \-]/,'')
73
118
 
@@ -79,42 +124,65 @@ module WEBrick
79
124
  when 500...600; parent = ServerError
80
125
  end
81
126
 
82
- eval %-
83
- RC_#{var_name} = #{code}
84
- class #{err_name} < #{parent}
85
- def self.code() RC_#{var_name} end
86
- def self.reason_phrase() StatusMessage[code] end
87
- def code() self::class::code end
88
- def reason_phrase() self::class::reason_phrase end
89
- alias to_i code
90
- end
91
- -
92
-
93
- CodeToError[code] = const_get(err_name)
127
+ const_set("RC_#{var_name}", code)
128
+ err_class = Class.new(parent)
129
+ err_class.instance_variable_set(:@code, code)
130
+ err_class.instance_variable_set(:@reason_phrase, message)
131
+ const_set(err_name, err_class)
132
+ CodeToError[code] = err_class
94
133
  }
95
134
 
135
+ ##
136
+ # Returns the description corresponding to the HTTP status +code+
137
+ #
138
+ # WEBrick::HTTPStatus.reason_phrase 404
139
+ # => "Not Found"
96
140
  def reason_phrase(code)
97
141
  StatusMessage[code.to_i]
98
142
  end
143
+
144
+ ##
145
+ # Is +code+ an informational status?
99
146
  def info?(code)
100
147
  code.to_i >= 100 and code.to_i < 200
101
148
  end
149
+
150
+ ##
151
+ # Is +code+ a successful status?
102
152
  def success?(code)
103
153
  code.to_i >= 200 and code.to_i < 300
104
154
  end
155
+
156
+ ##
157
+ # Is +code+ a redirection status?
105
158
  def redirect?(code)
106
159
  code.to_i >= 300 and code.to_i < 400
107
160
  end
161
+
162
+ ##
163
+ # Is +code+ an error status?
108
164
  def error?(code)
109
165
  code.to_i >= 400 and code.to_i < 600
110
166
  end
167
+
168
+ ##
169
+ # Is +code+ a client error status?
111
170
  def client_error?(code)
112
171
  code.to_i >= 400 and code.to_i < 500
113
172
  end
173
+
174
+ ##
175
+ # Is +code+ a server error status?
114
176
  def server_error?(code)
115
177
  code.to_i >= 500 and code.to_i < 600
116
178
  end
117
179
 
180
+ ##
181
+ # Returns the status class corresponding to +code+
182
+ #
183
+ # WEBrick::HTTPStatus[302]
184
+ # => WEBrick::HTTPStatus::NotFound
185
+ #
118
186
  def self.[](code)
119
187
  CodeToError[code]
120
188
  end
@@ -12,12 +12,21 @@ require 'socket'
12
12
  require 'tempfile'
13
13
 
14
14
  module WEBrick
15
- CR = "\x0d"
16
- LF = "\x0a"
17
- CRLF = "\x0d\x0a"
15
+ CR = "\x0d" # :nodoc:
16
+ LF = "\x0a" # :nodoc:
17
+ CRLF = "\x0d\x0a" # :nodoc:
18
+
19
+ ##
20
+ # HTTPUtils provides utility methods for working with the HTTP protocol.
21
+ #
22
+ # This module is generally used internally by WEBrick
18
23
 
19
24
  module HTTPUtils
20
25
 
26
+ ##
27
+ # Normalizes a request path. Raises an exception if the path cannot be
28
+ # normalized.
29
+
21
30
  def normalize_path(path)
22
31
  raise "abnormal path `#{path}'" if path[0] != ?/
23
32
  ret = path.dup
@@ -31,7 +40,8 @@ module WEBrick
31
40
  end
32
41
  module_function :normalize_path
33
42
 
34
- #####
43
+ ##
44
+ # Default mime types
35
45
 
36
46
  DefaultMimeTypes = {
37
47
  "ai" => "application/postscript",
@@ -57,6 +67,7 @@ module WEBrick
57
67
  "jpe" => "image/jpeg",
58
68
  "jpeg" => "image/jpeg",
59
69
  "jpg" => "image/jpeg",
70
+ "js" => "application/javascript",
60
71
  "lha" => "application/octet-stream",
61
72
  "lzh" => "application/octet-stream",
62
73
  "mov" => "video/quicktime",
@@ -78,10 +89,12 @@ module WEBrick
78
89
  "rtf" => "application/rtf",
79
90
  "sgm" => "text/sgml",
80
91
  "sgml" => "text/sgml",
92
+ "svg" => "image/svg+xml",
81
93
  "tif" => "image/tiff",
82
94
  "tiff" => "image/tiff",
83
95
  "txt" => "text/plain",
84
96
  "xbm" => "image/x-xbitmap",
97
+ "xhtml" => "text/html",
85
98
  "xls" => "application/vnd.ms-excel",
86
99
  "xml" => "text/xml",
87
100
  "xpm" => "image/x-xpixmap",
@@ -89,7 +102,9 @@ module WEBrick
89
102
  "zip" => "application/zip",
90
103
  }
91
104
 
92
- # Load Apache compatible mime.types file.
105
+ ##
106
+ # Loads Apache-compatible mime.types in +file+.
107
+
93
108
  def load_mime_types(file)
94
109
  open(file){ |io|
95
110
  hash = Hash.new
@@ -97,7 +112,7 @@ module WEBrick
97
112
  next if /^#/ =~ line
98
113
  line.chomp!
99
114
  mimetype, ext0 = line.split(/\s+/, 2)
100
- next unless ext0
115
+ next unless ext0
101
116
  next if ext0.empty?
102
117
  ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
103
118
  }
@@ -106,6 +121,10 @@ module WEBrick
106
121
  end
107
122
  module_function :load_mime_types
108
123
 
124
+ ##
125
+ # Returns the mime type of +filename+ from the list in +mime_tab+. If no
126
+ # mime type was found application/octet-stream is returned.
127
+
109
128
  def mime_type(filename, mime_tab)
110
129
  suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
111
130
  suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
@@ -113,12 +132,14 @@ module WEBrick
113
132
  end
114
133
  module_function :mime_type
115
134
 
116
- #####
135
+ ##
136
+ # Parses an HTTP header +raw+ into a hash of header fields with an Array
137
+ # of values.
117
138
 
118
139
  def parse_header(raw)
119
140
  header = Hash.new([].freeze)
120
141
  field = nil
121
- raw.each{|line|
142
+ raw.each_line{|line|
122
143
  case line
123
144
  when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
124
145
  field, value = $1, $2
@@ -128,11 +149,11 @@ module WEBrick
128
149
  when /^\s+(.*?)\s*\z/om
129
150
  value = $1
130
151
  unless field
131
- raise "bad header '#{line.inspect}'."
152
+ raise HTTPStatus::BadRequest, "bad header '#{line}'."
132
153
  end
133
154
  header[field][-1] << " " << value
134
155
  else
135
- raise "bad header '#{line.inspect}'."
156
+ raise HTTPStatus::BadRequest, "bad header '#{line}'."
136
157
  end
137
158
  }
138
159
  header.each{|key, values|
@@ -145,12 +166,18 @@ module WEBrick
145
166
  end
146
167
  module_function :parse_header
147
168
 
169
+ ##
170
+ # Splits a header value +str+ according to HTTP specification.
171
+
148
172
  def split_header_value(str)
149
173
  str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
150
174
  (?:,\s*|\Z)'xn).flatten
151
175
  end
152
176
  module_function :split_header_value
153
177
 
178
+ ##
179
+ # Parses a Range header value +ranges_specifier+
180
+
154
181
  def parse_range_header(ranges_specifier)
155
182
  if /^bytes=(.*)/ =~ ranges_specifier
156
183
  byte_range_set = split_header_value($1)
@@ -166,6 +193,9 @@ module WEBrick
166
193
  end
167
194
  module_function :parse_range_header
168
195
 
196
+ ##
197
+ # Parses q values in +value+ as used in Accept headers.
198
+
169
199
  def parse_qvalues(value)
170
200
  tmp = []
171
201
  if value
@@ -184,7 +214,8 @@ module WEBrick
184
214
  end
185
215
  module_function :parse_qvalues
186
216
 
187
- #####
217
+ ##
218
+ # Removes quotes and escapes from +str+
188
219
 
189
220
  def dequote(str)
190
221
  ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
@@ -193,20 +224,43 @@ module WEBrick
193
224
  end
194
225
  module_function :dequote
195
226
 
227
+ ##
228
+ # Quotes and escapes quotes in +str+
229
+
196
230
  def quote(str)
197
231
  '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
198
232
  end
199
233
  module_function :quote
200
234
 
201
- #####
235
+ ##
236
+ # Stores multipart form data. FormData objects are created when
237
+ # WEBrick::HTTPUtils.parse_form_data is called.
202
238
 
203
239
  class FormData < String
204
- EmptyRawHeader = [].freeze
205
- EmptyHeader = {}.freeze
240
+ EmptyRawHeader = [].freeze # :nodoc:
241
+ EmptyHeader = {}.freeze # :nodoc:
242
+
243
+ ##
244
+ # The name of the form data part
206
245
 
207
- attr_accessor :name, :filename, :next_data
246
+ attr_accessor :name
247
+
248
+ ##
249
+ # The filename of the form data part
250
+
251
+ attr_accessor :filename
252
+
253
+ attr_accessor :next_data # :nodoc:
208
254
  protected :next_data
209
255
 
256
+ ##
257
+ # Creates a new FormData object.
258
+ #
259
+ # +args+ is an Array of form data entries. One FormData will be created
260
+ # for each entry.
261
+ #
262
+ # This is called by WEBrick::HTTPUtils.parse_form_data for you
263
+
210
264
  def initialize(*args)
211
265
  @name = @filename = @next_data = nil
212
266
  if args.empty?
@@ -215,7 +269,7 @@ module WEBrick
215
269
  super("")
216
270
  else
217
271
  @raw_header = EmptyRawHeader
218
- @header = EmptyHeader
272
+ @header = EmptyHeader
219
273
  super(args.shift)
220
274
  unless args.empty?
221
275
  @next_data = self.class.new(*args)
@@ -223,6 +277,9 @@ module WEBrick
223
277
  end
224
278
  end
225
279
 
280
+ ##
281
+ # Retrieves the header at the first entry in +key+
282
+
226
283
  def [](*key)
227
284
  begin
228
285
  @header[key[0].downcase].join(", ")
@@ -231,11 +288,17 @@ module WEBrick
231
288
  end
232
289
  end
233
290
 
291
+ ##
292
+ # Adds +str+ to this FormData which may be the body, a header or a
293
+ # header entry.
294
+ #
295
+ # This is called by WEBrick::HTTPUtils.parse_form_data for you
296
+
234
297
  def <<(str)
235
298
  if @header
236
299
  super
237
300
  elsif str == CRLF
238
- @header = HTTPUtils::parse_header(@raw_header)
301
+ @header = HTTPUtils::parse_header(@raw_header.join)
239
302
  if cd = self['content-disposition']
240
303
  if /\s+name="(.*?)"/ =~ cd then @name = $1 end
241
304
  if /\s+filename="(.*?)"/ =~ cd then @filename = $1 end
@@ -246,10 +309,15 @@ module WEBrick
246
309
  self
247
310
  end
248
311
 
312
+ ##
313
+ # Adds +data+ at the end of the chain of entries
314
+ #
315
+ # This is called by WEBrick::HTTPUtils.parse_form_data for you.
316
+
249
317
  def append_data(data)
250
318
  tmp = self
251
319
  while tmp
252
- unless tmp.next_data
320
+ unless tmp.next_data
253
321
  tmp.next_data = data
254
322
  break
255
323
  end
@@ -258,6 +326,9 @@ module WEBrick
258
326
  self
259
327
  end
260
328
 
329
+ ##
330
+ # Yields each entry in this FormData
331
+
261
332
  def each_data
262
333
  tmp = self
263
334
  while tmp
@@ -267,6 +338,9 @@ module WEBrick
267
338
  end
268
339
  end
269
340
 
341
+ ##
342
+ # Returns all the FormData as an Array
343
+
270
344
  def list
271
345
  ret = []
272
346
  each_data{|data|
@@ -275,18 +349,27 @@ module WEBrick
275
349
  ret
276
350
  end
277
351
 
352
+ ##
353
+ # A FormData will behave like an Array
354
+
278
355
  alias :to_ary :list
279
356
 
357
+ ##
358
+ # This FormData's body
359
+
280
360
  def to_s
281
361
  String.new(self)
282
362
  end
283
363
  end
284
364
 
365
+ ##
366
+ # Parses the query component of a URI in +str+
367
+
285
368
  def parse_query(str)
286
369
  query = Hash.new
287
370
  if str
288
371
  str.split(/[&;]/).each{|x|
289
- next if x.empty?
372
+ next if x.empty?
290
373
  key, val = x.split(/=/,2)
291
374
  key = unescape_form(key)
292
375
  val = unescape_form(val.to_s)
@@ -303,12 +386,15 @@ module WEBrick
303
386
  end
304
387
  module_function :parse_query
305
388
 
389
+ ##
390
+ # Parses form data in +io+ with the given +boundary+
391
+
306
392
  def parse_form_data(io, boundary)
307
- boundary_regexp = /\A--#{boundary}(--)?#{CRLF}\z/
393
+ boundary_regexp = /\A--#{Regexp.quote(boundary)}(--)?#{CRLF}\z/
308
394
  form_data = Hash.new
309
395
  return form_data unless io
310
396
  data = nil
311
- io.each{|line|
397
+ io.each_line{|line|
312
398
  if boundary_regexp =~ line
313
399
  if data
314
400
  data.chop!
@@ -316,7 +402,7 @@ module WEBrick
316
402
  if form_data.has_key?(key)
317
403
  form_data[key].append_data(data)
318
404
  else
319
- form_data[key] = data
405
+ form_data[key] = data
320
406
  end
321
407
  end
322
408
  data = FormData.new
@@ -347,9 +433,11 @@ module WEBrick
347
433
 
348
434
  module_function
349
435
 
436
+ # :stopdoc:
437
+
350
438
  def _make_regex(str) /([#{Regexp.escape(str)}])/n end
351
439
  def _make_regex!(str) /([^#{Regexp.escape(str)}])/n end
352
- def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1[0] } end
440
+ def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1.ord } end
353
441
  def _unescape(str, regex) str.gsub(regex){ $1.hex.chr } end
354
442
 
355
443
  UNESCAPED = _make_regex(control+space+delims+unwise+nonascii)
@@ -358,24 +446,41 @@ module WEBrick
358
446
  ESCAPED = /%([0-9a-fA-F]{2})/
359
447
  UNESCAPED_PCHAR = _make_regex!(unreserved+":@&=+$,")
360
448
 
449
+ # :startdoc:
450
+
451
+ ##
452
+ # Escapes HTTP reserved and unwise characters in +str+
453
+
361
454
  def escape(str)
362
455
  _escape(str, UNESCAPED)
363
456
  end
364
457
 
458
+ ##
459
+ # Unescapes HTTP reserved and unwise characters in +str+
460
+
365
461
  def unescape(str)
366
462
  _unescape(str, ESCAPED)
367
463
  end
368
464
 
465
+ ##
466
+ # Escapes form reserved characters in +str+
467
+
369
468
  def escape_form(str)
370
469
  ret = _escape(str, UNESCAPED_FORM)
371
470
  ret.gsub!(/ /, "+")
372
471
  ret
373
472
  end
374
473
 
474
+ ##
475
+ # Unescapes form reserved characters in +str+
476
+
375
477
  def unescape_form(str)
376
478
  _unescape(str.gsub(/\+/, " "), ESCAPED)
377
479
  end
378
480
 
481
+ ##
482
+ # Escapes path +str+
483
+
379
484
  def escape_path(str)
380
485
  result = ""
381
486
  str.scan(%r{/([^/]*)}).each{|i|
@@ -384,6 +489,9 @@ module WEBrick
384
489
  return result
385
490
  end
386
491
 
492
+ ##
493
+ # Escapes 8 bit characters in +str+
494
+
387
495
  def escape8bit(str)
388
496
  _escape(str, NONASCII)
389
497
  end