diff --git a/CHANGELOG.md b/CHANGELOG.md index 74bdf14..1026aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Libcurl.js Changelog: +## v0.6.11 (7/18/24): +- Add support for SOCKS5, SOCKS4, and HTTP proxies + ## v0.6.10 (7/11/24): - Fix a problem where the websocket URL wouldn't be set properly in some cases diff --git a/README.md b/README.md index 1cfb377..f29373e 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ Sending cookies is supported, but they will not be automatically sent unless you The response may contain multiple HTTP headers with the same name, which the `Headers` object isn't able to properly represent. If this matters to you, use `response.raw_headers`, which is an array of key value pairs, instead of `response.headers`. There is support for streaming the response body using a `ReadableStream`, as well as canceling requests using an `AbortSignal`. All requests made using this method share the same connection pool, which has a limit of 50 active TCP connections. +The `proxy` option may be used to specify the URL of a `socks5h`, `socks4a`, or `http` proxy server. For example `proxy: "socks5h://127.0.0.0:1080"` will set the proxy server for just the current request. + ### Creating New HTTP Sessions: To create new sessions for HTTP requests, use the `libcurl.HTTPSession` class. The constructor for this class takes the following arguments: - `options` - An optional object with various settings. @@ -129,6 +131,7 @@ To create new sessions for HTTP requests, use the `libcurl.HTTPSession` class. T The valid HTTP session settings are: - `enable_cookies` - A boolean which indicate whether or not cookies should be persisted within the session. - `cookie_jar` - A string containing the data in the cookie jar file. This should have been exported from a previous session. For more information on the format for this file, see the [curl documentation](https://curl.se/docs/http-cookies.html). +- `proxy` - A URL for a `socks5h`, `socks4a`, or `http` proxy server. Each HTTP session has the following methods available: - `fetch` - Identical to the `libcurl.fetch` function but only creates connections in this session. @@ -156,6 +159,7 @@ To use WebSockets, create a `libcurl.CurlWebSocket` object, which takes the foll The valid WebSocket options are: - `headers` - HTTP request headers for the websocket handshake. - `verbose` - A boolean flag that toggles the verbose libcurl output. This verbose output will be passed to the function defined in `libcurl.stderr`, which is `console.warn` by default. +- `proxy` - A URL for a `socks5h`, `socks4a`, or `http` proxy server. The following callbacks are available: - `CurlWebSocket.onopen` - Called when the websocket is successfully connected. @@ -198,6 +202,7 @@ Raw TLS sockets can be created with the `libcurl.TLSSocket` class, which takes t The valid TLS socket options are: - `verbose` - A boolean flag that toggles the verbose libcurl output. +- `proxy` - A URL for a `socks5h`, `socks4a`, or `http` proxy server. The callbacks work similarly to the `libcurl.CurlWebSocket` object, with the main difference being that the `onmessage` callback always returns a `Uint8Array`. diff --git a/client/exported_funcs.txt b/client/exported_funcs.txt index 0a61260..7659fd2 100644 --- a/client/exported_funcs.txt +++ b/client/exported_funcs.txt @@ -9,6 +9,7 @@ session_cleanup request_cleanup create_request +request_set_proxy get_version get_cacert get_error_str diff --git a/client/javascript/http.js b/client/javascript/http.js index 68db92d..6effa93 100644 --- a/client/javascript/http.js +++ b/client/javascript/http.js @@ -88,6 +88,9 @@ class HTTPSession extends CurlSession { if (this.cookie_filename && params.credentials !== "omit") { c_func(_http_set_cookie_jar, [http_handle, this.cookie_filename]); } + if (params.proxy) { + c_func_str(_request_set_proxy, [http_handle, params.proxy]); + } this.start_request(http_handle); }); @@ -110,6 +113,12 @@ class HTTPSession extends CurlSession { else { url = "" + url; } + + if (this.options && this.options.proxy) { + params.proxy = this.options.proxy; + } + check_proxy(params.proxy); + let body = await this.constructor.create_options(params); return await this.request_async(url, params, body); } @@ -152,6 +161,7 @@ class HTTPSession extends CurlSession { if (params.body instanceof ReadableStream) { params.duplex = "half"; } + let request_obj = new Request("http://127.0.0.1/", params); let array_buffer = await request_obj.arrayBuffer(); if (array_buffer.byteLength > 0) { diff --git a/client/javascript/tls_socket.js b/client/javascript/tls_socket.js index 17ed256..e18315e 100644 --- a/client/javascript/tls_socket.js +++ b/client/javascript/tls_socket.js @@ -17,8 +17,15 @@ class TLSSocket extends CurlSession { this.connected = false; this.recv_loop = null; - this.set_connections(1, 0); - this.connect(); + try { + check_proxy(this.options.proxy); + this.set_connections(1, 0); + this.connect(); + } + catch (e) { + this.cleanup(true); + throw e; + } } connect() { @@ -40,6 +47,9 @@ class TLSSocket extends CurlSession { this.http_handle = this.create_request(this.url, data_callback, finish_callback, headers_callback); _tls_socket_set_options(this.http_handle, +this.options.verbose); + if (this.options.proxy) { + c_func_str(_request_set_proxy, [this.http_handle, this.options.proxy]); + } this.start_request(this.http_handle); } diff --git a/client/javascript/util.js b/client/javascript/util.js index ec47961..2ee579d 100644 --- a/client/javascript/util.js +++ b/client/javascript/util.js @@ -104,4 +104,14 @@ function c_func_str(target, args=[]) { let str = UTF8ToString(ptr); _free(ptr); return str; +} + +//ensure that the proxy url has a valid protocol +function check_proxy(proxy) { + if (typeof proxy === "string" || proxy instanceof String) { + let protocol = new URL(proxy).protocol; + if (!["socks5h:", "socks4a:", "http:"].includes(protocol)) { + throw new TypeError("Only socks5h, socks4a, and http proxies are supported."); + } + } } \ No newline at end of file diff --git a/client/javascript/websocket.js b/client/javascript/websocket.js index 9c32f54..e86229f 100644 --- a/client/javascript/websocket.js +++ b/client/javascript/websocket.js @@ -19,8 +19,15 @@ class CurlWebSocket extends CurlSession { this.http_handle = null; this.recv_buffer = []; - this.set_connections(1, 0); - this.connect(); + try { + check_proxy(this.options.proxy); + this.set_connections(1, 0); + this.connect(); + } + catch (e) { + this.cleanup(true); + throw e; + } } connect() { @@ -52,6 +59,9 @@ class CurlWebSocket extends CurlSession { this.http_handle = this.create_request(this.url, data_callback, finish_callback, headers_callback); c_func(_http_set_options, [this.http_handle, JSON.stringify(request_options), null, 0]); _websocket_set_options(this.http_handle); + if (this.options.proxy) { + c_func_str(_request_set_proxy, [this.http_handle, this.options.proxy]); + } this.start_request(this.http_handle); } diff --git a/client/libcurl/request.c b/client/libcurl/request.c index 77d57b2..a6f7c20 100644 --- a/client/libcurl/request.c +++ b/client/libcurl/request.c @@ -75,6 +75,10 @@ void finish_request(CURLMsg *curl_msg) { (*request_info->end_callback)(request_info->request_id, error); } +void request_set_proxy(CURL* http_handle, const char* proxy_url) { + curl_easy_setopt(http_handle, CURLOPT_PROXY, proxy_url); +} + unsigned char* get_cacert() { return _cacert_pem; } diff --git a/client/package.json b/client/package.json index 33c83ed..0f16d6d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "libcurl.js", - "version": "0.6.10", + "version": "0.6.11", "description": "An experimental port of libcurl to WebAssembly for use in the browser.", "main": "libcurl.mjs", "exports": {