Class: Rex::Proto::Http::Packet

Inherits:
Object
  • Object
show all
Defined in:
lib/rex/proto/http/packet.rb

Overview

This class represents an HTTP packet.

Direct Known Subclasses

Request, Response

Defined Under Namespace

Modules: ParseCode, ParseState Classes: Header

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePacket

Initializes an instance of an HTTP packet.


40
41
42
43
44
45
# File 'lib/rex/proto/http/packet.rb', line 40

def initialize()
  self.headers = Header.new
  self.auto_cl = true

  reset
end

Instance Attribute Details

#auto_clObject

Returns the value of attribute auto_cl


254
255
256
# File 'lib/rex/proto/http/packet.rb', line 254

def auto_cl
  @auto_cl
end

#bodyObject

Returns the value of attribute body


253
254
255
# File 'lib/rex/proto/http/packet.rb', line 253

def body
  @body
end

#body_bytes_leftObject (protected)

Returns the value of attribute body_bytes_left


266
267
268
# File 'lib/rex/proto/http/packet.rb', line 266

def body_bytes_left
  @body_bytes_left
end

#bufqObject

Returns the value of attribute bufq


252
253
254
# File 'lib/rex/proto/http/packet.rb', line 252

def bufq
  @bufq
end

#chunk_max_sizeObject

Returns the value of attribute chunk_max_size


261
262
263
# File 'lib/rex/proto/http/packet.rb', line 261

def chunk_max_size
  @chunk_max_size
end

#chunk_min_sizeObject

Returns the value of attribute chunk_min_size


260
261
262
# File 'lib/rex/proto/http/packet.rb', line 260

def chunk_min_size
  @chunk_min_size
end

#compressObject

Returns the value of attribute compress


257
258
259
# File 'lib/rex/proto/http/packet.rb', line 257

def compress
  @compress
end

#errorObject

Returns the value of attribute error


250
251
252
# File 'lib/rex/proto/http/packet.rb', line 250

def error
  @error
end

#headersObject

Returns the value of attribute headers


249
250
251
# File 'lib/rex/proto/http/packet.rb', line 249

def headers
  @headers
end

#incompleteObject

Returns the value of attribute incomplete


258
259
260
# File 'lib/rex/proto/http/packet.rb', line 258

def incomplete
  @incomplete
end

#inside_chunkObject (protected)

Returns the value of attribute inside_chunk


267
268
269
# File 'lib/rex/proto/http/packet.rb', line 267

def inside_chunk
  @inside_chunk
end

#keepaliveObject (protected)

Returns the value of attribute keepalive


268
269
270
# File 'lib/rex/proto/http/packet.rb', line 268

def keepalive
  @keepalive
end

#max_dataObject

Returns the value of attribute max_data


255
256
257
# File 'lib/rex/proto/http/packet.rb', line 255

def max_data
  @max_data
end

#stateObject

Returns the value of attribute state


251
252
253
# File 'lib/rex/proto/http/packet.rb', line 251

def state
  @state
end

#transfer_chunkedObject

Returns the value of attribute transfer_chunked


256
257
258
# File 'lib/rex/proto/http/packet.rb', line 256

def transfer_chunked
  @transfer_chunked
end

Instance Method Details

#[](key) ⇒ Object

Return the associated header value, if any.


50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/rex/proto/http/packet.rb', line 50

def [](key)
  if (self.headers.include?(key))
    return self.headers[key]
  end

  self.headers.each_pair do |k,v|
    if (k.downcase == key.downcase)
      return v
    end
  end

  return nil
end

#[]=(key, value) ⇒ Object

Set the associated header value.


67
68
69
# File 'lib/rex/proto/http/packet.rb', line 67

def []=(key, value)
  self.headers[key] = value
end

#check_100Object (protected)

Override this as needed


427
428
# File 'lib/rex/proto/http/packet.rb', line 427

def check_100
end

#chunk(str, min_size = 1, max_size = 1000) ⇒ Object

Build a 'Transfer-Encoding: chunked' payload with random chunk sizes


150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/rex/proto/http/packet.rb', line 150

def chunk(str, min_size = 1, max_size = 1000)
  chunked = ''

  # min chunk size is 1 byte
  if (min_size < 1); min_size = 1; end

  # don't be dumb
  if (max_size < min_size); max_size = min_size; end

  while (str.size > 0)
    chunk = str.slice!(0, rand(max_size - min_size) + min_size)
    chunked += sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n"
  end
  chunked += "0\r\n\r\n"
end

#cmd_stringObject

Returns the command string, such as:

HTTP/1.0 200 OK for a response

or

GET /foo HTTP/1.0 for a request


245
246
247
# File 'lib/rex/proto/http/packet.rb', line 245

def cmd_string
  self.headers.cmd_string
end

#completed?Boolean

Returns whether or not parsing has completed.

Returns:

  • (Boolean)

132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/rex/proto/http/packet.rb', line 132

def completed?

  return true if self.state == ParseState::Completed

  # If the parser state is processing the body and there are an
  # undetermined number of bytes left to read, we just need to say that
  # things are completed as it's hard to tell whether or not they really
  # are.
  if (self.state == ParseState::ProcessingBody and self.body_bytes_left < 0)
    return true
  end

  false
end

#from_s(str) ⇒ Object

Converts the packet from a string.


231
232
233
234
# File 'lib/rex/proto/http/packet.rb', line 231

def from_s(str)
  reset
  parse(str)
end

#output_packet(ignore_chunk = false, headers_only: false) ⇒ Object

Converts the packet to a string. If ignore_chunk is set the chunked encoding is omitted (for pretty print)


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/rex/proto/http/packet.rb', line 184

def output_packet(ignore_chunk = false, headers_only: false)
  content = self.body.to_s.dup

  # Update the content length field in the header with the body length.
  if (content)
    if !self.compress.nil?
      case self.compress
        when 'gzip'
          self.headers['Content-Encoding'] = 'gzip'
          content = Rex::Text.gzip(content)
        when 'deflate'
          self.headers['Content-Encoding'] = 'deflate'
          content = Rex::Text.zlib_deflate(content)
        when 'none'
        # this one is fine...
        # when 'compress'
        else
          raise RuntimeError, 'Invalid Content-Encoding'
      end
    end

    unless ignore_chunk
      if self.auto_cl && self.transfer_chunked
        raise RuntimeError, "'Content-Length' and 'Transfer-Encoding: chunked' are incompatible"
      end

      if self.auto_cl
        self.headers['Content-Length'] = content.length
      elsif self.transfer_chunked
        if self.proto != '1.1'
          raise RuntimeError, 'Chunked encoding is only available via 1.1'
        end
        self.headers['Transfer-Encoding'] = 'chunked'
        content = self.chunk(content, self.chunk_min_size, self.chunk_max_size)
      end
    end
  end

  str  = self.headers.to_s(cmd_string)
  str += content || '' unless headers_only

  str
end

#parse(buf) ⇒ Object

Parses the supplied buffer. Returns one of the two parser processing codes (Completed, Partial, or Error).


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
100
101
102
103
104
# File 'lib/rex/proto/http/packet.rb', line 75

def parse(buf)

  # Append the incoming buffer to the buffer queue.
  self.bufq += buf.to_s

  begin

    # Process the header
    if(self.state == ParseState::ProcessingHeader)
      parse_header
    end

    # Continue on to the body if the header was processed
    if(self.state == ParseState::ProcessingBody)
      # Chunked encoding sets the parsing state on its own
      if (self.body_bytes_left == 0 and not self.transfer_chunked)
        self.state = ParseState::Completed
      else
        parse_body
      end
    end
  rescue
    # XXX: BUG: This rescue might be a problem because it will swallow TimeoutError
    self.error = $!
    return ParseCode::Error
  end

  # Return completed or partial to the parsing status to the caller
  (self.state == ParseState::Completed) ? ParseCode::Completed : ParseCode::Partial
end

#parse_bodyObject (protected)

Parses the body portion of the request.


349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/rex/proto/http/packet.rb', line 349

def parse_body
  # Just return if the buffer is empty
  if (self.bufq.length == 0)
    return
  end

  # Handle chunked transfer-encoding responses
  if (self.transfer_chunked and self.inside_chunk != 1 and self.bufq.length)

    # Remove any leading newlines or spaces
    self.bufq.lstrip!

    # If we didn't get a newline, then this might not be the full
    # length, go back and get more.
    # e.g.
    #  first packet: "200"
    #  second packet: "0\r\n\r\n<html>..."
    if not bufq.index("\n")
      return
    end

    # Extract the actual hexadecimal length value
    clen = self.bufq.slice!(/^[a-fA-F0-9]+\r?\n/)

    clen.rstrip! if (clen)

    # if we happen to fall upon the end of the buffer for the next chunk len and have no data left, go get some more...
    if clen.nil? and self.bufq.length == 0
      return
    end

    # Invalid chunk length, exit out early
    if clen.nil?
      self.state = ParseState::Completed
      return
    end

    self.body_bytes_left = clen.to_i(16)

    if (self.body_bytes_left == 0)
      self.bufq.sub!(/^\r?\n/s,'')
      self.state = ParseState::Completed
      self.check_100
      return
    end

    self.inside_chunk = 1
  end

  # If there are bytes remaining, slice as many as we can and append them
  # to our body state.
  if (self.body_bytes_left > 0)
    part = self.bufq.slice!(0, self.body_bytes_left)
    self.body += part
    self.body_bytes_left -= part.length
  # Otherwise, just read it all.
  else
    self.body += self.bufq
    self.bufq  = ''
  end

  # Finish this chunk and move on to the next one
  if (self.transfer_chunked and self.body_bytes_left == 0)
    self.inside_chunk = 0
    self.parse_body
    return
  end

  # If there are no more bytes left, then parsing has completed and we're
  # ready to go.
  if (not self.transfer_chunked and self.body_bytes_left == 0)
    self.state = ParseState::Completed
    self.check_100
    return
  end
end

#parse_headerObject (protected)

Parsing


288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/rex/proto/http/packet.rb', line 288

def parse_header

  head,data = self.bufq.split(/\r?\n\r?\n/, 2)

  return if not data

  self.headers.from_s(head)
  self.bufq = data || ""

  # Set the content-length to -1 as a placeholder (read until EOF)
  self.body_bytes_left = -1

  # Extract the content length if it was specified
  if (self.headers['Content-Length'])
    self.body_bytes_left = self.headers['Content-Length'].to_i
  end

  # Look for a chunked transfer header
  if (self.headers['Transfer-Encoding'].to_s.downcase == 'chunked')
    self.transfer_chunked = true
    self.auto_cl = false
  end

  # Determine how to handle data when there is no length header
  if (self.body_bytes_left == -1)
    if (not self.transfer_chunked)
      if (self.headers['Connection'].to_s.downcase.include?('keep-alive'))
        # If we are using keep-alive, but have no content-length and
        # no chunked transfer header, pretend this is the entire
        # buffer and call it done
        self.body_bytes_left = self.bufq.length
      elsif (not self.headers['Content-Length'] and self.class == Rex::Proto::Http::Request)
        # RFC 2616 says: "The presence of a message-body in a request
        # is signaled by the inclusion of a Content-Length or
        # Transfer-Encoding header field in the request's
        # message-headers."
        #
        # So if we haven't seen either a Content-Length or a
        # Transfer-Encoding header, there shouldn't be a message body.
        self.body_bytes_left = 0
      #else
      # Otherwise we need to keep reading until EOF
      end
    end
  end

  # Throw an error if we didnt parse the header properly
  if !self.headers.cmd_string
    raise RuntimeError, "Invalid command string", caller
  end

  # Move the state into body processing
  self.state = ParseState::ProcessingBody

  # Allow derived classes to update the parts of the command string
  self.update_cmd_parts(self.headers.cmd_string)
end

#resetObject

Reset the parsing state and buffers.


109
110
111
112
113
114
115
116
# File 'lib/rex/proto/http/packet.rb', line 109

def reset
  self.state = ParseState::ProcessingHeader
  self.transfer_chunked = false
  self.inside_chunk     = false
  self.headers.reset
  self.bufq  = ''
  self.body  = ''
end

#reset_except_queueObject

Reset the parsing state but leave the buffers.


121
122
123
124
125
126
127
# File 'lib/rex/proto/http/packet.rb', line 121

def reset_except_queue
  self.state = ParseState::ProcessingHeader
  self.transfer_chunked = false
  self.inside_chunk     = false
  self.headers.reset
  self.body  = ''
end

#to_s(headers_only: false) ⇒ Object

Converts the packet to a string.


176
177
178
# File 'lib/rex/proto/http/packet.rb', line 176

def to_s(headers_only: false)
  output_packet(false, headers_only: headers_only)
end

#to_terminal_output(headers_only: false) ⇒ Object

Outputs a readable string of the packet for terminal output


169
170
171
# File 'lib/rex/proto/http/packet.rb', line 169

def to_terminal_output(headers_only: false)
  output_packet(true, headers_only: headers_only)
end

#update_cmd_parts(str) ⇒ Object (protected)

Allows derived classes to split apart the command string.


279
280
# File 'lib/rex/proto/http/packet.rb', line 279

def update_cmd_parts(str)
end