Class: Thin::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/thin/request.rb

Overview

A request made to the server.

Constant Summary collapse

HTTP_LESS_HEADERS =
%w(Content-Length Content-Type).freeze
BODYFUL_METHODS =
%w(POST PUT).freeze
MAX_FIELD_NAME_LENGTH =

We control max length of different part of the request to prevent attack and resource overflow.

256
MAX_FIELD_VALUE_LENGTH =
80 * 1024
MAX_REQUEST_URI_LENGTH =
1024 * 12
MAX_FRAGMENT_LENGTH =
1024
MAX_REQUEST_PATH_LENGTH =
1024
MAX_QUERY_STRING_LENGTH =
1024 * 10
MAX_HEADER_LENGTH =
1024 * (80 + 32)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRequest

Returns a new instance of Request.



24
25
26
27
28
29
30
31
32
33
# File 'lib/thin/request.rb', line 24

def initialize
  @params = {
    'GATEWAY_INTERFACE' => 'CGI/1.2',
    'HTTP_VERSION'      => 'HTTP/1.1',
    'SERVER_PROTOCOL'   => 'HTTP/1.1'
  }
  @body = StringIO.new
  @raw = ''
  @trace = false
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body.



21
22
23
# File 'lib/thin/request.rb', line 21

def body
  @body
end

#paramsObject (readonly)

Returns the value of attribute params.



21
22
23
# File 'lib/thin/request.rb', line 21

def params
  @params
end

#pathObject (readonly)

Returns the value of attribute path.



21
22
23
# File 'lib/thin/request.rb', line 21

def path
  @path
end

#rawObject

For debugging and trace



22
23
24
# File 'lib/thin/request.rb', line 22

def raw
  @raw
end

#traceObject

For debugging and trace



22
23
24
# File 'lib/thin/request.rb', line 22

def trace
  @trace
end

#verbObject (readonly)

Returns the value of attribute verb.



21
22
23
# File 'lib/thin/request.rb', line 21

def verb
  @verb
end

Instance Method Details

#closeObject



113
114
115
# File 'lib/thin/request.rb', line 113

def close
  @body.close
end

#content_lengthObject



117
118
119
# File 'lib/thin/request.rb', line 117

def content_length
  @params['CONTENT_LENGTH'].to_i
end

#parse!(content) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/thin/request.rb', line 35

def parse!(content)      
  parse_headers! content
  parse_body!    content if BODYFUL_METHODS.include?(verb)
rescue InvalidRequest => e
  raise
rescue Object => e
  raise InvalidRequest, e.message
end

#parse_body!(content) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/thin/request.rb', line 101

def parse_body!(content)
  # Parse by chunks
  length = content_length
  while @body.size < length
    chunk = content.readpartial(CHUNK_SIZE)
    break unless chunk && chunk.size > 0
    @body << chunk
  end
  
  @body.rewind
end

#parse_headers!(content) ⇒ Object

Parse the request headers from the socket into CGI like variables. Parse the request according to www.w3.org/Protocols/rfc2616/rfc2616.html Parse env variables according to www.ietf.org/rfc/rfc3875

Raises:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/thin/request.rb', line 47

def parse_headers!(content)
  if matches = readline(content).match(/^([A-Z]+) (.*?)(?:#(.*))? HTTP/)
    @verb, uri, fragment = matches[1,3]
  else
    raise InvalidRequest, 'No valid request line found'
  end

  raise InvalidRequest, 'No method specified' unless @verb
  raise InvalidRequest, 'No URI specified'    unless uri

  # Validation various length for security
  raise InvalidRequest, 'URI too long'        if uri.size > MAX_REQUEST_URI_LENGTH
  raise InvalidRequest, 'Fragment too long'   if fragment && fragment.size > MAX_FRAGMENT_LENGTH

  if matches = uri.match(/^(.*?)(?:\?(.*))?$/)
    @path, query_string = matches[1,2]
  else
    raise InvalidRequest, "No valid path found in #{uri}"
  end

  raise InvalidRequest, 'Request path too long' if @path.size > MAX_REQUEST_PATH_LENGTH
  raise InvalidRequest, 'Query string path too long' if query_string && query_string.size > MAX_QUERY_STRING_LENGTH

  @params['REQUEST_URI']    = uri
  @params['FRAGMENT']       = fragment if fragment
  @params['REQUEST_PATH']   =
  @params['PATH_INFO']      = @path
  @params['SCRIPT_NAME']    = '/'
  @params['REQUEST_METHOD'] = @verb
  @params['QUERY_STRING']   = query_string if query_string

  headers_size = 0
  until content.eof?
    line = readline(content)
    headers_size += line.size
    if [?\r, ?\n].include?(line[0])
      break # Reached the end of the headers
    elsif matches = line.match(/^([\w\-]+): (.*)$/)
      name, value = matches[1,2]
      raise InvalidRequest, 'Header name too long' if name.size > MAX_FIELD_NAME_LENGTH
      raise InvalidRequest, 'Header value too long' if value.size > MAX_FIELD_VALUE_LENGTH
      # Transform headers into a HTTP_NAME => value hash
      prefix = HTTP_LESS_HEADERS.include?(name) ? '' : 'HTTP_'
      params["#{prefix}#{name.upcase.gsub('-', '_')}"] = value.chomp
    else
      raise InvalidRequest, "Expected header : #{line}"
    end
  end  

  raise InvalidRequest, 'Headers too long' if headers_size > MAX_HEADER_LENGTH

  @params['SERVER_NAME'] = @params['HTTP_HOST'].split(':')[0] if @params['HTTP_HOST']      
end

#to_sObject



121
122
123
# File 'lib/thin/request.rb', line 121

def to_s
  "#{@params['REQUEST_METHOD']} #{@params['REQUEST_URI']}"
end