webrick 1.3.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (63) hide show
  1. data/README.txt +21 -0
  2. data/lib/webrick.rb +227 -0
  3. data/lib/webrick/accesslog.rb +151 -0
  4. data/lib/webrick/cgi.rb +260 -0
  5. data/lib/webrick/compat.rb +35 -0
  6. data/lib/webrick/config.rb +121 -0
  7. data/lib/webrick/cookie.rb +110 -0
  8. data/lib/webrick/htmlutils.rb +28 -0
  9. data/lib/webrick/httpauth.rb +95 -0
  10. data/lib/webrick/httpauth/authenticator.rb +112 -0
  11. data/lib/webrick/httpauth/basicauth.rb +108 -0
  12. data/lib/webrick/httpauth/digestauth.rb +392 -0
  13. data/lib/webrick/httpauth/htdigest.rb +128 -0
  14. data/lib/webrick/httpauth/htgroup.rb +93 -0
  15. data/lib/webrick/httpauth/htpasswd.rb +121 -0
  16. data/lib/webrick/httpauth/userdb.rb +52 -0
  17. data/lib/webrick/httpproxy.rb +305 -0
  18. data/lib/webrick/httprequest.rb +461 -0
  19. data/lib/webrick/httpresponse.rb +399 -0
  20. data/lib/webrick/https.rb +64 -0
  21. data/lib/webrick/httpserver.rb +264 -0
  22. data/lib/webrick/httpservlet.rb +22 -0
  23. data/lib/webrick/httpservlet/abstract.rb +153 -0
  24. data/lib/webrick/httpservlet/cgi_runner.rb +46 -0
  25. data/lib/webrick/httpservlet/cgihandler.rb +108 -0
  26. data/lib/webrick/httpservlet/erbhandler.rb +87 -0
  27. data/lib/webrick/httpservlet/filehandler.rb +470 -0
  28. data/lib/webrick/httpservlet/prochandler.rb +33 -0
  29. data/lib/webrick/httpstatus.rb +184 -0
  30. data/lib/webrick/httputils.rb +394 -0
  31. data/lib/webrick/httpversion.rb +49 -0
  32. data/lib/webrick/log.rb +136 -0
  33. data/lib/webrick/server.rb +218 -0
  34. data/lib/webrick/ssl.rb +127 -0
  35. data/lib/webrick/utils.rb +241 -0
  36. data/lib/webrick/version.rb +13 -0
  37. data/sample/webrick/demo-app.rb +66 -0
  38. data/sample/webrick/demo-multipart.cgi +12 -0
  39. data/sample/webrick/demo-servlet.rb +6 -0
  40. data/sample/webrick/demo-urlencoded.cgi +12 -0
  41. data/sample/webrick/hello.cgi +11 -0
  42. data/sample/webrick/hello.rb +8 -0
  43. data/sample/webrick/httpd.rb +23 -0
  44. data/sample/webrick/httpproxy.rb +25 -0
  45. data/sample/webrick/httpsd.rb +33 -0
  46. data/test/openssl/utils.rb +313 -0
  47. data/test/ruby/envutil.rb +208 -0
  48. data/test/webrick/test_cgi.rb +134 -0
  49. data/test/webrick/test_cookie.rb +131 -0
  50. data/test/webrick/test_filehandler.rb +285 -0
  51. data/test/webrick/test_httpauth.rb +167 -0
  52. data/test/webrick/test_httpproxy.rb +282 -0
  53. data/test/webrick/test_httprequest.rb +411 -0
  54. data/test/webrick/test_httpresponse.rb +49 -0
  55. data/test/webrick/test_httpserver.rb +305 -0
  56. data/test/webrick/test_httputils.rb +96 -0
  57. data/test/webrick/test_httpversion.rb +40 -0
  58. data/test/webrick/test_server.rb +67 -0
  59. data/test/webrick/test_utils.rb +64 -0
  60. data/test/webrick/utils.rb +58 -0
  61. data/test/webrick/webrick.cgi +36 -0
  62. data/test/webrick/webrick_long_filename.cgi +36 -0
  63. metadata +106 -0
@@ -0,0 +1,461 @@
1
+ #
2
+ # httprequest.rb -- HTTPRequest Class
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
10
+
11
+ require 'uri'
12
+ require 'webrick/httpversion'
13
+ require 'webrick/httpstatus'
14
+ require 'webrick/httputils'
15
+ require 'webrick/cookie'
16
+
17
+ module WEBrick
18
+
19
+ ##
20
+ # An HTTP request.
21
+ class HTTPRequest
22
+
23
+ BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
24
+
25
+ # :section: Request line
26
+ attr_reader :request_line
27
+ attr_reader :request_method, :unparsed_uri, :http_version
28
+
29
+ # :section: Request-URI
30
+ attr_reader :request_uri, :path
31
+ attr_accessor :script_name, :path_info, :query_string
32
+
33
+ # :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
37
+
38
+ # :section:
39
+ attr_accessor :user
40
+ attr_reader :addr, :peeraddr
41
+ attr_reader :attributes
42
+ attr_reader :keep_alive
43
+ attr_reader :request_time
44
+
45
+ def initialize(config)
46
+ @config = config
47
+ @buffer_size = @config[:InputBufferSize]
48
+ @logger = config[:Logger]
49
+
50
+ @request_line = @request_method =
51
+ @unparsed_uri = @http_version = nil
52
+
53
+ @request_uri = @host = @port = @path = nil
54
+ @script_name = @path_info = nil
55
+ @query_string = nil
56
+ @query = nil
57
+ @form_data = nil
58
+
59
+ @raw_header = Array.new
60
+ @header = nil
61
+ @cookies = []
62
+ @accept = []
63
+ @accept_charset = []
64
+ @accept_encoding = []
65
+ @accept_language = []
66
+ @body = ""
67
+
68
+ @addr = @peeraddr = nil
69
+ @attributes = {}
70
+ @user = nil
71
+ @keep_alive = false
72
+ @request_time = nil
73
+
74
+ @remaining_size = nil
75
+ @socket = nil
76
+
77
+ @forwarded_proto = @forwarded_host = @forwarded_port =
78
+ @forwarded_server = @forwarded_for = nil
79
+ end
80
+
81
+ def parse(socket=nil)
82
+ @socket = socket
83
+ begin
84
+ @peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
85
+ @addr = socket.respond_to?(:addr) ? socket.addr : []
86
+ rescue Errno::ENOTCONN
87
+ raise HTTPStatus::EOFError
88
+ end
89
+
90
+ read_request_line(socket)
91
+ if @http_version.major > 0
92
+ read_header(socket)
93
+ @header['cookie'].each{|cookie|
94
+ @cookies += Cookie::parse(cookie)
95
+ }
96
+ @accept = HTTPUtils.parse_qvalues(self['accept'])
97
+ @accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
98
+ @accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
99
+ @accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
100
+ end
101
+ return if @request_method == "CONNECT"
102
+ return if @unparsed_uri == "*"
103
+
104
+ begin
105
+ setup_forwarded_info
106
+ @request_uri = parse_uri(@unparsed_uri)
107
+ @path = HTTPUtils::unescape(@request_uri.path)
108
+ @path = HTTPUtils::normalize_path(@path)
109
+ @host = @request_uri.host
110
+ @port = @request_uri.port
111
+ @query_string = @request_uri.query
112
+ @script_name = ""
113
+ @path_info = @path.dup
114
+ rescue
115
+ raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
116
+ end
117
+
118
+ if /close/io =~ self["connection"]
119
+ @keep_alive = false
120
+ elsif /keep-alive/io =~ self["connection"]
121
+ @keep_alive = true
122
+ elsif @http_version < "1.1"
123
+ @keep_alive = false
124
+ else
125
+ @keep_alive = true
126
+ end
127
+ end
128
+
129
+ # Generate HTTP/1.1 100 continue response if the client expects it,
130
+ # otherwise does nothing.
131
+ def continue
132
+ if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
133
+ @socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
134
+ @header.delete('expect')
135
+ end
136
+ end
137
+
138
+ def body(&block)
139
+ block ||= Proc.new{|chunk| @body << chunk }
140
+ read_body(@socket, block)
141
+ @body.empty? ? nil : @body
142
+ end
143
+
144
+ ##
145
+ # Request query as a Hash
146
+
147
+ def query
148
+ unless @query
149
+ parse_query()
150
+ end
151
+ @query
152
+ end
153
+
154
+ ##
155
+ # The content-length header
156
+
157
+ def content_length
158
+ return Integer(self['content-length'])
159
+ end
160
+
161
+ ##
162
+ # The content-type header
163
+
164
+ def content_type
165
+ return self['content-type']
166
+ end
167
+
168
+ ##
169
+ # Retrieves +header_name+
170
+
171
+ def [](header_name)
172
+ if @header
173
+ value = @header[header_name.downcase]
174
+ value.empty? ? nil : value.join(", ")
175
+ end
176
+ end
177
+
178
+ ##
179
+ # Iterates over the request headers
180
+
181
+ def each
182
+ if @header
183
+ @header.each{|k, v|
184
+ value = @header[k]
185
+ yield(k, value.empty? ? nil : value.join(", "))
186
+ }
187
+ end
188
+ end
189
+
190
+ ##
191
+ # The host this request is for
192
+
193
+ def host
194
+ return @forwarded_host || @host
195
+ end
196
+
197
+ ##
198
+ # The port this request is for
199
+
200
+ def port
201
+ return @forwarded_port || @port
202
+ end
203
+
204
+ ##
205
+ # The server name this request is for
206
+
207
+ def server_name
208
+ return @forwarded_server || @config[:ServerName]
209
+ end
210
+
211
+ ##
212
+ # The client's IP address
213
+
214
+ def remote_ip
215
+ return self["client-ip"] || @forwarded_for || @peeraddr[3]
216
+ end
217
+
218
+ ##
219
+ # Is this an SSL request?
220
+
221
+ def ssl?
222
+ return @request_uri.scheme == "https"
223
+ end
224
+
225
+ ##
226
+ # Should the connection this request was made on be kept alive?
227
+
228
+ def keep_alive?
229
+ @keep_alive
230
+ end
231
+
232
+ def to_s # :nodoc:
233
+ ret = @request_line.dup
234
+ @raw_header.each{|line| ret << line }
235
+ ret << CRLF
236
+ ret << body if body
237
+ ret
238
+ end
239
+
240
+ def fixup()
241
+ begin
242
+ body{|chunk| } # read remaining body
243
+ rescue HTTPStatus::Error => ex
244
+ @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
245
+ @keep_alive = false
246
+ rescue => ex
247
+ @logger.error(ex)
248
+ @keep_alive = false
249
+ end
250
+ end
251
+
252
+ # This method provides the metavariables defined by the revision 3
253
+ # of "The WWW Common Gateway Interface Version 1.1"
254
+ # http://Web.Golux.Com/coar/cgi/
255
+
256
+ def meta_vars
257
+ meta = Hash.new
258
+
259
+ cl = self["Content-Length"]
260
+ ct = self["Content-Type"]
261
+ meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
262
+ meta["CONTENT_TYPE"] = ct.dup if ct
263
+ meta["GATEWAY_INTERFACE"] = "CGI/1.1"
264
+ meta["PATH_INFO"] = @path_info ? @path_info.dup : ""
265
+ #meta["PATH_TRANSLATED"] = nil # no plan to be provided
266
+ meta["QUERY_STRING"] = @query_string ? @query_string.dup : ""
267
+ meta["REMOTE_ADDR"] = @peeraddr[3]
268
+ meta["REMOTE_HOST"] = @peeraddr[2]
269
+ #meta["REMOTE_IDENT"] = nil # no plan to be provided
270
+ meta["REMOTE_USER"] = @user
271
+ meta["REQUEST_METHOD"] = @request_method.dup
272
+ meta["REQUEST_URI"] = @request_uri.to_s
273
+ meta["SCRIPT_NAME"] = @script_name.dup
274
+ meta["SERVER_NAME"] = @host
275
+ meta["SERVER_PORT"] = @port.to_s
276
+ meta["SERVER_PROTOCOL"] = "HTTP/" + @config[:HTTPVersion].to_s
277
+ meta["SERVER_SOFTWARE"] = @config[:ServerSoftware].dup
278
+
279
+ self.each{|key, val|
280
+ next if /^content-type$/i =~ key
281
+ next if /^content-length$/i =~ key
282
+ name = "HTTP_" + key
283
+ name.gsub!(/-/o, "_")
284
+ name.upcase!
285
+ meta[name] = val
286
+ }
287
+
288
+ meta
289
+ end
290
+
291
+ private
292
+
293
+ MAX_URI_LENGTH = 2083 # :nodoc:
294
+
295
+ def read_request_line(socket)
296
+ @request_line = read_line(socket, MAX_URI_LENGTH) if socket
297
+ if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
298
+ raise HTTPStatus::RequestURITooLarge
299
+ end
300
+ @request_time = Time.now
301
+ raise HTTPStatus::EOFError unless @request_line
302
+ if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
303
+ @request_method = $1
304
+ @unparsed_uri = $2
305
+ @http_version = HTTPVersion.new($3 ? $3 : "0.9")
306
+ else
307
+ rl = @request_line.sub(/\x0d?\x0a\z/o, '')
308
+ raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
309
+ end
310
+ end
311
+
312
+ def read_header(socket)
313
+ if socket
314
+ while line = read_line(socket)
315
+ break if /\A(#{CRLF}|#{LF})\z/om =~ line
316
+ @raw_header << line
317
+ end
318
+ end
319
+ @header = HTTPUtils::parse_header(@raw_header.join)
320
+ end
321
+
322
+ def parse_uri(str, scheme="http")
323
+ if @config[:Escape8bitURI]
324
+ str = HTTPUtils::escape8bit(str)
325
+ end
326
+ str.sub!(%r{\A/+}o, '/')
327
+ uri = URI::parse(str)
328
+ return uri if uri.absolute?
329
+ if @forwarded_host
330
+ host, port = @forwarded_host, @forwarded_port
331
+ elsif self["host"]
332
+ pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
333
+ host, port = *self['host'].scan(pattern)[0]
334
+ elsif @addr.size > 0
335
+ host, port = @addr[2], @addr[1]
336
+ else
337
+ host, port = @config[:ServerName], @config[:Port]
338
+ end
339
+ uri.scheme = @forwarded_proto || scheme
340
+ uri.host = host
341
+ uri.port = port ? port.to_i : nil
342
+ return URI::parse(uri.to_s)
343
+ end
344
+
345
+ def read_body(socket, block)
346
+ return unless socket
347
+ if tc = self['transfer-encoding']
348
+ case tc
349
+ when /chunked/io then read_chunked(socket, block)
350
+ else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
351
+ end
352
+ elsif self['content-length'] || @remaining_size
353
+ @remaining_size ||= self['content-length'].to_i
354
+ while @remaining_size > 0
355
+ sz = [@buffer_size, @remaining_size].min
356
+ break unless buf = read_data(socket, sz)
357
+ @remaining_size -= buf.bytesize
358
+ block.call(buf)
359
+ end
360
+ if @remaining_size > 0 && @socket.eof?
361
+ raise HTTPStatus::BadRequest, "invalid body size."
362
+ end
363
+ elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
364
+ raise HTTPStatus::LengthRequired
365
+ end
366
+ return @body
367
+ end
368
+
369
+ def read_chunk_size(socket)
370
+ line = read_line(socket)
371
+ if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
372
+ chunk_size = $1.hex
373
+ chunk_ext = $2
374
+ [ chunk_size, chunk_ext ]
375
+ else
376
+ raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
377
+ end
378
+ end
379
+
380
+ def read_chunked(socket, block)
381
+ chunk_size, = read_chunk_size(socket)
382
+ while chunk_size > 0
383
+ data = read_data(socket, chunk_size) # read chunk-data
384
+ if data.nil? || data.bytesize != chunk_size
385
+ raise BadRequest, "bad chunk data size."
386
+ end
387
+ read_line(socket) # skip CRLF
388
+ block.call(data)
389
+ chunk_size, = read_chunk_size(socket)
390
+ end
391
+ read_header(socket) # trailer + CRLF
392
+ @header.delete("transfer-encoding")
393
+ @remaining_size = 0
394
+ end
395
+
396
+ def _read_data(io, method, *arg)
397
+ begin
398
+ WEBrick::Utils.timeout(@config[:RequestTimeout]){
399
+ return io.__send__(method, *arg)
400
+ }
401
+ rescue Errno::ECONNRESET
402
+ return nil
403
+ rescue TimeoutError
404
+ raise HTTPStatus::RequestTimeout
405
+ end
406
+ end
407
+
408
+ def read_line(io, size=4096)
409
+ _read_data(io, :gets, LF, size)
410
+ end
411
+
412
+ def read_data(io, size)
413
+ _read_data(io, :read, size)
414
+ end
415
+
416
+ def parse_query()
417
+ begin
418
+ if @request_method == "GET" || @request_method == "HEAD"
419
+ @query = HTTPUtils::parse_query(@query_string)
420
+ elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
421
+ @query = HTTPUtils::parse_query(body)
422
+ elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
423
+ boundary = HTTPUtils::dequote($1)
424
+ @query = HTTPUtils::parse_form_data(body, boundary)
425
+ else
426
+ @query = Hash.new
427
+ end
428
+ rescue => ex
429
+ raise HTTPStatus::BadRequest, ex.message
430
+ end
431
+ end
432
+
433
+ PrivateNetworkRegexp = /
434
+ ^unknown$|
435
+ ^((::ffff:)?127.0.0.1|::1)$|
436
+ ^(::ffff:)?(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\.
437
+ /ixo
438
+
439
+ # It's said that all X-Forwarded-* headers will contain more than one
440
+ # (comma-separated) value if the original request already contained one of
441
+ # these headers. Since we could use these values as Host header, we choose
442
+ # the initial(first) value. (apr_table_mergen() adds new value after the
443
+ # existing value with ", " prefix)
444
+ def setup_forwarded_info
445
+ if @forwarded_server = self["x-forwarded-server"]
446
+ @forwarded_server = @forwarded_server.split(",", 2).first
447
+ end
448
+ @forwarded_proto = self["x-forwarded-proto"]
449
+ if host_port = self["x-forwarded-host"]
450
+ host_port = host_port.split(",", 2).first
451
+ @forwarded_host, tmp = host_port.split(":", 2)
452
+ @forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
453
+ end
454
+ if addrs = self["x-forwarded-for"]
455
+ addrs = addrs.split(",").collect(&:strip)
456
+ addrs.reject!{|ip| PrivateNetworkRegexp =~ ip }
457
+ @forwarded_for = addrs.first
458
+ end
459
+ end
460
+ end
461
+ end