| 1 | module websocket |
| 2 | |
| 3 | import encoding.base64 |
| 4 | import strings |
| 5 | |
| 6 | // handshake manages the websocket handshake process |
| 7 | fn (mut ws Client) handshake() ! { |
| 8 | nonce := get_nonce(ws.nonce_size) |
| 9 | seckey := base64.encode_str(nonce) |
| 10 | mut sb := strings.new_builder(1024) |
| 11 | defer { |
| 12 | unsafe { sb.free() } |
| 13 | } |
| 14 | sb.write_string('GET ') |
| 15 | sb.write_string(ws.uri.resource) |
| 16 | sb.write_string(ws.uri.querystring) |
| 17 | sb.write_string(' HTTP/1.1\r\nHost: ') |
| 18 | sb.write_string(ws.uri.hostname) |
| 19 | sb.write_string(':') |
| 20 | sb.write_string(ws.uri.port) |
| 21 | sb.write_string('\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n') |
| 22 | sb.write_string('Sec-WebSocket-Key: ') |
| 23 | sb.write_string(seckey) |
| 24 | sb.write_string('\r\nSec-WebSocket-Version: 13') |
| 25 | for key in ws.header.keys() { |
| 26 | val := ws.header.custom_values(key).join(',') |
| 27 | sb.write_string('\r\n${key}: ${val}') |
| 28 | } |
| 29 | sb.write_string('\r\n\r\n') |
| 30 | handshake := sb.str() |
| 31 | handshake_bytes := handshake.bytes() |
| 32 | ws.debug_log('sending handshake: ${handshake}') |
| 33 | ws.socket_write(handshake_bytes)! |
| 34 | ws.read_handshake(seckey)! |
| 35 | } |
| 36 | |
| 37 | // handle_server_handshake manages websocket server handshake process |
| 38 | fn (mut s Server) handle_server_handshake(mut c Client) !(string, &ServerClient) { |
| 39 | msg := c.read_handshake_str()! |
| 40 | handshake_response, client := s.parse_client_handshake(msg, mut c)! |
| 41 | return handshake_response, client |
| 42 | } |
| 43 | |
| 44 | // parse_client_handshake parses result from handshake process |
| 45 | fn (mut s Server) parse_client_handshake(client_handshake string, mut c Client) !(string, &ServerClient) { |
| 46 | s.logger.debug('server-> client handshake:\n${client_handshake}') |
| 47 | lines := client_handshake.split_into_lines() |
| 48 | get_tokens := lines[0].split(' ') |
| 49 | if get_tokens.len < 3 { |
| 50 | return error_with_code('unexpected get operation, ${get_tokens}', 1) |
| 51 | } |
| 52 | if get_tokens[0].trim_space() != 'GET' { |
| 53 | return error_with_code("unexpected request '${get_tokens[0]}', expected 'GET'", 2) |
| 54 | } |
| 55 | if get_tokens[2].trim_space() != 'HTTP/1.1' { |
| 56 | return error_with_code("unexpected request ${get_tokens}, expected 'HTTP/1.1'", 3) |
| 57 | } |
| 58 | mut seckey := '' |
| 59 | mut flags := []Flag{} |
| 60 | mut key := '' |
| 61 | for i in 1 .. lines.len { |
| 62 | if lines[i].len <= 0 || lines[i] == '\r\n' { |
| 63 | continue |
| 64 | } |
| 65 | keys := lines[i].split(':').map(it.trim_space()) |
| 66 | match keys[0].to_lower() { |
| 67 | 'upgrade' { |
| 68 | flags << .has_upgrade |
| 69 | } |
| 70 | 'connection' { |
| 71 | flags << .has_connection |
| 72 | } |
| 73 | 'sec-websocket-key' { |
| 74 | key = keys[1] |
| 75 | s.logger.debug('server-> got key: ${key}') |
| 76 | seckey = create_key_challenge_response(key)! |
| 77 | s.logger.debug('server-> challenge: ${seckey}, response: ${key}') |
| 78 | flags << .has_accept |
| 79 | } |
| 80 | else { |
| 81 | // we ignore other headers like protocol for now |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | if flags.len < 3 { |
| 86 | return error_with_code('invalid client handshake, ${client_handshake}', 4) |
| 87 | } |
| 88 | server_handshake := 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${seckey}\r\n\r\n' |
| 89 | server_client := &ServerClient{ |
| 90 | resource_name: get_tokens[1] |
| 91 | client_key: key |
| 92 | client: unsafe { c } |
| 93 | server: unsafe { s } |
| 94 | } |
| 95 | return server_handshake, server_client |
| 96 | } |
| 97 | |
| 98 | // read_handshake_str returns the handshake response |
| 99 | fn (mut ws Client) read_handshake_str() !string { |
| 100 | mut total_bytes_read := 0 |
| 101 | mut msg := [1024]u8{} |
| 102 | mut buffer := [1]u8{} |
| 103 | for total_bytes_read < 1024 { |
| 104 | bytes_read := ws.socket_read_ptr(&buffer[0], 1)! |
| 105 | if bytes_read == 0 { |
| 106 | return error_with_code('unexpected no response from handshake', 5) |
| 107 | } |
| 108 | msg[total_bytes_read] = buffer[0] |
| 109 | total_bytes_read++ |
| 110 | if total_bytes_read > 5 && msg[total_bytes_read - 1] == `\n` |
| 111 | && msg[total_bytes_read - 2] == `\r` && msg[total_bytes_read - 3] == `\n` |
| 112 | && msg[total_bytes_read - 4] == `\r` { |
| 113 | break |
| 114 | } |
| 115 | } |
| 116 | res := msg[..total_bytes_read].bytestr() |
| 117 | return res |
| 118 | } |
| 119 | |
| 120 | // read_handshake reads the handshake result and check if valid |
| 121 | fn (mut ws Client) read_handshake(seckey string) ! { |
| 122 | mut msg := ws.read_handshake_str()! |
| 123 | ws.check_handshake_response(msg, seckey)! |
| 124 | } |
| 125 | |
| 126 | // check_handshake_response checks the response from handshake and returns |
| 127 | // the response and secure key provided by the websocket client |
| 128 | fn (mut ws Client) check_handshake_response(handshake_response string, seckey string) ! { |
| 129 | ws.debug_log('handshake response:\n${handshake_response}') |
| 130 | lines := handshake_response.split_into_lines() |
| 131 | header := lines[0] |
| 132 | if !header.starts_with('HTTP/1.1 101') && !header.starts_with('HTTP/1.0 101') { |
| 133 | return error_with_code('handshake_handler: invalid HTTP status response code, ${header}', 6) |
| 134 | } |
| 135 | for i in 1 .. lines.len { |
| 136 | if lines[i].len <= 0 || lines[i] == '\r\n' { |
| 137 | continue |
| 138 | } |
| 139 | keys := lines[i].split(':').map(it.trim_space()) |
| 140 | match keys[0].to_lower() { |
| 141 | 'upgrade' { |
| 142 | ws.flags << .has_upgrade |
| 143 | } |
| 144 | 'connection' { |
| 145 | ws.flags << .has_connection |
| 146 | } |
| 147 | 'sec-websocket-accept' { |
| 148 | ws.debug_log('seckey: ${seckey}') |
| 149 | challenge := create_key_challenge_response(seckey)! |
| 150 | ws.debug_log('challenge: ${challenge}, response: ${keys[1]}') |
| 151 | if keys[1].trim_space() != challenge { |
| 152 | return error_with_code('handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.', |
| 153 | 7) |
| 154 | } |
| 155 | ws.flags << .has_accept |
| 156 | } |
| 157 | else {} |
| 158 | } |
| 159 | } |
| 160 | if ws.flags.len < 3 { |
| 161 | ws.close(1002, 'invalid websocket HTTP headers')! |
| 162 | return error_with_code('invalid websocket HTTP headers', 8) |
| 163 | } |
| 164 | } |
| 165 | |