lib/ftw/websocket/parser.rb in ftw-0.0.6 vs lib/ftw/websocket/parser.rb in ftw-0.0.7

- old
+ new

@@ -60,10 +60,12 @@ # Transition to a specified state and set the next required read length. def transition(state, next_length) @logger.debug("Transitioning", :transition => state, :nextlen => next_length) @state = state + # TODO(sissel): Assert this self.respond_to?(state) + # TODO(sissel): Assert next_length is a number need(next_length) end # def transition # Feed data to this parser. # @@ -75,12 +77,11 @@ def feed(data) @buffer << data while have?(@need) value = send(@state) # Return if our state yields a value. - return value if !value.nil? - #yield value if !value.nil? and block_given? + yield value if !value.nil? and block_given? end return nil end # def << # Do we have at least 'length' bytes in the buffer? @@ -109,11 +110,11 @@ # +-+-+-+-+------- # |F|R|R|R| opcode # |I|S|S|S| (4) # |N|V|V|V| # | |1|2|3| - byte = get.bytes.first + byte = get(@need).bytes.first @opcode = byte & 0xF # last 4 bites @fin = (byte & 0x80 == 0x80)# first bit #p :byte => byte, :bits => byte.to_s(2), :opcode => @opcode, :fin => @fin # mask_and_payload_length has a minimum length @@ -125,33 +126,39 @@ end # def flags_and_opcode # State: mask_and_payload_init # See: http://tools.ietf.org/html/rfc6455#section-5.2 def mask_and_payload_init - byte = get.bytes.first - @mask = byte & 0x80 # first bit (msb) + byte = get(@need).bytes.first + @masked = (byte & 0x80) == 0x80 # first bit (msb) @payload_length = byte & 0x7F # remaining bits are the length case @payload_length when 126 # 2 byte, unsigned value is the payload length transition(:extended_payload_length, 2) when 127 # 8 byte, unsigned value is the payload length transition(:extended_payload_length, 8) else - # Keep the current payload length, a 7 bit value. - # Go to read the payload - transition(:payload, @payload_length) + # If there is a mask, read that next + if @masked + transition(:mask, 4) + else + # Otherwise, the payload is next. + # Keep the current payload length, a 7 bit value. + # Go to read the payload + transition(:payload, @payload_length) + end end # case @payload_length # This state yields no output. return nil end # def mask_and_payload_init # State: payload_length # This is the 'extended payload length' with support for both 16 # and 64 bit lengths. # See: http://tools.ietf.org/html/rfc6455#section-5.2 - def payload_length + def extended_payload_length # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-------+-+-------------+-------------------------------+ # |F|R|R|R| opcode|M| Payload len | Extended payload length | # |I|S|S|S| (4) |A| (7) | (16/64) | # |N|V|V|V| |S| | (if payload len==126/127) | @@ -169,16 +176,32 @@ @payload_length = data.unpack("Q") else raise "Unknown payload_length byte length '#{@need}'" end - transition(:payload, @payload_length) + if @masked + # Read the mask next if there is one. + transition(:mask, 4) + else + # Otherwise, next is the payload + transition(:payload, @payload_length) + end # This state yields no output. return nil - end # def payload_length + end # def extended_payload_length + def mask + # + - - - - - - - - - - - - - - - +-------------------------------+ + # | |Masking-key, if MASK set to 1 | + # +-------------------------------+-------------------------------+ + # | Masking-key (continued) | Payload Data | + # +-------------------------------- - - - - - - - - - - - - - - - + + @mask = get(@need) + transition(:payload, @payload_length) + end # def mask + # State: payload # Read the full payload and return it. # See: http://tools.ietf.org/html/rfc6455#section-5.3 # def payload @@ -187,10 +210,26 @@ # instead of returning the whole thing, simply return an Enumerable that # yields chunks of the payload. There's no reason to buffer the entire # thing. Have the consumer of this library make that decision. data = get(@need) transition(:flags_and_opcode, 1) - return data + if @masked + return unmask(data, @mask) + else + return data + end end # def payload + + def unmask(message, key) + masked = [] + mask_bytes = key.unpack("C4") + i = 0 + message.each_byte do |byte| + masked << (byte ^ mask_bytes[i % 4]) + i += 1 + end + p :unmasked => masked.pack("C*"), :original => message + return masked.pack("C*") + end # def mask public(:feed) end # class FTW::WebSocket::Parser