webricknio 0.6.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.
@@ -0,0 +1,559 @@
1
+ #
2
+ # httprequest.rb -- HTTPRequest Class
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers, Pradeep Singh
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers
7
+ # Copyright (c) 2012 Pradeep Singh
8
+ # All rights reserved.
9
+ #
10
+ # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
11
+
12
+ require 'uri'
13
+ require 'webrick/httpversion'
14
+ require 'webrick/httpstatus'
15
+ require 'webrick/httputils'
16
+ require 'webrick/cookie'
17
+
18
+ require 'java'
19
+
20
+ java_import 'java.nio.ByteBuffer'
21
+
22
+ module WEBrickNIO
23
+
24
+ ##
25
+ # An HTTP request.
26
+ class HTTPRequest
27
+
28
+ BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
29
+
30
+ # :section: Request line
31
+ attr_reader :request_line
32
+ attr_reader :request_method, :unparsed_uri, :http_version
33
+
34
+ # :section: Request-URI
35
+ attr_reader :request_uri, :path
36
+ attr_accessor :script_name, :path_info, :query_string
37
+
38
+ # :section: Header and entity body
39
+ attr_reader :raw_header, :header, :cookies
40
+ attr_reader :accept, :accept_charset
41
+ attr_reader :accept_encoding, :accept_language
42
+
43
+ # :section:
44
+ attr_accessor :user
45
+ attr_reader :addr, :peeraddr
46
+ attr_reader :attributes
47
+ attr_reader :keep_alive
48
+ attr_reader :request_time
49
+
50
+ def initialize(config)
51
+ @config = config
52
+ @in_progress = false
53
+ @buffer_size = @config[:InputBufferSize]
54
+ @logger = config[:Logger]
55
+
56
+ @request_line = @request_method =
57
+ @unparsed_uri = @http_version = nil
58
+
59
+ @request_uri = @host = @port = @path = nil
60
+ @script_name = @path_info = nil
61
+ @query_string = nil
62
+ @query = nil
63
+ @form_data = nil
64
+
65
+ @raw_header = Array.new
66
+ @header = nil
67
+ @cookies = []
68
+ @accept = []
69
+ @accept_charset = []
70
+ @accept_encoding = []
71
+ @accept_language = []
72
+ @body = nil
73
+
74
+ @addr = @peeraddr = nil
75
+ @attributes = {}
76
+ @user = nil
77
+ @keep_alive = false
78
+ @request_time = nil
79
+
80
+ @remaining_size = nil
81
+ @socket_channel = nil
82
+ @socket = nil
83
+
84
+ @forwarded_proto = @forwarded_host = @forwarded_port =
85
+ @forwarded_server = @forwarded_for = nil
86
+
87
+ @byte_buffer = ByteBuffer.allocate(8192)
88
+
89
+ end
90
+
91
+ def parse(socket_channel)
92
+ @socket_channel = socket_channel
93
+ @socket = @socket_channel.socket
94
+
95
+ begin
96
+ @peeraddr = @socket.respond_to?(:get_remote_socket_address) ? @socket.get_remote_socket_address : []
97
+ @addr = @socket.respond_to?(:get_local_socket_address) ? @socket.get_local_socket_address : []
98
+ rescue Errno::ENOTCONN => ex
99
+ @logger.error "socket id: #{@socket.object_id}"
100
+ @logger.error(ex.backtrace)
101
+ raise ::WEBrick::HTTPStatus::EOFError
102
+ end
103
+
104
+ begin
105
+ time = Time.now
106
+ @num_read = 0
107
+ @anything_read = false
108
+ while ((@num_read = @socket_channel.java_send :read, [Java::JavaNio::ByteBuffer], @byte_buffer) > 0) || (!@anything_read && Time.now - time < 2)
109
+ @anything_read = true if @num_read > 0
110
+ #@logger.debug "num_read = #{@num_read}, socket id: #{@socket.object_id}"
111
+ raise "socket was closed" if @num_read == -1
112
+ end
113
+ rescue Exception => ex
114
+ ex.respond_to?(:java_class) ? @logger.debug("socket was closed: #{ex.java_class.name}") : @logger.debug("socket was closed")
115
+ raise ::WEBrick::HTTPStatus::EOFError
116
+ end
117
+
118
+ raise ::WEBrick::HTTPStatus::EOFError if @byte_buffer.array.length == 0 #close this socket
119
+
120
+ req_string = String.from_java_bytes(@byte_buffer.array)
121
+ index_header_begin = req_string.index("\r\n\r\n")
122
+ unless index_header_begin
123
+ # ruby gives nil if index is not found
124
+ @logger.error "double new line not found within first received block of request, null index_header_begin, request: #{req_string}, size: #{req_string.length}"
125
+ raise ::WEBrick::HTTPStatus::BadRequest
126
+ end
127
+
128
+ index_uri_end = req_string.index("\r\n")
129
+ unless index_uri_end
130
+ # ruby gives nil if index is not found
131
+ @logger.error "single new line not found within first received block of request, null index_uri_end, request: #{req_string}, size: #{req_string.length}"
132
+ raise ::WEBrick::HTTPStatus::BadRequest
133
+ end
134
+
135
+ @request_line = req_string[0..index_uri_end]
136
+
137
+ process_request_line(@request_line)
138
+
139
+ if @http_version.major > 0
140
+ process_header(req_string[index_uri_end+2..index_header_begin])
141
+ @header['cookie'].each{|cookie|
142
+ @cookies += ::WEBrick::Cookie::parse(cookie)
143
+ }
144
+ @accept = ::WEBrick::HTTPUtils.parse_qvalues(self['accept'])
145
+ @accept_charset = ::WEBrick::HTTPUtils.parse_qvalues(self['accept-charset'])
146
+ @accept_encoding = ::WEBrick::HTTPUtils.parse_qvalues(self['accept-encoding'])
147
+ @accept_language = ::WEBrick::HTTPUtils.parse_qvalues(self['accept-language'])
148
+ end
149
+ return if @request_method == "CONNECT"
150
+ return if @unparsed_uri == "*"
151
+
152
+ @logger.debug "User Agent: #{self["User-Agent"]}"
153
+
154
+ step_two
155
+
156
+ begin
157
+ if content_length > 0
158
+
159
+ index_header_end = index_header_begin + 4
160
+ index_body_end = req_string.index(/\u0000+\Z/) || req_string.length
161
+
162
+ if index_header_end < index_body_end
163
+ body_bytes_read = index_body_end - index_header_end
164
+ body_remaining = content_length - body_bytes_read
165
+ @body = req_string[index_header_end..index_body_end]
166
+ @logger.debug "body read so far: #{body_bytes_read}, remaining: #{body_remaining}, index_header_end: #{index_header_end}, index_body_end: #{index_body_end}, content length: #{content_length}"
167
+ if body_remaining > 0
168
+ #need to read more body
169
+ @byte_buffer = ByteBuffer.allocate(body_remaining)
170
+ @in_progress = true
171
+ resume
172
+ end
173
+ else
174
+ @logger.error "header did not end within first received block of request. index_header_end: #{index_header_end}, index_body_end: #{index_body_end}, req length: #{req_string.length}, req_string: #{req_string}"
175
+ raise ::WEBrick::HTTPStatus::RequestEntityTooLarge
176
+ end
177
+ end
178
+ rescue Exception => ex
179
+ if ex.respond_to?(:java_class)
180
+ @logger.error "#{ex.java_class.name}"
181
+ else
182
+ @logger.error(ex)
183
+ end
184
+ raise ::WEBrick::HTTPStatus::EOFError
185
+ end
186
+ end
187
+
188
+
189
+ def resume
190
+ begin
191
+ # 2 seconds for maximum interruption between bytes, where 0 bytes are received
192
+ # bytes should be constantly arriving, otherwise the request will be postponed
193
+ time = Time.now
194
+ while @byte_buffer.has_remaining && Time.now - time < 2
195
+ num_read = @socket_channel.java_send :read, [Java::JavaNio::ByteBuffer], @byte_buffer
196
+ if num_read > 0
197
+ time = Time.now
198
+ elsif num_read == -1
199
+ @logger.debug "socket closed"
200
+ raise ::WEBrick::HTTPStatus::EOFError
201
+ end
202
+ end
203
+ @logger.debug "buffer position :#{@byte_buffer.position}"
204
+ if !@byte_buffer.has_remaining
205
+ @body += String.from_java_bytes(@byte_buffer.array)
206
+ @in_progress = false
207
+ elsif @http_version < "1.1"
208
+ raise "http_version < 1.1 client did not send data for more than 2 seconds"
209
+ end
210
+ rescue Exception => ex
211
+ if ex.respond_to?(:java_class)
212
+ @logger.debug "error: #{ex.java_class.name}"
213
+ else
214
+ @logger.debug(ex)
215
+ end
216
+ raise ::WEBrick::HTTPStatus::EOFError
217
+ end
218
+ end
219
+
220
+ def step_two
221
+ begin
222
+ setup_forwarded_info
223
+ @request_uri = parse_uri(@unparsed_uri)
224
+ @path = ::WEBrick::HTTPUtils::unescape(@request_uri.path)
225
+ @path = ::WEBrick::HTTPUtils::normalize_path(@path)
226
+ @host = @request_uri.host
227
+ @port = @request_uri.port
228
+ @query_string = @request_uri.query
229
+ @script_name = ""
230
+ @path_info = @path.dup
231
+ rescue
232
+ raise ::WEBrick::HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
233
+ end
234
+
235
+ if /close/io =~ self["connection"]
236
+ @keep_alive = false
237
+ elsif /keep-alive/io =~ self["connection"]
238
+ @keep_alive = true
239
+ elsif @http_version < "1.1"
240
+ @keep_alive = false
241
+ else
242
+ @keep_alive = true
243
+ end
244
+ end
245
+
246
+ def in_progress?
247
+ @in_progress
248
+ end
249
+
250
+ # Generate HTTP/1.1 100 continue response if the client expects it,
251
+ # otherwise does nothing.
252
+ def continue
253
+ if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
254
+ @socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
255
+ @header.delete('expect')
256
+ end
257
+ end
258
+
259
+ def body(&block)
260
+ @body
261
+ end
262
+
263
+
264
+ ##
265
+ # Request query as a Hash
266
+
267
+ def query
268
+ unless @query
269
+ parse_query()
270
+ end
271
+ @query
272
+ end
273
+
274
+ ##
275
+ # The content-length header
276
+
277
+ def content_length
278
+ begin
279
+ return Integer(self['content-length'])
280
+ rescue Exception
281
+ return 0
282
+ end
283
+ end
284
+
285
+ ##
286
+ # The content-type header
287
+
288
+ def content_type
289
+ return self['content-type']
290
+ end
291
+
292
+ ##
293
+ # Retrieves +header_name+
294
+
295
+ def [](header_name)
296
+ if @header
297
+ value = @header[header_name.downcase]
298
+ value.empty? ? nil : value.join(", ")
299
+ end
300
+ end
301
+
302
+ ##
303
+ # Iterates over the request headers
304
+
305
+ def each
306
+ if @header
307
+ @header.each{|k, v|
308
+ value = @header[k]
309
+ yield(k, value.empty? ? nil : value.join(", "))
310
+ }
311
+ end
312
+ end
313
+
314
+ ##
315
+ # The host this request is for
316
+
317
+ def host
318
+ return @forwarded_host || @host
319
+ end
320
+
321
+ ##
322
+ # The port this request is for
323
+
324
+ def port
325
+ return @forwarded_port || @port
326
+ end
327
+
328
+ ##
329
+ # The server name this request is for
330
+
331
+ def server_name
332
+ return @forwarded_server || @config[:ServerName]
333
+ end
334
+
335
+ ##
336
+ # The client's IP address
337
+
338
+ def remote_ip
339
+ return self["client-ip"] || @forwarded_for || @peeraddr[3]
340
+ end
341
+
342
+ ##
343
+ # Is this an SSL request?
344
+
345
+ def ssl?
346
+ return @request_uri.scheme == "https"
347
+ end
348
+
349
+ ##
350
+ # Should the connection this request was made on be kept alive?
351
+
352
+ def keep_alive?
353
+ @keep_alive
354
+ end
355
+
356
+ def fixup()
357
+ begin
358
+ body{|chunk| } # read remaining body
359
+ rescue ::WEBrick::HTTPStatus::Error => ex
360
+ @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
361
+ @keep_alive = false
362
+ rescue => ex
363
+ @logger.error(ex)
364
+ @keep_alive = false
365
+ end
366
+ end
367
+
368
+ # This method provides the metavariables defined by the revision 3
369
+ # of "The WWW Common Gateway Interface Version 1.1"
370
+ # http://Web.Golux.Com/coar/cgi/
371
+
372
+ def meta_vars
373
+ meta = Hash.new
374
+
375
+ cl = self["Content-Length"]
376
+ ct = self["Content-Type"]
377
+ meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
378
+ meta["CONTENT_TYPE"] = ct.dup if ct
379
+ meta["GATEWAY_INTERFACE"] = "CGI/1.1"
380
+ meta["PATH_INFO"] = @path_info ? @path_info.dup : ""
381
+ #meta["PATH_TRANSLATED"] = nil # no plan to be provided
382
+ meta["QUERY_STRING"] = @query_string ? @query_string.dup : ""
383
+ meta["REMOTE_ADDR"] = @peeraddr.get_address.get_host_address
384
+ meta["REMOTE_HOST"] = @peeraddr.get_host_name
385
+ #meta["REMOTE_IDENT"] = nil # no plan to be provided
386
+ meta["REMOTE_USER"] = @user
387
+ meta["REQUEST_METHOD"] = @request_method.dup
388
+ meta["REQUEST_URI"] = @request_uri.to_s
389
+ meta["SCRIPT_NAME"] = @script_name ? @script_name.dup : ""
390
+ meta["SERVER_NAME"] = @host
391
+ meta["SERVER_PORT"] = @port.to_s
392
+ meta["SERVER_PROTOCOL"] = "HTTP/" + @config[:HTTPVersion].to_s
393
+ meta["SERVER_SOFTWARE"] = @config[:ServerSoftware].dup
394
+
395
+ self.each{|key, val|
396
+ next if /^content-type$/i =~ key
397
+ next if /^content-length$/i =~ key
398
+ name = "HTTP_" + key
399
+ name.gsub!(/-/o, "_")
400
+ name.upcase!
401
+ meta[name] = val
402
+ }
403
+
404
+ meta
405
+ end
406
+
407
+ private
408
+
409
+ MAX_URI_LENGTH = 2083 # :nodoc:
410
+
411
+ def process_request_line(req_line)
412
+ @logger.debug "request line: #{@request_line.strip}"
413
+ if @request_line.bytesize >= MAX_URI_LENGTH
414
+ raise ::WEBrick::HTTPStatus::RequestURITooLarge
415
+ end
416
+ @request_time = Time.now
417
+ raise ::WEBrick::HTTPStatus::EOFError unless @request_line
418
+ if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n?/mo =~ @request_line
419
+ @request_method = $1
420
+ @unparsed_uri = $2
421
+ @http_version = ::WEBrick::HTTPVersion.new($3 ? $3 : "0.9")
422
+ #@logger.debug "request method: #{@request_method}, unparsed uri: #{@unparsed_uri}, http version: #{@http_version}"
423
+ else
424
+ rl = @request_line.sub(/\r?\n\z/o, '')
425
+ raise ::WEBrick::HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
426
+ end
427
+ end
428
+
429
+ def process_header(req_lines)
430
+ @header = ::WEBrick::HTTPUtils::parse_header(req_lines)
431
+ end
432
+
433
+ # Not used anymore but being preserved as an indication of how to handle chunked encoding
434
+ def read_body(socket, block)
435
+ return unless socket
436
+ if tc = self['transfer-encoding']
437
+ case tc
438
+ when /chunked/io then read_chunked(socket, block)
439
+ else raise ::WEBrick::HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
440
+ end
441
+ elsif self['content-length'] || @remaining_size
442
+ @remaining_size ||= self['content-length'].to_i
443
+ while @remaining_size > 0
444
+ sz = [@buffer_size, @remaining_size].min
445
+ break unless buf = read_data(socket, sz)
446
+ @remaining_size -= buf.bytesize
447
+ block.call(buf)
448
+ end
449
+ if @remaining_size > 0 && @socket.eof?
450
+ raise ::WEBrick::HTTPStatus::BadRequest, "invalid body size."
451
+ end
452
+ elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
453
+ raise ::WEBrick::HTTPStatus::LengthRequired
454
+ end
455
+ return @body
456
+ end
457
+
458
+ def parse_uri(str, scheme="http")
459
+ if @config[:Escape8bitURI]
460
+ str = ::WEBrick::HTTPUtils::escape8bit(str)
461
+ end
462
+ str.sub!(%r{\A/+}o, '/')
463
+ uri = URI::parse(str)
464
+ return uri if uri.absolute?
465
+ if @forwarded_host
466
+ host, port = @forwarded_host, @forwarded_port
467
+ #elsif self["host"]
468
+ # @logger.debug "5, #{self['host']}"
469
+ # pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
470
+ # host, port = *self['host'].scan(pattern)[0]
471
+ elsif !@addr.nil?
472
+ host = @addr.get_address.isAnyLocalAddress || @addr.get_address.isLoopbackAddress ?
473
+ "localhost" :
474
+ @addr.get_address.getHostAddress #IP address string in textual presentation
475
+ port = @addr.get_port
476
+ #host, port = @addr[2], @addr[1]
477
+ else
478
+ host, port = @config[:ServerName], @config[:Port]
479
+ end
480
+ uri.scheme = @forwarded_proto || scheme
481
+ uri.host = host
482
+ uri.port = port ? port : nil
483
+ return URI::parse(uri.to_s)
484
+ end
485
+
486
+ def read_chunk_size(socket)
487
+ line = read_line(socket)
488
+ if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
489
+ chunk_size = $1.hex
490
+ chunk_ext = $2
491
+ [ chunk_size, chunk_ext ]
492
+ else
493
+ raise ::WEBrick::HTTPStatus::BadRequest, "bad chunk `#{line}'."
494
+ end
495
+ end
496
+
497
+ def read_chunked(socket, block)
498
+ chunk_size, = read_chunk_size(socket)
499
+ while chunk_size > 0
500
+ data = read_data(socket, chunk_size) # read chunk-data
501
+ if data.nil? || data.bytesize != chunk_size
502
+ raise BadRequest, "bad chunk data size."
503
+ end
504
+ read_line(socket) # skip CRLF
505
+ block.call(data)
506
+ chunk_size, = read_chunk_size(socket)
507
+ end
508
+ read_header(socket) # trailer + CRLF
509
+ @header.delete("transfer-encoding")
510
+ @remaining_size = 0
511
+ end
512
+
513
+ def parse_query()
514
+ begin
515
+ if @request_method == "GET" || @request_method == "HEAD"
516
+ @query = ::WEBrick::HTTPUtils::parse_query(@query_string)
517
+ elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
518
+ @query = ::WEBrick::HTTPUtils::parse_query(body)
519
+ elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
520
+ boundary = ::WEBrick::HTTPUtils::dequote($1)
521
+ @query = ::WEBrick::HTTPUtils::parse_form_data(body, boundary)
522
+ else
523
+ @query = Hash.new
524
+ end
525
+ rescue => ex
526
+ raise ::WEBrick::HTTPStatus::BadRequest, ex.message
527
+ end
528
+ end
529
+
530
+ PrivateNetworkRegexp = /
531
+ ^unknown$|
532
+ ^((::ffff:)?127.0.0.1|::1)$|
533
+ ^(::ffff:)?(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\.
534
+ /ixo
535
+
536
+ # It's said that all X-Forwarded-* headers will contain more than one
537
+ # (comma-separated) value if the original request already contained one of
538
+ # these headers. Since we could use these values as Host header, we choose
539
+ # the initial(first) value. (apr_table_mergen() adds new value after the
540
+ # existing value with ", " prefix)
541
+ def setup_forwarded_info
542
+ if @forwarded_server = self["x-forwarded-server"]
543
+ @forwarded_server = @forwarded_server.split(",", 2).first
544
+ end
545
+ @forwarded_proto = self["x-forwarded-proto"]
546
+ if host_port = self["x-forwarded-host"]
547
+ host_port = host_port.split(",", 2).first
548
+ @forwarded_host, tmp = host_port.split(":", 2)
549
+ @forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
550
+ end
551
+ if addrs = self["x-forwarded-for"]
552
+ addrs = addrs.split(",").collect(&:strip)
553
+ addrs.reject!{|ip| PrivateNetworkRegexp =~ ip }
554
+ @forwarded_for = addrs.first
555
+ end
556
+ end
557
+ end
558
+
559
+ end