aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/net/websocket/handshake.v
blob: 9f3ab000e08acb29f707f342836f9edc61f6bc6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
[manualfree]
module websocket

import encoding.base64
import strings

// handshake manages the websocket handshake process
fn (mut ws Client) handshake() ? {
	nonce := get_nonce(ws.nonce_size)
	seckey := base64.encode_str(nonce)
	mut sb := strings.new_builder(1024)
	defer {
		unsafe { sb.free() }
	}
	sb.write_string('GET ')
	sb.write_string(ws.uri.resource)
	sb.write_string(ws.uri.querystring)
	sb.write_string(' HTTP/1.1\r\nHost: ')
	sb.write_string(ws.uri.hostname)
	sb.write_string(':')
	sb.write_string(ws.uri.port)
	sb.write_string('\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n')
	sb.write_string('Sec-WebSocket-Key: ')
	sb.write_string(seckey)
	sb.write_string('\r\nSec-WebSocket-Version: 13')
	for key in ws.header.keys() {
		val := ws.header.custom_values(key).join(',')
		sb.write_string('\r\n$key:$val')
	}
	sb.write_string('\r\n\r\n')
	handshake := sb.str()
	defer {
		unsafe { handshake.free() }
	}
	handshake_bytes := handshake.bytes()
	ws.debug_log('sending handshake: $handshake')
	ws.socket_write(handshake_bytes) ?
	ws.read_handshake(seckey) ?
	unsafe { handshake_bytes.free() }
}

// handle_server_handshake manages websocket server handshake process
fn (mut s Server) handle_server_handshake(mut c Client) ?(string, &ServerClient) {
	msg := c.read_handshake_str() ?
	handshake_response, client := s.parse_client_handshake(msg, mut c) ?
	unsafe { msg.free() }
	return handshake_response, client
}

// parse_client_handshake parses result from handshake process
fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client) ?(string, &ServerClient) {
	s.logger.debug('server-> client handshake:\n$client_handshake')
	lines := client_handshake.split_into_lines()
	get_tokens := lines[0].split(' ')
	if get_tokens.len < 3 {
		return error_with_code('unexpected get operation, $get_tokens', 1)
	}
	if get_tokens[0].trim_space() != 'GET' {
		return error_with_code("unexpected request '${get_tokens[0]}', expected 'GET'",
			2)
	}
	if get_tokens[2].trim_space() != 'HTTP/1.1' {
		return error_with_code("unexpected request $get_tokens, expected 'HTTP/1.1'",
			3)
	}
	mut seckey := ''
	mut flags := []Flag{}
	mut key := ''
	for i in 1 .. lines.len {
		if lines[i].len <= 0 || lines[i] == '\r\n' {
			continue
		}
		keys := lines[i].split(':')
		match keys[0] {
			'Upgrade', 'upgrade' {
				flags << .has_upgrade
			}
			'Connection', 'connection' {
				flags << .has_connection
			}
			'Sec-WebSocket-Key', 'sec-websocket-key' {
				key = keys[1].trim_space()
				s.logger.debug('server-> got key: $key')
				seckey = create_key_challenge_response(key) ?
				s.logger.debug('server-> challenge: $seckey, response: ${keys[1]}')
				flags << .has_accept
			}
			else {
				// we ignore other headers like protocol for now
			}
		}
		unsafe { keys.free() }
	}
	if flags.len < 3 {
		return error_with_code('invalid client handshake, $client_handshake', 4)
	}
	server_handshake := 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $seckey\r\n\r\n'
	server_client := &ServerClient{
		resource_name: get_tokens[1]
		client_key: key
		client: unsafe { c }
		server: unsafe { s }
	}
	unsafe {
		lines.free()
		flags.free()
		get_tokens.free()
		seckey.free()
		key.free()
	}
	return server_handshake, server_client
}

// read_handshake_str returns the handshake response
fn (mut ws Client) read_handshake_str() ?string {
	mut total_bytes_read := 0
	mut msg := [1024]byte{}
	mut buffer := [1]byte{}
	for total_bytes_read < 1024 {
		bytes_read := ws.socket_read_ptr(&buffer[0], 1) ?
		if bytes_read == 0 {
			return error_with_code('unexpected no response from handshake', 5)
		}
		msg[total_bytes_read] = buffer[0]
		total_bytes_read++
		if total_bytes_read > 5 && msg[total_bytes_read - 1] == `\n`
			&& msg[total_bytes_read - 2] == `\r` && msg[total_bytes_read - 3] == `\n`
			&& msg[total_bytes_read - 4] == `\r` {
			break
		}
	}
	res := msg[..total_bytes_read].bytestr()
	return res
}

// read_handshake reads the handshake result and check if valid
fn (mut ws Client) read_handshake(seckey string) ? {
	mut msg := ws.read_handshake_str() ?
	ws.check_handshake_response(msg, seckey) ?
	unsafe { msg.free() }
}

// check_handshake_response checks the response from handshake and returns
// the response and secure key provided by the websocket client
fn (mut ws Client) check_handshake_response(handshake_response string, seckey string) ? {
	ws.debug_log('handshake response:\n$handshake_response')
	lines := handshake_response.split_into_lines()
	header := lines[0]
	if !header.starts_with('HTTP/1.1 101') && !header.starts_with('HTTP/1.0 101') {
		return error_with_code('handshake_handler: invalid HTTP status response code, $header',
			6)
	}
	for i in 1 .. lines.len {
		if lines[i].len <= 0 || lines[i] == '\r\n' {
			continue
		}
		keys := lines[i].split(':')
		match keys[0] {
			'Upgrade', 'upgrade' {
				ws.flags << .has_upgrade
			}
			'Connection', 'connection' {
				ws.flags << .has_connection
			}
			'Sec-WebSocket-Accept', 'sec-websocket-accept' {
				ws.debug_log('seckey: $seckey')
				challenge := create_key_challenge_response(seckey) ?
				ws.debug_log('challenge: $challenge, response: ${keys[1]}')
				if keys[1].trim_space() != challenge {
					return error_with_code('handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.',
						7)
				}
				ws.flags << .has_accept
				unsafe { challenge.free() }
			}
			else {}
		}
		unsafe { keys.free() }
	}
	unsafe { lines.free() }
	if ws.flags.len < 3 {
		ws.close(1002, 'invalid websocket HTTP headers') ?
		return error_with_code('invalid websocket HTTP headers', 8)
	}
}