| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module http |
| 5 | |
| 6 | import net |
| 7 | |
| 8 | const vschannel_connect_failed_msg = 'Failed to connect to host' |
| 9 | |
| 10 | const vschannel_sec_e_internal_error = -2146893052 |
| 11 | |
| 12 | // A fast version that avoids allocations in s.split() |
| 13 | // returns the locations of the 2 spaces |
| 14 | // "GET / HTTP/1.1" => ["GET" "/" "HTTP/1.1"] |
| 15 | fn fast_request_words(line string) (int, int) { |
| 16 | space1 := line.index(' ') or { return 0, 0 } |
| 17 | space2 := line.index_after(' ', space1 + 1) or { return 0, 0 } |
| 18 | return space1, space2 |
| 19 | } |
| 20 | |
| 21 | fn vschannel_error_message(err_code int) string { |
| 22 | $if windows { |
| 23 | if err_code >= int(net.WsaError.wsaeintr) |
| 24 | && err_code <= int(net.WsaError.wsa_ipsec_name_policy_error) { |
| 25 | return '(${err_code}) ${net.wsa_error(err_code)}' |
| 26 | } |
| 27 | } |
| 28 | if err_code < 0 { |
| 29 | return '0x${u32(err_code):08x}' |
| 30 | } |
| 31 | return '${err_code}' |
| 32 | } |
| 33 | |
| 34 | fn vschannel_connect_error(err_code int) IError { |
| 35 | if err_code == 0 { |
| 36 | return error(vschannel_connect_failed_msg) |
| 37 | } |
| 38 | return error_with_code(vschannel_connect_failed_msg, err_code) |
| 39 | } |
| 40 | |
| 41 | fn vschannel_should_report_connect_failure(err_code int) bool { |
| 42 | if err_code == vschannel_sec_e_internal_error { |
| 43 | return true |
| 44 | } |
| 45 | $if windows { |
| 46 | return err_code in [int(net.WsaError.wsaenotconn), int(net.WsaError.wsaenetdown), |
| 47 | int(net.WsaError.wsaenetunreach), int(net.WsaError.wsaetimedout), |
| 48 | int(net.WsaError.wsaeconnrefused), int(net.WsaError.wsaehostdown), |
| 49 | int(net.WsaError.wsaehostunreach), int(net.WsaError.wsahost_not_found), |
| 50 | int(net.WsaError.wsatry_again), int(net.WsaError.wsano_recovery), |
| 51 | int(net.WsaError.wsano_data)] |
| 52 | } |
| 53 | return false |
| 54 | } |
| 55 | |
| 56 | fn vschannel_looks_like_connect_failure_response(response_text string) bool { |
| 57 | trimmed := response_text.trim_space() |
| 58 | return trimmed.starts_with('Error ') && (trimmed.contains('sending data to server') |
| 59 | || trimmed.contains('Error performing handshake')) |
| 60 | } |
| 61 | |
| 62 | fn vschannel_request_error(err_code int) IError { |
| 63 | if vschannel_should_report_connect_failure(err_code) { |
| 64 | return vschannel_connect_error(err_code) |
| 65 | } |
| 66 | return error_with_code('http: vschannel request failed: ${vschannel_error_message(err_code)}', |
| 67 | err_code) |
| 68 | } |
| 69 | |
| 70 | fn vschannel_parse_response(response_text string, err_code int) !Response { |
| 71 | if response_text.len < 5 || response_text[..5].to_lower() != 'http/' { |
| 72 | if vschannel_should_report_connect_failure(err_code) |
| 73 | || vschannel_looks_like_connect_failure_response(response_text) { |
| 74 | return vschannel_connect_error(err_code) |
| 75 | } |
| 76 | } |
| 77 | return parse_response(response_text) |
| 78 | } |
| 79 | |