| ... | @@ -18,6 +18,7 @@ pub const connection_pool_size = std.options.http_connection_pool_size; | ... | @@ -18,6 +18,7 @@ pub const connection_pool_size = std.options.http_connection_pool_size; |
| 18 | allocator: Allocator, | 18 | allocator: Allocator, |
| 19 | ca_bundle: std.crypto.Certificate.Bundle = .{}, | 19 | ca_bundle: std.crypto.Certificate.Bundle = .{}, |
| 20 | ca_bundle_mutex: std.Thread.Mutex = .{}, | 20 | ca_bundle_mutex: std.Thread.Mutex = .{}, |
| | 21 | |
| 21 | /// When this is `true`, the next time this client performs an HTTPS request, | 22 | /// When this is `true`, the next time this client performs an HTTPS request, |
| 22 | /// it will first rescan the system for root certificates. | 23 | /// it will first rescan the system for root certificates. |
| 23 | next_https_rescan_certs: bool = true, | 24 | next_https_rescan_certs: bool = true, |
| ... | @@ -25,7 +26,11 @@ next_https_rescan_certs: bool = true, | ... | @@ -25,7 +26,11 @@ next_https_rescan_certs: bool = true, |
| 25 | /// The pool of connections that can be reused (and currently in use). | 26 | /// The pool of connections that can be reused (and currently in use). |
| 26 | connection_pool: ConnectionPool = .{}, | 27 | connection_pool: ConnectionPool = .{}, |
| 27 | | 28 | |
| 28 | proxy: ?HttpProxy = null, | 29 | /// This is the proxy that will handle http:// connections. It *must not* be modified when the client has any active connections. |
| | 30 | http_proxy: ?ProxyInformation = null, |
| | 31 | |
| | 32 | /// This is the proxy that will handle https:// connections. It *must not* be modified when the client has any active connections. |
| | 33 | https_proxy: ?ProxyInformation = null, |
| 29 | | 34 | |
| 30 | /// A set of linked lists of connections that can be reused. | 35 | /// A set of linked lists of connections that can be reused. |
| 31 | pub const ConnectionPool = struct { | 36 | pub const ConnectionPool = struct { |
| ... | @@ -33,7 +38,7 @@ pub const ConnectionPool = struct { | ... | @@ -33,7 +38,7 @@ pub const ConnectionPool = struct { |
| 33 | pub const Criteria = struct { | 38 | pub const Criteria = struct { |
| 34 | host: []const u8, | 39 | host: []const u8, |
| 35 | port: u16, | 40 | port: u16, |
| 36 | is_tls: bool, | 41 | protocol: Connection.Protocol, |
| 37 | }; | 42 | }; |
| 38 | | 43 | |
| 39 | const Queue = std.DoublyLinkedList(Connection); | 44 | const Queue = std.DoublyLinkedList(Connection); |
| ... | @@ -55,9 +60,9 @@ pub const ConnectionPool = struct { | ... | @@ -55,9 +60,9 @@ pub const ConnectionPool = struct { |
| 55 | | 60 | |
| 56 | var next = pool.free.last; | 61 | var next = pool.free.last; |
| 57 | while (next) |node| : (next = node.prev) { | 62 | while (next) |node| : (next = node.prev) { |
| 58 | if ((node.data.protocol == .tls) != criteria.is_tls) continue; | 63 | if (node.data.protocol != criteria.protocol) continue; |
| 59 | if (node.data.port != criteria.port) continue; | 64 | if (node.data.port != criteria.port) continue; |
| 60 | if (!mem.eql(u8, node.data.host, criteria.host)) continue; | 65 | if (!std.ascii.eqlIgnoreCase(node.data.host, criteria.host)) continue; |
| 61 | | 66 | |
| 62 | pool.acquireUnsafe(node); | 67 | pool.acquireUnsafe(node); |
| 63 | return node; | 68 | return node; |
| ... | @@ -84,23 +89,23 @@ pub const ConnectionPool = struct { | ... | @@ -84,23 +89,23 @@ pub const ConnectionPool = struct { |
| 84 | | 89 | |
| 85 | /// Tries to release a connection back to the connection pool. This function is threadsafe. | 90 | /// Tries to release a connection back to the connection pool. This function is threadsafe. |
| 86 | /// If the connection is marked as closing, it will be closed instead. | 91 | /// If the connection is marked as closing, it will be closed instead. |
| 87 | pub fn release(pool: *ConnectionPool, client: *Client, node: *Node) void { | 92 | pub fn release(pool: *ConnectionPool, allocator: Allocator, node: *Node) void { |
| 88 | pool.mutex.lock(); | 93 | pool.mutex.lock(); |
| 89 | defer pool.mutex.unlock(); | 94 | defer pool.mutex.unlock(); |
| 90 | | 95 | |
| 91 | pool.used.remove(node); | 96 | pool.used.remove(node); |
| 92 | | 97 | |
| 93 | if (node.data.closing) { | 98 | if (node.data.closing or pool.free_size == 0) { |
| 94 | node.data.deinit(client); | 99 | node.data.close(allocator); |
| 95 | return client.allocator.destroy(node); | 100 | return allocator.destroy(node); |
| 96 | } | 101 | } |
| 97 | | 102 | |
| 98 | if (pool.free_len >= pool.free_size) { | 103 | if (pool.free_len >= pool.free_size) { |
| 99 | const popped = pool.free.popFirst() orelse unreachable; | 104 | const popped = pool.free.popFirst() orelse unreachable; |
| 100 | pool.free_len -= 1; | 105 | pool.free_len -= 1; |
| 101 | | 106 | |
| 102 | popped.data.deinit(client); | 107 | popped.data.close(allocator); |
| 103 | client.allocator.destroy(popped); | 108 | allocator.destroy(popped); |
| 104 | } | 109 | } |
| 105 | | 110 | |
| 106 | if (node.data.proxied) { | 111 | if (node.data.proxied) { |
| ... | @@ -128,7 +133,7 @@ pub const ConnectionPool = struct { | ... | @@ -128,7 +133,7 @@ pub const ConnectionPool = struct { |
| 128 | defer client.allocator.destroy(node); | 133 | defer client.allocator.destroy(node); |
| 129 | next = node.next; | 134 | next = node.next; |
| 130 | | 135 | |
| 131 | node.data.deinit(client); | 136 | node.data.close(client.allocator); |
| 132 | } | 137 | } |
| 133 | | 138 | |
| 134 | next = pool.used.first; | 139 | next = pool.used.first; |
| ... | @@ -136,7 +141,7 @@ pub const ConnectionPool = struct { | ... | @@ -136,7 +141,7 @@ pub const ConnectionPool = struct { |
| 136 | defer client.allocator.destroy(node); | 141 | defer client.allocator.destroy(node); |
| 137 | next = node.next; | 142 | next = node.next; |
| 138 | | 143 | |
| 139 | node.data.deinit(client); | 144 | node.data.close(client.allocator); |
| 140 | } | 145 | } |
| 141 | | 146 | |
| 142 | pool.* = undefined; | 147 | pool.* = undefined; |
| ... | @@ -283,19 +288,15 @@ pub const Connection = struct { | ... | @@ -283,19 +288,15 @@ pub const Connection = struct { |
| 283 | return Writer{ .context = conn }; | 288 | return Writer{ .context = conn }; |
| 284 | } | 289 | } |
| 285 | | 290 | |
| 286 | pub fn close(conn: *Connection, client: *const Client) void { | 291 | pub fn close(conn: *Connection, allocator: Allocator) void { |
| 287 | if (conn.protocol == .tls) { | 292 | if (conn.protocol == .tls) { |
| 288 | // try to cleanly close the TLS connection, for any server that cares. | 293 | // try to cleanly close the TLS connection, for any server that cares. |
| 289 | _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; | 294 | _ = conn.tls_client.writeEnd(conn.stream, "", true) catch {}; |
| 290 | client.allocator.destroy(conn.tls_client); | 295 | allocator.destroy(conn.tls_client); |
| 291 | } | 296 | } |
| 292 | | 297 | |
| 293 | conn.stream.close(); | 298 | conn.stream.close(); |
| 294 | } | 299 | allocator.free(conn.host); |
| 295 | | | |
| 296 | pub fn deinit(conn: *Connection, client: *const Client) void { | | |
| 297 | conn.close(client); | | |
| 298 | client.allocator.free(conn.host); | | |
| 299 | } | 300 | } |
| 300 | }; | 301 | }; |
| 301 | | 302 | |
| ... | @@ -490,7 +491,7 @@ pub const Request = struct { | ... | @@ -490,7 +491,7 @@ pub const Request = struct { |
| 490 | // If the response wasn't fully read, then we need to close the connection. | 491 | // If the response wasn't fully read, then we need to close the connection. |
| 491 | connection.data.closing = true; | 492 | connection.data.closing = true; |
| 492 | } | 493 | } |
| 493 | req.client.connection_pool.release(req.client, connection); | 494 | req.client.connection_pool.release(req.client.allocator, connection); |
| 494 | } | 495 | } |
| 495 | | 496 | |
| 496 | req.arena.deinit(); | 497 | req.arena.deinit(); |
| ... | @@ -509,7 +510,7 @@ pub const Request = struct { | ... | @@ -509,7 +510,7 @@ pub const Request = struct { |
| 509 | .zstd => |*zstd| zstd.deinit(), | 510 | .zstd => |*zstd| zstd.deinit(), |
| 510 | } | 511 | } |
| 511 | | 512 | |
| 512 | req.client.connection_pool.release(req.client, req.connection.?); | 513 | req.client.connection_pool.release(req.client.allocator, req.connection.?); |
| 513 | req.connection = null; | 514 | req.connection = null; |
| 514 | | 515 | |
| 515 | const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme; | 516 | const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme; |
| ... | @@ -554,24 +555,16 @@ pub const Request = struct { | ... | @@ -554,24 +555,16 @@ pub const Request = struct { |
| 554 | try w.writeByte(' '); | 555 | try w.writeByte(' '); |
| 555 | | 556 | |
| 556 | if (req.method == .CONNECT) { | 557 | if (req.method == .CONNECT) { |
| 557 | try w.writeAll(req.uri.host.?); | 558 | try req.uri.writeToStream(.{ .authority = true }, w); |
| 558 | try w.writeByte(':'); | | |
| 559 | try w.print("{}", .{req.uri.port.?}); | | |
| 560 | } else { | 559 | } else { |
| 561 | if (req.connection.?.data.proxied) { | 560 | try req.uri.writeToStream(.{ |
| 562 | // proxied connections require the full uri | 561 | .scheme = req.connection.?.data.proxied, |
| 563 | if (options.raw_uri) { | 562 | .authentication = req.connection.?.data.proxied, |
| 564 | try w.print("{+/r}", .{req.uri}); | 563 | .authority = req.connection.?.data.proxied, |
| 565 | } else { | 564 | .path = true, |
| 566 | try w.print("{+/}", .{req.uri}); | 565 | .query = true, |
| 567 | } | 566 | .raw = options.raw_uri, |
| 568 | } else { | 567 | }, w); |
| 569 | if (options.raw_uri) { | | |
| 570 | try w.print("{/r}", .{req.uri}); | | |
| 571 | } else { | | |
| 572 | try w.print("{/}", .{req.uri}); | | |
| 573 | } | | |
| 574 | } | | |
| 575 | } | 568 | } |
| 576 | try w.writeByte(' '); | 569 | try w.writeByte(' '); |
| 577 | try w.writeAll(@tagName(req.version)); | 570 | try w.writeAll(@tagName(req.version)); |
| ... | @@ -579,7 +572,7 @@ pub const Request = struct { | ... | @@ -579,7 +572,7 @@ pub const Request = struct { |
| 579 | | 572 | |
| 580 | if (!req.headers.contains("host")) { | 573 | if (!req.headers.contains("host")) { |
| 581 | try w.writeAll("Host: "); | 574 | try w.writeAll("Host: "); |
| 582 | try w.writeAll(req.uri.host.?); | 575 | try req.uri.writeToStream(.{ .authority = true }, w); |
| 583 | try w.writeAll("\r\n"); | 576 | try w.writeAll("\r\n"); |
| 584 | } | 577 | } |
| 585 | | 578 | |
| ... | @@ -636,6 +629,24 @@ pub const Request = struct { | ... | @@ -636,6 +629,24 @@ pub const Request = struct { |
| 636 | try w.writeAll("\r\n"); | 629 | try w.writeAll("\r\n"); |
| 637 | } | 630 | } |
| 638 | | 631 | |
| | 632 | if (req.connection.?.data.proxied) { |
| | 633 | const proxy_headers: ?http.Headers = switch (req.connection.?.data.protocol) { |
| | 634 | .plain => if (req.client.http_proxy) |proxy| proxy.headers else null, |
| | 635 | .tls => if (req.client.https_proxy) |proxy| proxy.headers else null, |
| | 636 | }; |
| | 637 | |
| | 638 | if (proxy_headers) |headers| { |
| | 639 | for (headers.list.items) |entry| { |
| | 640 | if (entry.value.len == 0) continue; |
| | 641 | |
| | 642 | try w.writeAll(entry.name); |
| | 643 | try w.writeAll(": "); |
| | 644 | try w.writeAll(entry.value); |
| | 645 | try w.writeAll("\r\n"); |
| | 646 | } |
| | 647 | } |
| | 648 | } |
| | 649 | |
| 639 | try w.writeAll("\r\n"); | 650 | try w.writeAll("\r\n"); |
| 640 | | 651 | |
| 641 | try buffered.flush(); | 652 | try buffered.flush(); |
| ... | @@ -893,18 +904,15 @@ pub const Request = struct { | ... | @@ -893,18 +904,15 @@ pub const Request = struct { |
| 893 | } | 904 | } |
| 894 | }; | 905 | }; |
| 895 | | 906 | |
| 896 | pub const HttpProxy = struct { | 907 | pub const ProxyInformation = struct { |
| 897 | pub const ProxyAuthentication = union(enum) { | 908 | allocator: Allocator, |
| 898 | basic: []const u8, | 909 | headers: http.Headers, |
| 899 | custom: []const u8, | | |
| 900 | }; | | |
| 901 | | 910 | |
| 902 | protocol: Connection.Protocol, | 911 | protocol: Connection.Protocol, |
| 903 | host: []const u8, | 912 | host: []const u8, |
| 904 | port: ?u16 = null, | 913 | port: u16, |
| 905 | | 914 | |
| 906 | /// The value for the Proxy-Authorization header. | 915 | supports_connect: bool = true, |
| 907 | auth: ?ProxyAuthentication = null, | | |
| 908 | }; | 916 | }; |
| 909 | | 917 | |
| 910 | /// Release all associated resources with the client. | 918 | /// Release all associated resources with the client. |
| ... | @@ -912,19 +920,115 @@ pub const HttpProxy = struct { | ... | @@ -912,19 +920,115 @@ pub const HttpProxy = struct { |
| 912 | pub fn deinit(client: *Client) void { | 920 | pub fn deinit(client: *Client) void { |
| 913 | client.connection_pool.deinit(client); | 921 | client.connection_pool.deinit(client); |
| 914 | | 922 | |
| | 923 | if (client.http_proxy) |*proxy| { |
| | 924 | proxy.allocator.free(proxy.host); |
| | 925 | proxy.headers.deinit(); |
| | 926 | } |
| | 927 | |
| | 928 | if (client.https_proxy) |*proxy| { |
| | 929 | proxy.allocator.free(proxy.host); |
| | 930 | proxy.headers.deinit(); |
| | 931 | } |
| | 932 | |
| 915 | client.ca_bundle.deinit(client.allocator); | 933 | client.ca_bundle.deinit(client.allocator); |
| 916 | client.* = undefined; | 934 | client.* = undefined; |
| 917 | } | 935 | } |
| 918 | | 936 | |
| 919 | pub const ConnectUnproxiedError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed }; | 937 | /// Uses the *_proxy environment variable to set any unset proxies for the client. |
| | 938 | /// This function *must not* be called when the client has any active connections. |
| | 939 | pub fn loadDefaultProxies(client: *Client) !void { |
| | 940 | if (client.http_proxy == null) http: { |
| | 941 | const content: []const u8 = if (std.process.hasEnvVarConstant("http_proxy")) |
| | 942 | try std.process.getEnvVarOwned(client.allocator, "http_proxy") |
| | 943 | else if (std.process.hasEnvVarConstant("HTTP_PROXY")) |
| | 944 | try std.process.getEnvVarOwned(client.allocator, "HTTP_PROXY") |
| | 945 | else if (std.process.hasEnvVarConstant("all_proxy")) |
| | 946 | try std.process.getEnvVarOwned(client.allocator, "all_proxy") |
| | 947 | else if (std.process.hasEnvVarConstant("ALL_PROXY")) |
| | 948 | try std.process.getEnvVarOwned(client.allocator, "ALL_PROXY") |
| | 949 | else |
| | 950 | break :http; |
| | 951 | defer client.allocator.free(content); |
| | 952 | |
| | 953 | const uri = try Uri.parse(content); |
| | 954 | |
| | 955 | const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme; |
| | 956 | client.http_proxy = .{ |
| | 957 | .allocator = client.allocator, |
| | 958 | .headers = .{ .allocator = client.allocator }, |
| | 959 | |
| | 960 | .protocol = protocol, |
| | 961 | .host = if (uri.host) |host| try client.allocator.dupe(u8, host) else return error.UriMissingHost, |
| | 962 | .port = uri.port orelse switch (protocol) { |
| | 963 | .plain => 80, |
| | 964 | .tls => 443, |
| | 965 | }, |
| | 966 | }; |
| | 967 | |
| | 968 | if (uri.user != null and uri.password != null) { |
| | 969 | const unencoded = try std.fmt.allocPrint(client.allocator, "{s}:{s}", .{ uri.user.?, uri.password.? }); |
| | 970 | defer client.allocator.free(unencoded); |
| | 971 | |
| | 972 | const buffer = try client.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(unencoded.len)); |
| | 973 | defer client.allocator.free(buffer); |
| | 974 | |
| | 975 | const result = std.base64.standard.Encoder.encode(buffer, unencoded); |
| | 976 | |
| | 977 | try client.http_proxy.?.headers.append("proxy-authorization", result); |
| | 978 | } |
| | 979 | } |
| | 980 | |
| | 981 | if (client.https_proxy == null) https: { |
| | 982 | const content: []const u8 = if (std.process.hasEnvVarConstant("https_proxy")) |
| | 983 | try std.process.getEnvVarOwned(client.allocator, "https_proxy") |
| | 984 | else if (std.process.hasEnvVarConstant("HTTPS_PROXY")) |
| | 985 | try std.process.getEnvVarOwned(client.allocator, "HTTPS_PROXY") |
| | 986 | else if (std.process.hasEnvVarConstant("all_proxy")) |
| | 987 | try std.process.getEnvVarOwned(client.allocator, "all_proxy") |
| | 988 | else if (std.process.hasEnvVarConstant("ALL_PROXY")) |
| | 989 | try std.process.getEnvVarOwned(client.allocator, "ALL_PROXY") |
| | 990 | else |
| | 991 | break :https; |
| | 992 | defer client.allocator.free(content); |
| | 993 | |
| | 994 | const uri = try Uri.parse(content); |
| | 995 | |
| | 996 | const protocol = protocol_map.get(uri.scheme) orelse return error.UnsupportedUrlScheme; |
| | 997 | client.http_proxy = .{ |
| | 998 | .allocator = client.allocator, |
| | 999 | .headers = .{ .allocator = client.allocator }, |
| | 1000 | |
| | 1001 | .protocol = protocol, |
| | 1002 | .host = if (uri.host) |host| try client.allocator.dupe(u8, host) else return error.UriMissingHost, |
| | 1003 | .port = uri.port orelse switch (protocol) { |
| | 1004 | .plain => 80, |
| | 1005 | .tls => 443, |
| | 1006 | }, |
| | 1007 | }; |
| | 1008 | |
| | 1009 | if (uri.user != null and uri.password != null) { |
| | 1010 | const unencoded = try std.fmt.allocPrint(client.allocator, "{s}:{s}", .{ uri.user.?, uri.password.? }); |
| | 1011 | defer client.allocator.free(unencoded); |
| | 1012 | |
| | 1013 | const buffer = try client.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(unencoded.len)); |
| | 1014 | defer client.allocator.free(buffer); |
| | 1015 | |
| | 1016 | const result = std.base64.standard.Encoder.encode(buffer, unencoded); |
| | 1017 | |
| | 1018 | try client.https_proxy.?.headers.append("proxy-authorization", result); |
| | 1019 | } |
| | 1020 | } |
| | 1021 | } |
| | 1022 | |
| | 1023 | pub const ConnectTcpError = Allocator.Error || error{ ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, ConnectionResetByPeer, TemporaryNameServerFailure, NameServerFailure, UnknownHostName, HostLacksNetworkAddresses, UnexpectedConnectFailure, TlsInitializationFailed }; |
| 920 | | 1024 | |
| 921 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. | 1025 | /// Connect to `host:port` using the specified protocol. This will reuse a connection if one is already open. |
| 922 | /// This function is threadsafe. | 1026 | /// This function is threadsafe. |
| 923 | pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectUnproxiedError!*ConnectionPool.Node { | 1027 | pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectTcpError!*ConnectionPool.Node { |
| 924 | if (client.connection_pool.findConnection(.{ | 1028 | if (client.connection_pool.findConnection(.{ |
| 925 | .host = host, | 1029 | .host = host, |
| 926 | .port = port, | 1030 | .port = port, |
| 927 | .is_tls = protocol == .tls, | 1031 | .protocol = protocol, |
| 928 | })) |node| | 1032 | })) |node| |
| 929 | return node; | 1033 | return node; |
| 930 | | 1034 | |
| ... | @@ -948,8 +1052,8 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol: | ... | @@ -948,8 +1052,8 @@ pub fn connectUnproxied(client: *Client, host: []const u8, port: u16, protocol: |
| 948 | conn.data = .{ | 1052 | conn.data = .{ |
| 949 | .stream = stream, | 1053 | .stream = stream, |
| 950 | .tls_client = undefined, | 1054 | .tls_client = undefined, |
| 951 | .protocol = protocol, | | |
| 952 | | 1055 | |
| | 1056 | .protocol = protocol, |
| 953 | .host = try client.allocator.dupe(u8, host), | 1057 | .host = try client.allocator.dupe(u8, host), |
| 954 | .port = port, | 1058 | .port = port, |
| 955 | }; | 1059 | }; |
| ... | @@ -981,7 +1085,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti | ... | @@ -981,7 +1085,7 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 981 | if (client.connection_pool.findConnection(.{ | 1085 | if (client.connection_pool.findConnection(.{ |
| 982 | .host = path, | 1086 | .host = path, |
| 983 | .port = 0, | 1087 | .port = 0, |
| 984 | .is_tls = false, | 1088 | .protocol = .plain, |
| 985 | })) |node| | 1089 | })) |node| |
| 986 | return node; | 1090 | return node; |
| 987 | | 1091 | |
| ... | @@ -1007,34 +1111,120 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti | ... | @@ -1007,34 +1111,120 @@ pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connecti |
| 1007 | return conn; | 1111 | return conn; |
| 1008 | } | 1112 | } |
| 1009 | | 1113 | |
| 1010 | // Prevents a dependency loop in request() | 1114 | pub fn connectTunnel( |
| 1011 | const ConnectErrorPartial = ConnectUnproxiedError || error{ UnsupportedUrlScheme, ConnectionRefused }; | 1115 | client: *Client, |
| 1012 | pub const ConnectError = ConnectErrorPartial || RequestError; | 1116 | proxy: *ProxyInformation, |
| | 1117 | tunnel_host: []const u8, |
| | 1118 | tunnel_port: u16, |
| | 1119 | ) !*ConnectionPool.Node { |
| | 1120 | if (!proxy.supports_connect) return error.TunnelNotSupported; |
| 1013 | | 1121 | |
| 1014 | pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionPool.Node { | | |
| 1015 | if (client.connection_pool.findConnection(.{ | 1122 | if (client.connection_pool.findConnection(.{ |
| 1016 | .host = host, | 1123 | .host = tunnel_host, |
| 1017 | .port = port, | 1124 | .port = tunnel_port, |
| 1018 | .is_tls = protocol == .tls, | 1125 | .protocol = proxy.protocol, |
| 1019 | })) |node| | 1126 | })) |node| |
| 1020 | return node; | 1127 | return node; |
| 1021 | | 1128 | |
| 1022 | if (client.proxy) |proxy| { | 1129 | var maybe_valid = false; |
| 1023 | const proxy_port: u16 = proxy.port orelse switch (proxy.protocol) { | 1130 | _ = tunnel: { |
| 1024 | .plain => 80, | 1131 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); |
| 1025 | .tls => 443, | 1132 | errdefer { |
| | 1133 | conn.data.closing = true; |
| | 1134 | client.connection_pool.release(client.allocator, conn); |
| | 1135 | } |
| | 1136 | |
| | 1137 | const uri = Uri{ |
| | 1138 | .scheme = "http", |
| | 1139 | .user = null, |
| | 1140 | .password = null, |
| | 1141 | .host = tunnel_host, |
| | 1142 | .port = tunnel_port, |
| | 1143 | .path = "", |
| | 1144 | .query = null, |
| | 1145 | .fragment = null, |
| 1026 | }; | 1146 | }; |
| 1027 | | 1147 | |
| 1028 | const conn = try client.connectUnproxied(proxy.host, proxy_port, proxy.protocol); | 1148 | // we can use a small buffer here because a CONNECT response should be very small |
| 1029 | conn.data.proxied = true; | 1149 | var buffer: [8096]u8 = undefined; |
| | 1150 | |
| | 1151 | var req = client.request(.CONNECT, uri, proxy.headers, .{ |
| | 1152 | .handle_redirects = false, |
| | 1153 | .connection = conn, |
| | 1154 | .header_strategy = .{ .static = buffer[0..] }, |
| | 1155 | }) catch |err| { |
| | 1156 | std.log.debug("err {}", .{err}); |
| | 1157 | break :tunnel err; |
| | 1158 | }; |
| | 1159 | defer req.deinit(); |
| | 1160 | |
| | 1161 | req.start(.{ .raw_uri = true }) catch |err| break :tunnel err; |
| | 1162 | req.wait() catch |err| break :tunnel err; |
| | 1163 | |
| | 1164 | if (req.response.status.class() == .server_error) { |
| | 1165 | maybe_valid = true; |
| | 1166 | break :tunnel error.ServerError; |
| | 1167 | } |
| | 1168 | |
| | 1169 | if (req.response.status != .ok) break :tunnel error.ConnectionRefused; |
| 1030 | | 1170 | |
| | 1171 | // this connection is now a tunnel, so we can't use it for anything else, it will only be released when the client is de-initialized. |
| | 1172 | req.connection = null; |
| | 1173 | |
| | 1174 | client.allocator.free(conn.data.host); |
| | 1175 | conn.data.host = try client.allocator.dupe(u8, tunnel_host); |
| | 1176 | errdefer client.allocator.free(conn.data.host); |
| | 1177 | |
| | 1178 | conn.data.port = tunnel_port; |
| | 1179 | conn.data.closing = false; |
| | 1180 | |
| | 1181 | return conn; |
| | 1182 | } catch { |
| | 1183 | // something went wrong with the tunnel |
| | 1184 | proxy.supports_connect = maybe_valid; |
| | 1185 | return error.TunnelNotSupported; |
| | 1186 | }; |
| | 1187 | } |
| | 1188 | |
| | 1189 | // Prevents a dependency loop in request() |
| | 1190 | const ConnectErrorPartial = ConnectTcpError || error{ UnsupportedUrlScheme, ConnectionRefused }; |
| | 1191 | pub const ConnectError = ConnectErrorPartial || RequestError; |
| | 1192 | |
| | 1193 | pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connection.Protocol) ConnectError!*ConnectionPool.Node { |
| | 1194 | // pointer required so that `supports_connect` can be updated if a CONNECT fails |
| | 1195 | const potential_proxy: ?*ProxyInformation = switch (protocol) { |
| | 1196 | .plain => if (client.http_proxy) |*proxy_info| proxy_info else null, |
| | 1197 | .tls => if (client.https_proxy) |*proxy_info| proxy_info else null, |
| | 1198 | }; |
| | 1199 | |
| | 1200 | if (potential_proxy) |proxy| { |
| | 1201 | // don't attempt to proxy the proxy thru itself. |
| | 1202 | if (std.mem.eql(u8, proxy.host, host) and proxy.port == port and proxy.protocol == protocol) { |
| | 1203 | return client.connectTcp(host, port, protocol); |
| | 1204 | } |
| | 1205 | |
| | 1206 | _ = if (proxy.supports_connect) tunnel: { |
| | 1207 | return connectTunnel(client, proxy, host, port) catch |err| switch (err) { |
| | 1208 | error.TunnelNotSupported => break :tunnel, |
| | 1209 | else => |e| return e, |
| | 1210 | }; |
| | 1211 | }; |
| | 1212 | |
| | 1213 | // fall back to using the proxy as a normal http proxy |
| | 1214 | const conn = try client.connectTcp(proxy.host, proxy.port, proxy.protocol); |
| | 1215 | errdefer { |
| | 1216 | conn.data.closing = true; |
| | 1217 | client.connection_pool.release(conn); |
| | 1218 | } |
| | 1219 | |
| | 1220 | conn.data.proxied = true; |
| 1031 | return conn; | 1221 | return conn; |
| 1032 | } else { | | |
| 1033 | return client.connectUnproxied(host, port, protocol); | | |
| 1034 | } | 1222 | } |
| | 1223 | |
| | 1224 | return client.connectTcp(host, port, protocol); |
| 1035 | } | 1225 | } |
| 1036 | | 1226 | |
| 1037 | pub const RequestError = ConnectUnproxiedError || ConnectErrorPartial || Request.StartError || std.fmt.ParseIntError || Connection.WriteError || error{ | 1227 | pub const RequestError = ConnectTcpError || ConnectErrorPartial || Request.StartError || std.fmt.ParseIntError || Connection.WriteError || error{ |
| 1038 | UnsupportedUrlScheme, | 1228 | UnsupportedUrlScheme, |
| 1039 | UriMissingHost, | 1229 | UriMissingHost, |
| 1040 | | 1230 | |