1+ // Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+ //
3+ // Francis Bouvier <[email protected] > 4+ // Pierre Tachoire <[email protected] > 5+ //
6+ // This program is free software: you can redistribute it and/or modify
7+ // it under the terms of the GNU Affero General Public License as
8+ // published by the Free Software Foundation, either version 3 of the
9+ // License, or (at your option) any later version.
10+ //
11+ // This program is distributed in the hope that it will be useful,
12+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ // GNU Affero General Public License for more details.
15+ //
16+ // You should have received a copy of the GNU Affero General Public License
17+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
118const std = @import ("std" );
219const builtin = @import ("builtin" );
320
@@ -30,8 +47,9 @@ pub const Client = struct {
3047 state_pool : StatePool ,
3148 root_ca : tls.config.CertBundle ,
3249
33- pub fn init (allocator : Allocator , max_concurrent : usize ) ! Client {
34- var root_ca = try tls .config .CertBundle .fromSystem (allocator );
50+ // we only allow passing in a root_ca for testing
51+ pub fn init (allocator : Allocator , max_concurrent : usize , root_ca_ : ? tls.config.CertBundle ) ! Client {
52+ var root_ca = root_ca_ orelse try tls .config .CertBundle .fromSystem (allocator );
3553 errdefer root_ca .deinit (allocator );
3654
3755 const state_pool = try StatePool .init (allocator , max_concurrent );
@@ -1420,8 +1438,7 @@ pub const Response = struct {
14201438 fn processData (self : * Response ) ! ? []u8 {
14211439 const data = self ._data orelse return null ;
14221440 const result = try self ._reader .process (data );
1423- self ._done = result
1424- .done ;
1441+ self ._done = result .done ;
14251442 self ._data = result .unprocessed ; // for the next call
14261443 return result .data ;
14271444 }
@@ -1631,21 +1648,21 @@ test "HttpClient Reader: fuzz" {
16311648}
16321649
16331650test "HttpClient: invalid url" {
1634- var client = try Client . init ( testing . allocator , 1 );
1651+ var client = try testClient ( );
16351652 defer client .deinit ();
16361653 try testing .expectError (error .UriMissingHost , client .request (.GET , "http:///" ));
16371654}
16381655
16391656test "HttpClient: sync connect error" {
1640- var client = try Client . init ( testing . allocator , 2 );
1657+ var client = try testClient ( );
16411658 defer client .deinit ();
16421659
16431660 var req = try client .request (.GET , "HTTP://127.0.0.1:9920" );
16441661 try testing .expectError (error .ConnectionRefused , req .sendSync (.{}));
16451662}
16461663
16471664test "HttpClient: sync no body" {
1648- var client = try Client . init ( testing . allocator , 2 );
1665+ var client = try testClient ( );
16491666 defer client .deinit ();
16501667
16511668 var req = try client .request (.GET , "http://127.0.0.1:9582/http_client/simple" );
@@ -1658,8 +1675,24 @@ test "HttpClient: sync no body" {
16581675 try testing .expectEqual ("0" , res .header .get ("content-length" ));
16591676}
16601677
1678+ test "HttpClient: sync tls no body" {
1679+ // https://github.com/ianic/tls.zig/issues/10
1680+ for (0.. 1) | _ | {
1681+ var client = try testClient ();
1682+ defer client .deinit ();
1683+
1684+ var req = try client .request (.GET , "https://127.0.0.1:9581/http_client/simple" );
1685+ var res = try req .sendSync (.{});
1686+
1687+ try testing .expectEqual (null , try res .next ());
1688+ try testing .expectEqual (200 , res .header .status );
1689+ try testing .expectEqual (1 , res .header .count ());
1690+ try testing .expectEqual ("0" , res .header .get ("content-length" ));
1691+ }
1692+ }
1693+
16611694test "HttpClient: sync with body" {
1662- var client = try Client . init ( testing . allocator , 2 );
1695+ var client = try testClient ( );
16631696 defer client .deinit ();
16641697
16651698 var req = try client .request (.GET , "http://127.0.0.1:9582/http_client/echo" );
@@ -1674,8 +1707,84 @@ test "HttpClient: sync with body" {
16741707 try testing .expectEqual ("Close" , res .header .get ("_connection" ));
16751708}
16761709
1710+ test "HttpClient: sync tls with body" {
1711+ var arr : std .ArrayListUnmanaged (u8 ) = .{};
1712+ defer arr .deinit (testing .allocator );
1713+ try arr .ensureTotalCapacity (testing .allocator , 20 );
1714+
1715+ // https://github.com/ianic/tls.zig/issues/10
1716+ for (0.. 1) | _ | {
1717+ defer arr .clearRetainingCapacity ();
1718+ var client = try testClient ();
1719+ defer client .deinit ();
1720+
1721+ var req = try client .request (.GET , "https://127.0.0.1:9581/http_client/body" );
1722+ var res = try req .sendSync (.{});
1723+
1724+ while (try res .next ()) | data | {
1725+ arr .appendSliceAssumeCapacity (data );
1726+ }
1727+ try testing .expectEqual ("1234567890abcdefhijk" , arr .items );
1728+ try testing .expectEqual (201 , res .header .status );
1729+ try testing .expectEqual (2 , res .header .count ());
1730+ try testing .expectEqual ("20" , res .header .get ("content-length" ));
1731+ try testing .expectEqual ("HEaDer" , res .header .get ("another" ));
1732+ }
1733+ }
1734+
1735+ test "HttpClient: sync redirect from TLS to Plaintext" {
1736+ var arr : std .ArrayListUnmanaged (u8 ) = .{};
1737+ defer arr .deinit (testing .allocator );
1738+ try arr .ensureTotalCapacity (testing .allocator , 20 );
1739+
1740+ // https://github.com/ianic/tls.zig/issues/10
1741+ for (0.. 1) | _ | {
1742+ defer arr .clearRetainingCapacity ();
1743+ var client = try testClient ();
1744+ defer client .deinit ();
1745+
1746+ var req = try client .request (.GET , "https://127.0.0.1:9581/http_client/redirect/insecure" );
1747+ var res = try req .sendSync (.{});
1748+
1749+ while (try res .next ()) | data | {
1750+ arr .appendSliceAssumeCapacity (data );
1751+ }
1752+ try testing .expectEqual (201 , res .header .status );
1753+ try testing .expectEqual (4 , res .header .count ());
1754+ try testing .expectEqual ("close" , res .header .get ("connection" ));
1755+ try testing .expectEqual ("10" , res .header .get ("content-length" ));
1756+ try testing .expectEqual ("127.0.0.1" , res .header .get ("_host" ));
1757+ try testing .expectEqual ("Close" , res .header .get ("_connection" ));
1758+ }
1759+ }
1760+
1761+ test "HttpClient: sync redirect plaintext to TLS" {
1762+ var arr : std .ArrayListUnmanaged (u8 ) = .{};
1763+ defer arr .deinit (testing .allocator );
1764+ try arr .ensureTotalCapacity (testing .allocator , 20 );
1765+
1766+ // https://github.com/ianic/tls.zig/issues/10
1767+ for (0.. 1) | _ | {
1768+ defer arr .clearRetainingCapacity ();
1769+ var client = try testClient ();
1770+ defer client .deinit ();
1771+
1772+ var req = try client .request (.GET , "http://127.0.0.1:9582/http_client/redirect/secure" );
1773+ var res = try req .sendSync (.{});
1774+
1775+ while (try res .next ()) | data | {
1776+ arr .appendSliceAssumeCapacity (data );
1777+ }
1778+ try testing .expectEqual (201 , res .header .status );
1779+ try testing .expectEqual ("1234567890abcdefhijk" , arr .items );
1780+ try testing .expectEqual (2 , res .header .count ());
1781+ try testing .expectEqual ("20" , res .header .get ("content-length" ));
1782+ try testing .expectEqual ("HEaDer" , res .header .get ("another" ));
1783+ }
1784+ }
1785+
16771786test "HttpClient: sync GET redirect" {
1678- var client = try Client . init ( testing . allocator , 2 );
1787+ var client = try testClient ( );
16791788 defer client .deinit ();
16801789
16811790 var req = try client .request (.GET , "http://127.0.0.1:9582/http_client/redirect" );
@@ -1710,7 +1819,7 @@ test "HttpClient: async connect error" {
17101819 };
17111820
17121821 var reset : Thread.ResetEvent = .{};
1713- var client = try Client . init ( testing . allocator , 2 );
1822+ var client = try testClient ( );
17141823 defer client .deinit ();
17151824
17161825 var req = try client .request (.GET , "HTTP://127.0.0.1:9920" );
@@ -1720,7 +1829,7 @@ test "HttpClient: async connect error" {
17201829}
17211830
17221831test "HttpClient: async no body" {
1723- var client = try Client . init ( testing . allocator , 2 );
1832+ var client = try testClient ( );
17241833 defer client .deinit ();
17251834
17261835 var handler = try CaptureHandler .init ();
@@ -1731,26 +1840,44 @@ test "HttpClient: async no body" {
17311840
17321841 var req = try client .request (.GET , "HTTP://127.0.0.1:9582/http_client/simple" );
17331842 try req .sendAsync (& handler .loop , & handler , .{});
1734- try handler .loop .io .run_for_ns (std .time .ns_per_ms );
1735- try handler .reset .timedWait (std .time .ns_per_s );
1843+ try handler .waitUntilDone ();
17361844
17371845 const res = handler .response ;
17381846 try testing .expectEqual ("" , res .body .items );
17391847 try testing .expectEqual (200 , res .status );
17401848 try res .assertHeaders (&.{ "connection" , "close" , "content-length" , "0" });
17411849}
17421850
1851+ // test "HttpClient: async tls no body" {
1852+ // var client = try testClient();
1853+ // defer client.deinit();
1854+
1855+ // var handler = try CaptureHandler.init();
1856+ // defer handler.deinit();
1857+
1858+ // var loop = try jsruntime.Loop.init(testing.allocator);
1859+ // defer loop.deinit();
1860+
1861+ // var req = try client.request(.GET, "HTTPs://127.0.0.1:9581/http_client/simple");
1862+ // try req.sendAsync(&handler.loop, &handler, .{});
1863+ // try handler.waitUntilDone();
1864+
1865+ // const res = handler.response;
1866+ // try testing.expectEqual("", res.body.items);
1867+ // try testing.expectEqual(200, res.status);
1868+ // try res.assertHeaders(&.{ "connection", "close", "content-length", "0" });
1869+ // }
1870+
17431871test "HttpClient: async with body" {
1744- var client = try Client . init ( testing . allocator , 2 );
1872+ var client = try testClient ( );
17451873 defer client .deinit ();
17461874
17471875 var handler = try CaptureHandler .init ();
17481876 defer handler .deinit ();
17491877
17501878 var req = try client .request (.GET , "HTTP://127.0.0.1:9582/http_client/echo" );
17511879 try req .sendAsync (& handler .loop , & handler , .{});
1752- try handler .loop .io .run_for_ns (std .time .ns_per_ms );
1753- try handler .reset .timedWait (std .time .ns_per_s );
1880+ try handler .waitUntilDone ();
17541881
17551882 const res = handler .response ;
17561883 try testing .expectEqual ("over 9000!" , res .body .items );
@@ -1764,7 +1891,7 @@ test "HttpClient: async with body" {
17641891}
17651892
17661893test "HttpClient: async redirect" {
1767- var client = try Client . init ( testing . allocator , 2 );
1894+ var client = try testClient ( );
17681895 defer client .deinit ();
17691896
17701897 var handler = try CaptureHandler .init ();
@@ -1781,8 +1908,7 @@ test "HttpClient: async redirect" {
17811908 // start to requeue events (from the redirected request), so we need the
17821909 //loop to process those also.
17831910 try handler .loop .io .run_for_ns (std .time .ns_per_ms );
1784- try handler .loop .io .run_for_ns (std .time .ns_per_ms );
1785- try handler .reset .timedWait (std .time .ns_per_s );
1911+ try handler .waitUntilDone ();
17861912
17871913 const res = handler .response ;
17881914 try testing .expectEqual ("over 9000!" , res .body .items );
@@ -1885,6 +2011,11 @@ const CaptureHandler = struct {
18852011 self .reset .set ();
18862012 }
18872013 }
2014+
2015+ fn waitUntilDone (self : * CaptureHandler ) ! void {
2016+ try self .loop .io .run_for_ns (std .time .ns_per_ms );
2017+ try self .reset .timedWait (std .time .ns_per_s );
2018+ }
18882019};
18892020
18902021fn testReader (state : * State , res : * TestResponse , data : []const u8 ) ! void {
@@ -1928,3 +2059,9 @@ fn testReader(state: *State, res: *TestResponse, data: []const u8) !void {
19282059 }
19292060 return error .NeverDone ;
19302061}
2062+
2063+ fn testClient () ! Client {
2064+ const test_dir = try std .fs .cwd ().openDir ("tests" , .{});
2065+ const root_ca = try tls .config .CertBundle .fromFile (testing .allocator , test_dir , "test_cert.pem" );
2066+ return try Client .init (testing .allocator , 1 , root_ca );
2067+ }
0 commit comments