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 ⇒ Object
Returns the value of attribute body.
-
#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.
-
#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.
258 259 260 |
# File 'lib/rex/proto/http/packet.rb', line 258 def auto_cl @auto_cl end |
#body ⇒ Object
Returns the value of attribute body.
257 258 259 |
# File 'lib/rex/proto/http/packet.rb', line 257 def body @body end |
#body_bytes_left ⇒ Object (protected)
Returns the value of attribute body_bytes_left.
270 271 272 |
# File 'lib/rex/proto/http/packet.rb', line 270 def body_bytes_left @body_bytes_left end |
#bufq ⇒ Object
Returns the value of attribute bufq.
256 257 258 |
# File 'lib/rex/proto/http/packet.rb', line 256 def bufq @bufq end |
#chunk_max_size ⇒ Object
Returns the value of attribute chunk_max_size.
265 266 267 |
# File 'lib/rex/proto/http/packet.rb', line 265 def chunk_max_size @chunk_max_size end |
#chunk_min_size ⇒ Object
Returns the value of attribute chunk_min_size.
264 265 266 |
# File 'lib/rex/proto/http/packet.rb', line 264 def chunk_min_size @chunk_min_size end |
#compress ⇒ Object
Returns the value of attribute compress.
261 262 263 |
# File 'lib/rex/proto/http/packet.rb', line 261 def compress @compress end |
#error ⇒ Object
Returns the value of attribute error.
254 255 256 |
# File 'lib/rex/proto/http/packet.rb', line 254 def error @error end |
#headers ⇒ Object
Returns the value of attribute headers.
253 254 255 |
# File 'lib/rex/proto/http/packet.rb', line 253 def headers @headers end |
#incomplete ⇒ Object
Returns the value of attribute incomplete.
262 263 264 |
# File 'lib/rex/proto/http/packet.rb', line 262 def incomplete @incomplete end |
#inside_chunk ⇒ Object (protected)
Returns the value of attribute inside_chunk.
271 272 273 |
# File 'lib/rex/proto/http/packet.rb', line 271 def inside_chunk @inside_chunk end |
#keepalive ⇒ Object (protected)
Returns the value of attribute keepalive.
272 273 274 |
# File 'lib/rex/proto/http/packet.rb', line 272 def keepalive @keepalive end |
#max_data ⇒ Object
Returns the value of attribute max_data.
259 260 261 |
# File 'lib/rex/proto/http/packet.rb', line 259 def max_data @max_data end |
#state ⇒ Object
Returns the value of attribute state.
255 256 257 |
# File 'lib/rex/proto/http/packet.rb', line 255 def state @state end |
#transfer_chunked ⇒ Object
Returns the value of attribute transfer_chunked.
260 261 262 |
# File 'lib/rex/proto/http/packet.rb', line 260 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 |
#check_100 ⇒ Object (protected)
Override this as needed
437 438 |
# File 'lib/rex/proto/http/packet.rb', line 437 def check_100 end |
#chunk(str, min_size = 1, max_size = 1000) ⇒ Object
Build a ‘Transfer-Encoding: chunked’ payload with random chunk sizes
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/rex/proto/http/packet.rb', line 154 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
249 250 251 |
# File 'lib/rex/proto/http/packet.rb', line 249 def cmd_string self.headers.cmd_string end |
#completed? ⇒ Boolean
Returns whether or not parsing has completed.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/rex/proto/http/packet.rb', line 136 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.
235 236 237 238 |
# File 'lib/rex/proto/http/packet.rb', line 235 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)
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 227 228 229 230 |
# File 'lib/rex/proto/http/packet.rb', line 188 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).
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 105 106 107 108 |
# File 'lib/rex/proto/http/packet.rb', line 77 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.
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 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/rex/proto/http/packet.rb', line 359 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_header(opts) ⇒ Object (protected)
Parse the HTTP header returned by the target server.
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 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/rex/proto/http/packet.rb', line 293 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.
113 114 115 116 117 118 119 120 |
# File 'lib/rex/proto/http/packet.rb', line 113 def reset self.state = ParseState::ProcessingHeader self.transfer_chunked = false self.inside_chunk = false self.headers.reset self.bufq = '' self.body = '' end |
#reset_except_queue ⇒ Object
Reset the parsing state but leave the buffers.
125 126 127 128 129 130 131 |
# File 'lib/rex/proto/http/packet.rb', line 125 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.
180 181 182 |
# File 'lib/rex/proto/http/packet.rb', line 180 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
173 174 175 |
# File 'lib/rex/proto/http/packet.rb', line 173 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.
283 284 |
# File 'lib/rex/proto/http/packet.rb', line 283 def update_cmd_parts(str) end |