webricknio 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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