# MIT License

# Copyright (c) 2017 Balazs Bucsay

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys

if "websocket_proto.py" in sys.argv[0]:
	print("[-] Instead of poking around just try: python xfltreat.py --help")
	sys.exit(-1)

import struct
import base64
import os
import hashlib
import re

class WebSocket_Proto():
	def __init__(self):
		return

	def upgrade(self, url, hostname, port, version):
		upgrade_request  = "GET http://{0}:{1}/{2} HTTP/1.1\r\n".format(hostname, port, url)
		upgrade_request += "Host: {0}:{1}\r\n".format(hostname, port)
		upgrade_request += "Upgrade: websocket\r\n"
		upgrade_request += "Connection: Upgrade\r\n"
		upgrade_request += "Origin: {0}:{1}\r\n".format(hostname, port)
		upgrade_request += "Sec-WebSocket-Key: {0}\r\n".format(base64.b64encode(os.urandom(16)))
		upgrade_request += "Sec-WebSocket-Protocol: chat, superchat\r\n"
		upgrade_request += "Sec-WebSocket-Version: {0}\r\n\r\n".format(version)

		return upgrade_request

	def get_handshake_init(self, request):
		r = re.compile("Sec-WebSocket-Key: ([0-9a-zA-Z=/+]*)")
		res = r.search(request)
		if res:
			res = r.search(request).group(1)
		return res

	def calculate_handshake(self, handshake_init):
		magic_string = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
		h = hashlib.sha1()
		h.update(handshake_init+magic_string)
		return base64.b64encode(h.digest())

	def switching_protocol(self, handshake):
		switching_reponse  = "HTTP/1.1 101 Switching Protocols\r\n"
		switching_reponse += "Upgrade: websocket\r\n"
		switching_reponse += "Connection: Upgrade\r\n"
		switching_reponse += "Sec-WebSocket-Accept: {0}\r\n\r\n".format(handshake)

		return switching_reponse
	'''
	Frame format:
      0                   1                   2                   3
      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)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+
	'''
	def build_message(self, serverorclient, opcode, data):
		if serverorclient:
			header = struct.pack(">BBH", ((1<<7) | (opcode & 0xF)), 126, len(data))
		else:
			header = struct.pack(">BBHI", ((1<<7) | (opcode & 0xF)), 0x80 | 126, len(data), 0)

		return header+data

	def is_masked(self, header):
		if ord(header[1:2]) & 0x80:
			return True
		return False

	def get_length_type(self, header):
		if len(header) != 2:
			return -1

		length = struct.unpack(">BB", header)[1] & 0x7F
		if length < 126:
			return 0
		if length == 126:
			return 1
		if length == 127:
			return 2

		return -1

	def get_header_length(self, masked, length_type):
		mask = 0
		if masked:
			mask = 4 
		if length_type == 0:
			return 2 + mask
		if length_type == 1:
			return 4 + mask
		if length_type == 2:
			return 10 + mask

	def get_data_length(self, header, masked, length_type):
		mask = 0
		if masked:
			mask = 4
		header = header[0:len(header)-mask]
		if length_type == 0:
			length = struct.unpack(">BB", header)[1] & 0x7F
			if length > 125:
				return -1
			else:
				return length

		if length_type == 1:
			length = struct.unpack(">HH", header)[1]
			return length

		if length_type == 2:
			length_tmp = struct.unpack(">HII", header)
			return (length_tmp[1] << 32 | length_tmp[2])

		return -1

	def get_data(self, data, header_length, data_length):
		if len(data) != (header_length + data_length):
			return ""

		return data[header_length:header_length+data_length]
