diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 23777ff..309f0c1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -34,7 +34,7 @@ jobs: make test - name: Run examples run: | - make run-examples + make run - name: Memory leak detect # Wait https://github.com/ziglang/zig/issues/15547 if: false @@ -61,4 +61,4 @@ jobs: version: master - name: Run examples run: | - make run-examples + make run diff --git a/Makefile b/Makefile index ae4867e..76c270a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -run-examples: +run: zig build run-basic -freference-trace zig build run-advanced -freference-trace @@ -9,4 +9,4 @@ test: docs: zig build-lib -femit-docs src/main.zig -.PHONY: test run-examples docs +.PHONY: test run docs diff --git a/build.zig b/build.zig index e54c530..11d4001 100644 --- a/build.zig +++ b/build.zig @@ -9,22 +9,23 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const module = b.addModule(MODULE_NAME, .{ - .source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/root.zig" }, }); - const libcurl = b.dependency("libcurl", .{ .target = target, .optimize = optimize }); - b.installArtifact(libcurl.artifact("curl")); + // FIXME: libcurl doesn't work with zig master yet. + // const libcurl = b.dependency("libcurl", .{ .target = target, .optimize = optimize }); + // b.installArtifact(libcurl.artifact("curl")); - try addExample(b, "basic", module, libcurl, target, optimize); - try addExample(b, "advanced", module, libcurl, target, optimize); + try addExample(b, "basic", module, target, optimize); + try addExample(b, "advanced", module, target, optimize); const main_tests = b.addTest(.{ - .root_source_file = .{ .path = "src/main.zig" }, + .root_source_file = .{ .path = "src/root.zig" }, .target = target, .optimize = optimize, }); - main_tests.addModule(MODULE_NAME, module); - main_tests.linkLibrary(libcurl.artifact("curl")); + main_tests.linkLibC(); + main_tests.linkSystemLibrary("curl"); const run_main_tests = b.addRunArtifact(main_tests); const test_step = b.step("test", "Run library tests"); @@ -35,8 +36,8 @@ fn addExample( b: *std.Build, comptime name: []const u8, curl_module: *Module, - libcurl: *Build.Dependency, - target: std.zig.CrossTarget, + // libcurl: *Build.Dependency, + target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, ) !void { const exe = b.addExecutable(.{ @@ -47,8 +48,10 @@ fn addExample( }); b.installArtifact(exe); - exe.addModule(MODULE_NAME, curl_module); - exe.linkLibrary(libcurl.artifact("curl")); + exe.root_module.addImport(MODULE_NAME, curl_module); + // exe.linkLibrary(libcurl.artifact("curl")); + exe.linkSystemLibrary("curl"); + exe.linkLibC(); const run_step = b.step("run-" ++ name, std.fmt.comptimePrint("Run {s} example", .{name})); run_step.dependOn(&b.addRunArtifact(exe).step); diff --git a/build.zig.zon b/build.zig.zon index a4a3cd2..326fc83 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,9 +8,9 @@ "LICENSE", }, .dependencies = .{ - .libcurl = .{ - .url = "https://github.com/Cloudef/zigcurl/archive/e9e726131d3eb80c02d20b7ee5ca6935324d6697.tar.gz", - .hash = "1220970ad7d9b96b3464fc12c1e67998881bc4d07c03b8b7d75a5367e4c9991ef099", - }, + // .libcurl = .{ + // .url = "https://github.com/star-tek-mb/zigcurl/archive/41379c8.tar.gz", + // .hash = "1220424bef91a3ffe97980b7a45062a2c1b26c3bbcefbda301419ad14c3641abcf57", + // }, }, } diff --git a/examples/advanced.zig b/examples/advanced.zig index 03ce49d..c958e74 100644 --- a/examples/advanced.zig +++ b/examples/advanced.zig @@ -7,7 +7,7 @@ const Easy = curl.Easy; const UA = "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0"; -const Resposne = struct { +const Response = struct { headers: struct { @"User-Agent": []const u8, Authorization: []const u8, @@ -21,27 +21,27 @@ const Resposne = struct { }; fn put_with_custom_header(allocator: Allocator, easy: Easy) !void { - var stream = std.io.fixedBufferStream( + const body = \\ {"name": "John", "age": 15} - ); - const body = stream.reader(); + ; - const header = blk: { - var h = curl.RequestHeader.init(allocator); + const headers = blk: { + var h = try easy.create_headers(); errdefer h.deinit(); - try h.add(curl.HEADER_CONTENT_TYPE, "application/json"); + try h.add("content-type", "application/json"); try h.add("user-agent", UA); try h.add("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l"); break :blk h; }; - var req = curl.Request(@TypeOf(body)).init("https://httpbin.org/anything/zig-curl", body, .{ - .method = .PUT, - .header = header, - .verbose = true, - }); - defer req.deinit(); + defer headers.deinit(); + + try easy.set_url("https://httpbin.org/anything/zig-curl"); + try easy.set_headers(headers); + try easy.set_method(.PUT); + try easy.set_verbose(true); + try easy.set_post_fields(body); - const resp = try easy.do(req); + const resp = try easy.perform(); defer resp.deinit(); std.debug.print("Status code: {d}\nBody: {s}\n", .{ @@ -49,14 +49,14 @@ fn put_with_custom_header(allocator: Allocator, easy: Easy) !void { resp.body.items, }); - const parsed = try std.json.parseFromSlice(Resposne, allocator, resp.body.items, .{ + const parsed = try std.json.parseFromSlice(Response, allocator, resp.body.items, .{ .ignore_unknown_fields = true, }); defer parsed.deinit(); try std.testing.expectEqualDeep( parsed.value, - .{ + Response{ .headers = .{ .@"User-Agent" = UA, .Authorization = "Basic YWxhZGRpbjpvcGVuc2VzYW1l", @@ -83,20 +83,22 @@ fn put_with_custom_header(allocator: Allocator, easy: Easy) !void { } fn post_mutli_part(easy: Easy) !void { - const multi_part = try easy.add_multi_part(); + // Reset old options, e.g. headers. + easy.reset(); + + const multi_part = try easy.create_multi_part(); try multi_part.add_part("foo", .{ .data = "hello foo" }); try multi_part.add_part("bar", .{ .data = "hello bar" }); try multi_part.add_part("build.zig", .{ .file = "build.zig" }); try multi_part.add_part("readme", .{ .file = "README.org" }); + defer multi_part.deinit(); - var req = curl.Request(void).init("https://httpbin.org/anything/mp", {}, .{ - .method = .PUT, - .multi_part = multi_part, - .verbose = true, - }); - defer req.deinit(); + try easy.set_url("https://httpbin.org/anything/mp"); + try easy.set_method(.PUT); + try easy.set_multi_part(multi_part); + try easy.set_verbose(true); - const resp = try easy.do(req); + const resp = try easy.perform(); defer resp.deinit(); std.debug.print("resp:{s}\n", .{resp.body.items}); diff --git a/examples/basic.zig b/examples/basic.zig index 5c98168..63fb239 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -5,7 +5,7 @@ const Allocator = mem.Allocator; const curl = @import("curl"); const Easy = curl.Easy; -fn get(easy: Easy) !void { +fn get(allocator: Allocator, easy: Easy) !void { const resp = try easy.get("https://httpbin.org/anything"); defer resp.deinit(); @@ -13,19 +13,54 @@ fn get(easy: Easy) !void { resp.status_code, resp.body.items, }); + + const Response = struct { + headers: struct { + Host: []const u8, + }, + method: []const u8, + }; + const parsed = try std.json.parseFromSlice(Response, allocator, resp.body.items, .{ + .ignore_unknown_fields = true, + }); + defer parsed.deinit(); + + try std.testing.expectEqualDeep(parsed.value, Response{ + .headers = .{ .Host = "httpbin.org" }, + .method = "GET", + }); } -fn post(easy: Easy) !void { - var payload = std.io.fixedBufferStream( +fn post(allocator: Allocator, easy: Easy) !void { + const payload = \\{"name": "John", "age": 15} - ); - const resp = try easy.post("https://httpbin.org/anything", "application/json", payload.reader()); + ; + const resp = try easy.post("https://httpbin.org/anything", "application/json", payload); defer resp.deinit(); std.debug.print("Status code: {d}\nBody: {s}\n", .{ resp.status_code, resp.body.items, }); + + const Response = struct { + headers: struct { + @"Content-Type": []const u8, + }, + json: struct { + name: []const u8, + age: u32, + }, + method: []const u8, + }; + const parsed = try std.json.parseFromSlice(Response, allocator, resp.body.items, .{ .ignore_unknown_fields = true }); + defer parsed.deinit(); + + try std.testing.expectEqualDeep(parsed.value, Response{ + .headers = .{ .@"Content-Type" = "application/json" }, + .json = .{ .name = "John", .age = 15 }, + .method = "POST", + }); } pub fn main() !void { @@ -37,8 +72,8 @@ pub fn main() !void { defer easy.deinit(); println("GET demo"); - try get(easy); + try get(allocator, easy); println("POST demo"); - try post(easy); + try post(allocator, easy); } diff --git a/src/c.zig b/src/c.zig index 2d509cf..22b55e6 100644 --- a/src/c.zig +++ b/src/c.zig @@ -56,7 +56,7 @@ comptime { } } -pub fn url_encode(string: []const u8) ?[]const u8 { +pub fn url_encode(string: [:0]const u8) ?[]const u8 { const r = c.curl_easy_escape(null, string.ptr, @intCast(string.len)); return std.mem.sliceTo(r.?, 0); } diff --git a/src/easy.zig b/src/easy.zig index 8bd2c4e..eea5a76 100644 --- a/src/easy.zig +++ b/src/easy.zig @@ -14,13 +14,9 @@ const Self = @This(); allocator: Allocator, handle: *c.CURL, -/// The maximum time in milliseconds that the entire transfer operation to take. -timeout_ms: usize = 30_000, -default_user_agent: []const u8 = "zig-curl/0.1.0", +timeout_ms: usize, ca_bundle: ?[]const u8, -pub const HEADER_CONTENT_TYPE: []const u8 = "Content-Type"; -pub const HEADER_USER_AGENT: []const u8 = "User-Agent"; const CERT_MARKER_BEGIN = "-----BEGIN CERTIFICATE-----"; const CERT_MARKER_END = "\n-----END CERTIFICATE-----\n"; @@ -32,113 +28,34 @@ pub const Method = enum { PATCH, DELETE, - fn asString(self: @This()) [:0]const u8 { + fn asString(self: Method) [:0]const u8 { return @tagName(self); } }; -pub const RequestHeader = struct { - entries: std.StringHashMap([]const u8), +pub const Headers = struct { + headers: ?*c.struct_curl_slist, allocator: Allocator, - pub fn init(allocator: Allocator) @This() { + pub fn init(allocator: Allocator) !Headers { return .{ - .entries = std.StringHashMap([]const u8).init(allocator), .allocator = allocator, + .headers = null, }; } - pub fn deinit(self: *@This()) void { - self.entries.deinit(); + pub fn deinit(self: Headers) void { + c.curl_slist_free_all(self.headers); } - pub fn add(self: *@This(), k: []const u8, v: []const u8) !void { - try self.entries.put(k, v); - } - - // Note: Caller should free returned list (after usage) with `freeCHeader`. - fn asCHeader(self: @This(), ua: []const u8) !?*c.struct_curl_slist { - if (self.entries.count() == 0) { - return null; - } - - var lst: ?*c.struct_curl_slist = null; - var it = self.entries.iterator(); - var has_ua = false; - while (it.next()) |entry| { - if (!has_ua and std.ascii.eqlIgnoreCase(entry.key_ptr.*, HEADER_USER_AGENT)) { - has_ua = true; - } - - const kv = try fmt.allocPrintZ(self.allocator, "{s}: {s}", .{ entry.key_ptr.*, entry.value_ptr.* }); - defer self.allocator.free(kv); - - lst = c.curl_slist_append(lst, kv); - } - if (!has_ua) { - const kv = try fmt.allocPrintZ(self.allocator, "{s}: {s}", .{ HEADER_USER_AGENT, ua }); - defer self.allocator.free(kv); - - lst = c.curl_slist_append(lst, kv); - } + pub fn add(self: *Headers, name: []const u8, value: []const u8) !void { + const header = try std.fmt.allocPrintZ(self.allocator, "{s}: {s}", .{ name, value }); + defer self.allocator.free(header); - return lst; - } - - fn freeCHeader(lst: *c.struct_curl_slist) void { - c.curl_slist_free_all(lst); + self.headers = c.curl_slist_append(self.headers, header); } }; -pub const RequestArgs = struct { - method: Method = .GET, - header: ?RequestHeader = null, - multi_part: ?MultiPart = null, - verbose: bool = false, - /// Redirection limit, 0 refuse any redirect, -1 for an infinite number of redirects. - redirects: i32 = 10, - /// Max body size, default 128M. - max_body_size: usize = 128 * 1024 * 1024, -}; - -pub fn Request(comptime ReaderType: type) type { - return struct { - url: []const u8, - /// body is io.Reader or void - body: ReaderType, - args: RequestArgs, - - pub fn init(url: []const u8, body: ReaderType, args: RequestArgs) @This() { - return .{ - .url = url, - .body = body, - .args = args, - }; - } - - pub fn deinit(self: *@This()) void { - if (self.args.header) |*h| { - h.deinit(); - } - if (self.args.multi_part) |mp| { - mp.deinit(); - } - } - - fn getVerbose(self: @This()) c_long { - return if (self.args.verbose) 1 else 0; - } - - fn getBody(self: @This(), allocator: Allocator) !?[]u8 { - if (@TypeOf(self.body) == void) { - return null; - } - - return try self.body.readAllAlloc(allocator, self.args.max_body_size); - } - }; -} - pub const Buffer = std.ArrayList(u8); pub const Response = struct { body: Buffer, @@ -157,20 +74,17 @@ pub const Response = struct { /// Get the first value associated with the given key. /// Applications need to copy the data if it wants to keep it around. - pub fn get(self: @This()) []const u8 { + pub fn get(self: Header) []const u8 { return mem.sliceTo(self.c_header.value, 0); } }; /// Gets the header associated with the given name. - pub fn get_header(self: @This(), name: []const u8) errors.HeaderError!?Header { + pub fn get_header(self: Response, name: [:0]const u8) errors.HeaderError!?Header { if (comptime !has_parse_header_support()) { return error.NoCurlHeaderSupport; } - const c_name = try fmt.allocPrintZ(self.allocator, "{s}", .{name}); - defer self.allocator.free(c_name); - var header: ?*c.struct_curl_header = null; const code = c.curl_easy_header(self.handle, name.ptr, 0, c.CURLH_HEADER, -1, &header); return if (errors.headerErrorFrom(code)) |err| @@ -179,7 +93,7 @@ pub const Response = struct { else => err, } else - Header{ + .{ .c_header = header.?, .name = name, }; @@ -196,31 +110,25 @@ pub const MultiPart = struct { /// Setting large data is memory consuming: one might consider using `data_callback` in such a case. data: []const u8, /// Set a mime part's body data from a file contents. - file: []const u8, + file: [:0]const u8, // TODO: https://curl.se/libcurl/c/curl_mime_data_cb.html // data_callback: u8, }; - pub fn deinit(self: @This()) void { + pub fn deinit(self: MultiPart) void { c.curl_mime_free(self.mime_handle); } - pub fn add_part(self: @This(), name: []const u8, source: DataSource) !void { + pub fn add_part(self: MultiPart, name: [:0]const u8, source: DataSource) !void { const part = if (c.curl_mime_addpart(self.mime_handle)) |part| part else return error.MimeAddPart; - const namez = try fmt.allocPrintZ(self.allocator, "{s}", .{name}); - defer self.allocator.free(namez); - - try checkCode(c.curl_mime_name(part, namez)); + try checkCode(c.curl_mime_name(part, name)); switch (source) { .data => |slice| { try checkCode(c.curl_mime_data(part, slice.ptr, slice.len)); }, .file => |filepath| { - const filepathz = try std.fmt.allocPrintZ(self.allocator, "{s}", .{filepath}); - defer self.allocator.free(filepathz); - - try checkCode(c.curl_mime_filedata(part, filepathz)); + try checkCode(c.curl_mime_filedata(part, filepath)); }, } } @@ -231,6 +139,8 @@ pub const EasyOptions = struct { /// Use zig's std.crypto.Certificate.Bundle for TLS instead of libcurl's default. /// Note that the builtin libcurl is compiled with mbedtls and does not include a CA bundle. use_std_crypto_ca_bundle: bool = true, + /// The maximum time in milliseconds that the entire transfer operation to take. + default_timeout_ms: usize = 30_000, }; pub fn init(allocator: Allocator, options: EasyOptions) !Self { @@ -240,7 +150,6 @@ pub fn init(allocator: Allocator, options: EasyOptions) !Self { defer bundle.deinit(allocator); try bundle.rescan(allocator); - var blob = std.ArrayList(u8).init(allocator); var iter = bundle.map.iterator(); while (iter.next()) |entry| { @@ -263,10 +172,11 @@ pub fn init(allocator: Allocator, options: EasyOptions) !Self { } }; - return if (c.curl_easy_init()) |h| + return if (c.curl_easy_init()) |handle| .{ .allocator = allocator, - .handle = h, + .handle = handle, + .timeout_ms = options.default_timeout_ms, .ca_bundle = ca_bundle, } else @@ -281,7 +191,11 @@ pub fn deinit(self: Self) void { c.curl_easy_cleanup(self.handle); } -pub fn add_multi_part(self: Self) !MultiPart { +pub fn create_headers(self: Self) !Headers { + return Headers.init(self.allocator); +} + +pub fn create_multi_part(self: Self) !MultiPart { return if (c.curl_mime_init(self.handle)) |h| .{ .allocator = self.allocator, @@ -291,49 +205,44 @@ pub fn add_multi_part(self: Self) !MultiPart { error.MimeInit; } -/// Do sends an HTTP request and returns an HTTP response. -pub fn do(self: Self, req: anytype) !Response { - // Re-initializes all options previously set on a specified CURL handle to the default values. - defer c.curl_easy_reset(self.handle); - - try self.set_common_opts(); - const url = try fmt.allocPrintZ(self.allocator, "{s}", .{req.url}); - defer self.allocator.free(url); +pub fn set_url(self: Self, url: [:0]const u8) !void { try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_URL, url.ptr)); - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_MAXREDIRS, @as(c_long, req.args.redirects))); - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_CUSTOMREQUEST, req.args.method.asString().ptr)); - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_VERBOSE, req.getVerbose())); +} - if (self.ca_bundle) |bundle| { - const blob = c.curl_blob{ - .data = @constCast(bundle.ptr), - .len = bundle.len, - .flags = c.CURL_BLOB_NOCOPY, - }; - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_CAINFO_BLOB, blob)); - } +pub fn set_max_redirects(self: Self, redirects: u32) !void { + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_MAXREDIRS, @as(c_long, redirects))); +} - const body = try req.getBody(self.allocator); - defer if (body) |b| { - self.allocator.free(b); - }; - if (body) |b| { - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_POSTFIELDS, b.ptr)); - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_POSTFIELDSIZE, b.len)); - } +pub fn set_method(self: Self, method: Method) !void { + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_CUSTOMREQUEST, method.asString().ptr)); +} - var mime_handle: ?*c.curl_mime = null; - if (req.args.multi_part) |mp| { - mime_handle = mp.mime_handle; - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_MIMEPOST, mime_handle)); - } +pub fn set_verbose(self: Self, verbose: bool) !void { + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_VERBOSE, verbose)); +} + +pub fn set_post_fields(self: Self, body: []const u8) !void { + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_POSTFIELDS, body.ptr)); + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_POSTFIELDSIZE, body.len)); +} - var header: ?*c.struct_curl_slist = null; - if (req.args.header) |h| { - header = try h.asCHeader(self.default_user_agent); +pub fn set_multi_part(self: Self, multi_part: MultiPart) !void { + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_MIMEPOST, multi_part.mime_handle)); +} + +pub fn reset(self: Self) void { + c.curl_easy_reset(self.handle); +} + +pub fn set_headers(self: Self, headers: Headers) !void { + if (headers.headers) |c_headers| { + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_HTTPHEADER, c_headers)); } - defer if (header) |h| RequestHeader.freeCHeader(h); - try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_HTTPHEADER, header)); +} + +/// Perform sends an HTTP request and returns an HTTP response. +pub fn perform(self: Self) !Response { + try self.set_common_opts(); var resp_buffer = Buffer.init(self.allocator); errdefer resp_buffer.deinit(); @@ -346,7 +255,7 @@ pub fn do(self: Self, req: anytype) !Response { try checkCode(c.curl_easy_getinfo(self.handle, c.CURLINFO_RESPONSE_CODE, &status_code)); return .{ - .status_code = @truncate(status_code), + .status_code = @intCast(status_code), .body = resp_buffer, .handle = self.handle, .allocator = self.allocator, @@ -354,36 +263,31 @@ pub fn do(self: Self, req: anytype) !Response { } /// Get issues a GET to the specified URL. -pub fn get(self: Self, url: []const u8) !Response { - var req = Request(void).init(url, {}, .{}); - defer req.deinit(); - - return self.do(req); +pub fn get(self: Self, url: [:0]const u8) !Response { + try self.set_url(url); + return self.perform(); } /// Head issues a HEAD to the specified URL. -pub fn head(self: Self, url: []const u8) !Response { - var req = Request(void).init(url, {}, .{ .method = .HEAD }); - defer req.deinit(); +pub fn head(self: Self, url: [:0]const u8) !Response { + try self.set_url(url); + try self.set_method(.HEAD); - return self.do(req); + return self.perform(); } -/// Post issues a POST to the specified URL. -pub fn post(self: Self, url: []const u8, content_type: []const u8, body: anytype) !Response { - const header = blk: { - var h = RequestHeader.init(self.allocator); - errdefer h.deinit(); - try h.add(HEADER_CONTENT_TYPE, content_type); - break :blk h; - }; - var req = Request(@TypeOf(body)).init(url, body, .{ - .method = .POST, - .header = header, - }); - defer req.deinit(); +// /// Post issues a POST to the specified URL. +pub fn post(self: Self, url: [:0]const u8, content_type: []const u8, body: []const u8) !Response { + try self.set_url(url); + try self.set_post_fields(body); + + var headers = try self.create_headers(); + defer headers.deinit(); - return self.do(req); + try headers.add("Content-Type", content_type); + + try self.set_headers(headers); + return self.perform(); } /// Used for https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html @@ -399,6 +303,14 @@ fn write_callback(ptr: [*c]c_char, size: c_uint, nmemb: c_uint, user_data: *anyo } fn set_common_opts(self: Self) !void { + if (self.ca_bundle) |bundle| { + const blob = c.curl_blob{ + .data = @constCast(bundle.ptr), + .len = bundle.len, + .flags = c.CURL_BLOB_NOCOPY, + }; + try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_CAINFO_BLOB, blob)); + } try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_TIMEOUT_MS, self.timeout_ms)); } diff --git a/src/main.zig b/src/root.zig similarity index 100% rename from src/main.zig rename to src/root.zig diff --git a/src/util.zig b/src/util.zig index b84436d..0939427 100644 --- a/src/util.zig +++ b/src/util.zig @@ -1,6 +1,6 @@ const std = @import("std"); +const c = @import("c.zig").c; const Allocator = std.mem.Allocator; - const Encoder = std.base64.standard.Encoder; pub fn encode_base64(allocator: Allocator, input: []const u8) ![]const u8 {