Class: Rex::Proto::Http::Packet
- Inherits:
-
Object
- Object
- Rex::Proto::Http::Packet
- Defined in:
- lib/rex/proto/http/packet.rb
Overview
This class represents an HTTP packet.
Defined Under Namespace
Modules: ParseCode, ParseState Classes: Header
Instance Attribute Summary collapse
-
#auto_cl ⇒ Object
Returns the value of attribute auto_cl.
-
#body_bytes_left ⇒ Object
protected
Returns the value of attribute body_bytes_left.
-
#bufq ⇒ Object
Returns the value of attribute bufq.
-
#chunk_max_size ⇒ Object
Returns the value of attribute chunk_max_size.
-
#chunk_min_size ⇒ Object
Returns the value of attribute chunk_min_size.
-
#compress ⇒ Object
Returns the value of attribute compress.
-
#error ⇒ Object
Returns the value of attribute error.
-
#headers ⇒ Object
Returns the value of attribute headers.
-
#incomplete ⇒ Object
Returns the value of attribute incomplete.
-
#inside_chunk ⇒ Object
protected
Returns the value of attribute inside_chunk.
-
#keepalive ⇒ Object
protected
Returns the value of attribute keepalive.
-
#max_data ⇒ Object
Returns the value of attribute max_data.
-
#state ⇒ Object
Returns the value of attribute state.
-
#transfer_chunked ⇒ Object
Returns the value of attribute transfer_chunked.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Return the associated header value, if any.
-
#[]=(key, value) ⇒ Object
Set the associated header value.
- #body ⇒ Object
-
#body=(val) ⇒ Object
The ‘body` attribute was overridden by subclasses, causing quirky behaviour issues, such as when a POST request contained query string parameters resulting in the query string being prepended to the data that was contained in the body.
-
#check_100 ⇒ Object
protected
Override this as needed.
-
#chunk(str, min_size = 1, max_size = 1000) ⇒ Object
Build a ‘Transfer-Encoding: chunked’ payload with random chunk sizes.
-
#cmd_string ⇒ Object
Returns the command string, such as:.
-
#completed? ⇒ Boolean
Returns whether or not parsing has completed.
-
#from_s(str) ⇒ Object
Converts the packet from a string.
-
#initialize ⇒ Packet
constructor
Initializes an instance of an HTTP packet.
-
#output_packet(ignore_chunk = false, headers_only: false) ⇒ Object
Converts the packet to a string.
-
#parse(buf, opts = {}) ⇒ Object
Parses the supplied buffer.
-
#parse_body ⇒ Object
protected
Parses the body portion of the request.
-
#parse_header(opts) ⇒ Object
protected
Parse the HTTP header returned by the target server.
-
#reset ⇒ Object
Reset the parsing state and buffers.
-
#reset_except_queue ⇒ Object
Reset the parsing state but leave the buffers.
-
#to_s(headers_only: false) ⇒ Object
Converts the packet to a string.
-
#to_terminal_output(headers_only: false) ⇒ Object
Outputs a readable string of the packet for terminal output.
-
#update_cmd_parts(str) ⇒ Object
protected
Allows derived classes to split apart the command string.
Constructor Details
Instance Attribute Details
#auto_cl ⇒ Object
Returns the value of attribute auto_cl.
274 275 276 |
# File 'lib/rex/proto/http/packet.rb', line 274 def auto_cl @auto_cl end |
#body_bytes_left ⇒ Object (protected)
Returns the value of attribute body_bytes_left.
286 287 288 |
# File 'lib/rex/proto/http/packet.rb', line 286 def body_bytes_left @body_bytes_left end |
#bufq ⇒ Object
Returns the value of attribute bufq.
273 274 275 |
# File 'lib/rex/proto/http/packet.rb', line 273 def bufq @bufq end |
#chunk_max_size ⇒ Object
Returns the value of attribute chunk_max_size.
281 282 283 |
# File 'lib/rex/proto/http/packet.rb', line 281 def chunk_max_size @chunk_max_size end |
#chunk_min_size ⇒ Object
Returns the value of attribute chunk_min_size.
280 281 282 |
# File 'lib/rex/proto/http/packet.rb', line 280 def chunk_min_size @chunk_min_size end |
#compress ⇒ Object
Returns the value of attribute compress.
277 278 279 |
# File 'lib/rex/proto/http/packet.rb', line 277 def compress @compress end |
#error ⇒ Object
Returns the value of attribute error.
271 272 273 |
# File 'lib/rex/proto/http/packet.rb', line 271 def error @error end |
#headers ⇒ Object
Returns the value of attribute headers.
270 271 272 |
# File 'lib/rex/proto/http/packet.rb', line 270 def headers @headers end |
#incomplete ⇒ Object
Returns the value of attribute incomplete.
278 279 280 |
# File 'lib/rex/proto/http/packet.rb', line 278 def incomplete @incomplete end |
#inside_chunk ⇒ Object (protected)
Returns the value of attribute inside_chunk.
287 288 289 |
# File 'lib/rex/proto/http/packet.rb', line 287 def inside_chunk @inside_chunk end |
#keepalive ⇒ Object (protected)
Returns the value of attribute keepalive.
288 289 290 |
# File 'lib/rex/proto/http/packet.rb', line 288 def keepalive @keepalive end |
#max_data ⇒ Object
Returns the value of attribute max_data.
275 276 277 |
# File 'lib/rex/proto/http/packet.rb', line 275 def max_data @max_data end |
#state ⇒ Object
Returns the value of attribute state.
272 273 274 |
# File 'lib/rex/proto/http/packet.rb', line 272 def state @state end |
#transfer_chunked ⇒ Object
Returns the value of attribute transfer_chunked.
276 277 278 |
# File 'lib/rex/proto/http/packet.rb', line 276 def transfer_chunked @transfer_chunked end |
Instance Method Details
#[](key) ⇒ Object
Return the associated header value, if any.
49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/rex/proto/http/packet.rb', line 49 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.
66 67 68 |
# File 'lib/rex/proto/http/packet.rb', line 66 def []=(key, value) self.headers[key] = value end |
#body ⇒ Object
83 84 85 |
# File 'lib/rex/proto/http/packet.rb', line 83 def body @body_bytes end |
#body=(val) ⇒ Object
The ‘body` attribute was overridden by subclasses, causing quirky behaviour issues, such as when a POST request contained query string parameters resulting in the query string being prepended to the data that was contained in the body. To avoid this utterly ridiculous behaviour while maintaning the status-quo of having the request class shoe-horn query strings into POST bodies, we’re using ‘body_bytes` as an internal buffer to collect the request’s body, rather than using the attribute, which prevents this insanity from happening.
79 80 81 |
# File 'lib/rex/proto/http/packet.rb', line 79 def body=(val) @body_bytes = val end |
#check_100 ⇒ Object (protected)
Override this as needed
453 454 |
# File 'lib/rex/proto/http/packet.rb', line 453 def check_100 end |
#chunk(str, min_size = 1, max_size = 1000) ⇒ Object
Build a ‘Transfer-Encoding: chunked’ payload with random chunk sizes
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/rex/proto/http/packet.rb', line 171 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_string ⇒ Object
Returns the command string, such as:
HTTP/1.0 200 OK for a response
or
GET /foo HTTP/1.0 for a request
266 267 268 |
# File 'lib/rex/proto/http/packet.rb', line 266 def cmd_string self.headers.cmd_string end |
#completed? ⇒ Boolean
Returns whether or not parsing has completed.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/rex/proto/http/packet.rb', line 153 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.
252 253 254 255 |
# File 'lib/rex/proto/http/packet.rb', line 252 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)
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/rex/proto/http/packet.rb', line 205 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, opts = {}) ⇒ Object
Parses the supplied buffer. Returns one of the two parser processing codes (Completed, Partial, or Error).
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/rex/proto/http/packet.rb', line 94 def parse(buf, opts={}) # Append the incoming buffer to the buffer queue. self.bufq += buf.to_s begin # Process the header if(self.state == ParseState::ProcessingHeader) parse_header(opts) 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. # HEAD requests can return immediately. orig_method = opts.fetch(:orig_method) { '' } if (self.body_bytes_left == 0 && (!self.transfer_chunked || orig_method == 'HEAD')) 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_body ⇒ Object (protected)
Parses the body portion of the request.
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 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/rex/proto/http/packet.rb', line 375 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) @body_bytes += part self.body_bytes_left -= part.length # Otherwise, just read it all. else @body_bytes += 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_header(opts) ⇒ Object (protected)
Parse the HTTP header returned by the target server.
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 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/rex/proto/http/packet.rb', line 309 def parse_header(opts) head,data = self.bufq.split(/\r?\n\r?\n/, 2) return if data.nil? 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 orig_method = opts.fetch(:orig_method) { '' } self.body_bytes_left = 0 if orig_method == 'HEAD' # Extract the content length if it was specified (ignoring it for HEAD requests, per RFC9110) if (self.headers['Content-Length'] && orig_method != 'HEAD') 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 elsif (self.headers['Connection']&.downcase == 'upgrade' && self.headers['Upgrade']&.downcase == 'websocket') # The server appears to be responding to a websocket request 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 |
#reset ⇒ Object
Reset the parsing state and buffers.
130 131 132 133 134 135 136 137 |
# File 'lib/rex/proto/http/packet.rb', line 130 def reset self.state = ParseState::ProcessingHeader self.transfer_chunked = false self.inside_chunk = false self.headers.reset self.bufq = '' @body_bytes = '' end |
#reset_except_queue ⇒ Object
Reset the parsing state but leave the buffers.
142 143 144 145 146 147 148 |
# File 'lib/rex/proto/http/packet.rb', line 142 def reset_except_queue self.state = ParseState::ProcessingHeader self.transfer_chunked = false self.inside_chunk = false self.headers.reset @body_bytes = '' end |
#to_s(headers_only: false) ⇒ Object
Converts the packet to a string.
197 198 199 |
# File 'lib/rex/proto/http/packet.rb', line 197 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
190 191 192 |
# File 'lib/rex/proto/http/packet.rb', line 190 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.
299 300 |
# File 'lib/rex/proto/http/packet.rb', line 299 def update_cmd_parts(str) end |