import six from . import packet class Payload(object): """Engine.IO payload.""" def __init__(self, packets=None, encoded_payload=None): self.packets = packets or [] if encoded_payload is not None: self.decode(encoded_payload) def encode(self, b64=False): """Encode the payload for transmission.""" encoded_payload = b'' for pkt in self.packets: encoded_packet = pkt.encode(b64=b64) packet_len = len(encoded_packet) if b64: encoded_payload += str(packet_len).encode('utf-8') + b':' + \ encoded_packet else: binary_len = b'' while packet_len != 0: binary_len = six.int2byte(packet_len % 10) + binary_len packet_len = int(packet_len / 10) if not pkt.binary: encoded_payload += b'\0' else: encoded_payload += b'\1' encoded_payload += binary_len + b'\xff' + encoded_packet return encoded_payload def decode(self, encoded_payload): """Decode a transmitted payload.""" fixed_double_encode = False self.packets = [] while encoded_payload: if six.byte2int(encoded_payload[0:1]) <= 1: packet_len = 0 i = 1 while six.byte2int(encoded_payload[i:i + 1]) != 255: packet_len = packet_len * 10 + six.byte2int( encoded_payload[i:i + 1]) i += 1 self.packets.append(packet.Packet( encoded_packet=encoded_payload[i + 1:i + 1 + packet_len])) else: i = encoded_payload.find(b':') if i == -1: raise ValueError('invalid payload') # the packet_len below is given in utf-8 characters, but we # receive the payload as bytes, so down below this length is # adjusted to reflect byte length packet_len = int(encoded_payload[0:i]) if not fixed_double_encode: # the engine.io javascript client sends text payloads with # a double UTF-8 encoding. Here we try to fix that mess and # restore the original packet try: # first we remove one UTF-8 encoding layer fixed_payload = encoded_payload.decode( 'utf-8').encode('raw_unicode_escape') # then we make sure the result can be decoded a second # time (this will raise an exception if not) fixed_payload.decode('utf-8') # if a second utf-8 decode worked, then this appears to # be a double encoded packet, so here we keep the # packet after a single decode, since the packet class # will perform a decode as well, and in this case it is # not necessary to adjust the packet length encoded_payload = fixed_payload except: # if we couldn't apply a double utf-8 decode then # the packet must have been correct, so we just adjust # the packet length to be in bytes and not utf-8 # characters and keep going packet_len += len(encoded_payload) - len(fixed_payload) fixed_double_encode = True pkt = encoded_payload[i + 1: i + 1 + packet_len] self.packets.append(packet.Packet(encoded_packet=pkt)) encoded_payload = encoded_payload[i + 1 + packet_len:]