From b6a03c8634246596b641b39a8c9c190100aa2437 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Wed, 5 Jan 2022 10:24:14 -0800 Subject: [PATCH] enable uploads --- .gitignore | 2 + GitRepoStep.zig | 206 +++++++++++++++++++++++++++++++++++++++++++ build.zig | 67 +++++++++++++- src/clipboard.zig | 122 +++++++++++++++++++++---- src/crypt.zig | 171 +++++++++++++++++++++++++++++++++++ src/main-linux.zig | 33 ++++--- src/main-windows.zig | 15 +++- zfetch_deps.zig | 1 + 8 files changed, 586 insertions(+), 31 deletions(-) create mode 100644 GitRepoStep.zig create mode 100644 src/crypt.zig create mode 100644 zfetch_deps.zig diff --git a/.gitignore b/.gitignore index 5aee06e..8f53324 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ zig-out libx11-libX11-1.7.2 libxfixes-libXfixes-5.0.3 xorgproto-xorgproto-2021.5 +.clippy +libs/ diff --git a/GitRepoStep.zig b/GitRepoStep.zig new file mode 100644 index 0000000..1a83e2e --- /dev/null +++ b/GitRepoStep.zig @@ -0,0 +1,206 @@ +//! Publish Date: 2021_10_17 +//! This file is hosted at github.com/marler8997/zig-build-repos and is meant to be copied +//! to projects that use it. +const std = @import("std"); +const GitRepoStep = @This(); + +pub const ShaCheck = enum { + none, + warn, + err, + + pub fn reportFail(self: ShaCheck, comptime fmt: []const u8, args: anytype) void { + switch (self) { + .none => unreachable, + .warn => std.log.warn(fmt, args), + .err => { + std.log.err(fmt, args); + std.os.exit(0xff); + }, + } + } +}; + +step: std.build.Step, +builder: *std.build.Builder, +url: []const u8, +name: []const u8, +branch: ?[]const u8 = null, +sha: []const u8, +path: []const u8 = null, +sha_check: ShaCheck = .warn, +fetch_enabled: bool, + +var cached_default_fetch_option: ?bool = null; +pub fn defaultFetchOption(b: *std.build.Builder) bool { + if (cached_default_fetch_option) |_| {} else { + cached_default_fetch_option = if (b.option(bool, "fetch", "automatically fetch network resources")) |o| o else false; + } + return cached_default_fetch_option.?; +} + +pub fn create(b: *std.build.Builder, opt: struct { + url: []const u8, + branch: ?[]const u8 = null, + sha: []const u8, + path: ?[]const u8 = null, + sha_check: ShaCheck = .warn, + fetch_enabled: ?bool = null, +}) *GitRepoStep { + var result = b.allocator.create(GitRepoStep) catch @panic("memory"); + const name = std.fs.path.basename(opt.url); + result.* = GitRepoStep{ + .step = std.build.Step.init(.custom, "clone a git repository", b.allocator, make), + .builder = b, + .url = opt.url, + .name = name, + .branch = opt.branch, + .sha = opt.sha, + .path = if (opt.path) |p| (b.allocator.dupe(u8, p) catch @panic("memory")) else (std.fs.path.resolve(b.allocator, &[_][]const u8{ + b.build_root, + "libs", + name, + })) catch @panic("memory"), + .sha_check = opt.sha_check, + .fetch_enabled = if (opt.fetch_enabled) |fe| fe else defaultFetchOption(b), + }; + return result; +} + +// TODO: this should be included in std.build, it helps find bugs in build files +fn hasDependency(step: *const std.build.Step, dep_candidate: *const std.build.Step) bool { + for (step.dependencies.items) |dep| { + // TODO: should probably use step.loop_flag to prevent infinite recursion + // when a circular reference is encountered, or maybe keep track of + // the steps encounterd with a hash set + if (dep == dep_candidate or hasDependency(dep, dep_candidate)) + return true; + } + return false; +} + +fn make(step: *std.build.Step) !void { + const self = @fieldParentPtr(GitRepoStep, "step", step); + + std.fs.accessAbsolute(self.path, std.fs.File.OpenFlags{ .read = true }) catch { + const branch_args = if (self.branch) |b| &[2][]const u8{ " -b ", b } else &[2][]const u8{ "", "" }; + if (!self.fetch_enabled) { + std.debug.print("Error: git repository '{s}' does not exist\n", .{self.path}); + std.debug.print(" Use -Dfetch to download it automatically, or run the following to clone it:\n", .{}); + std.debug.print(" git clone {s}{s}{s} {s} && git -C {3s} checkout {s} -b for_ziget\n", .{ self.url, branch_args[0], branch_args[1], self.path, self.sha }); + std.os.exit(1); + } + + { + var args = std.ArrayList([]const u8).init(self.builder.allocator); + defer args.deinit(); + try args.append("git"); + try args.append("clone"); + try args.append("--recurse-submodules"); + try args.append(self.url); + // TODO: clone it to a temporary location in case of failure + // also, remove that temporary location before running + try args.append(self.path); + if (self.branch) |branch| { + try args.append("-b"); + try args.append(branch); + } + try run(self.builder, args.items); + } + try run(self.builder, &[_][]const u8{ + "git", + "-C", + self.path, + "checkout", + self.sha, + "-b", + "fordep", + }); + }; + + try self.checkSha(); +} + +fn checkSha(self: GitRepoStep) !void { + if (self.sha_check == .none) + return; + + const result: union(enum) { failed: anyerror, output: []const u8 } = blk: { + const result = std.ChildProcess.exec(.{ + .allocator = self.builder.allocator, + .argv = &[_][]const u8{ + "git", + "-C", + self.path, + "rev-parse", + "HEAD", + }, + .cwd = self.builder.build_root, + .env_map = self.builder.env_map, + }) catch |e| break :blk .{ .failed = e }; + try std.io.getStdErr().writer().writeAll(result.stderr); + switch (result.term) { + .Exited => |code| { + if (code == 0) break :blk .{ .output = result.stdout }; + break :blk .{ .failed = error.GitProcessNonZeroExit }; + }, + .Signal => break :blk .{ .failed = error.GitProcessFailedWithSignal }, + .Stopped => break :blk .{ .failed = error.GitProcessWasStopped }, + .Unknown => break :blk .{ .failed = error.GitProcessFailed }, + } + }; + switch (result) { + .failed => |err| { + return self.sha_check.reportFail("failed to retreive sha for repository '{s}': {s}", .{ self.name, @errorName(err) }); + }, + .output => |output| { + if (!std.mem.eql(u8, std.mem.trimRight(u8, output, "\n\r"), self.sha)) { + return self.sha_check.reportFail("repository '{s}' sha does not match\nexpected: {s}\nactual : {s}\n", .{ self.name, self.sha, output }); + } + }, + } +} + +fn run(builder: *std.build.Builder, argv: []const []const u8) !void { + { + var msg = std.ArrayList(u8).init(builder.allocator); + defer msg.deinit(); + const writer = msg.writer(); + var prefix: []const u8 = ""; + for (argv) |arg| { + try writer.print("{s}\"{s}\"", .{ prefix, arg }); + prefix = " "; + } + std.log.info("[RUN] {s}", .{msg.items}); + } + + const child = try std.ChildProcess.init(argv, builder.allocator); + defer child.deinit(); + + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + child.cwd = builder.build_root; + child.env_map = builder.env_map; + + try child.spawn(); + const result = try child.wait(); + switch (result) { + .Exited => |code| if (code != 0) { + std.log.err("git clone failed with exit code {}", .{code}); + std.os.exit(0xff); + }, + else => { + std.log.err("git clone failed with: {}", .{result}); + std.os.exit(0xff); + }, + } +} + +// Get's the repository path and also verifies that the step requesting the path +// is dependent on this step. +pub fn getPath(self: *const GitRepoStep, who_wants_to_know: *const std.build.Step) []const u8 { + if (!hasDependency(who_wants_to_know, &self.step)) + @panic("a step called GitRepoStep.getPath but has not added it as a dependency"); + return self.path; +} diff --git a/build.zig b/build.zig index 503db85..ddef92c 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,12 @@ const std = @import("std"); +const GitRepoStep = @import("GitRepoStep.zig"); pub fn build(b: *std.build.Builder) void { + const zfetch_repo = GitRepoStep.create(b, .{ + .url = "https://github.com/truemedian/zfetch", + // .branch = "0.1.10", // branch also takes tags. Tag 0.1.10 isn't quite new enough + .sha = "271cab5da4d12c8f08e67aa0cd5268da100e52f1", + }); // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options @@ -12,8 +18,8 @@ pub fn build(b: *std.build.Builder) void { const mode = b.standardReleaseOptions(); const exe = switch (target.getOs().tag) { - .linux => b.addExecutable("clipboard", "src/main-linux.zig"), - .windows => b.addExecutable("clipboard", "src/main-windows.zig"), + .linux => b.addExecutable("clipboard-upload", "src/main-linux.zig"), + .windows => b.addExecutable("clipboard-upload", "src/main-windows.zig"), else => std.os.exit(1), }; @@ -48,6 +54,28 @@ pub fn build(b: *std.build.Builder) void { exe.linkSystemLibrary("kernel32"); exe.linkSystemLibrary("shell32"); } + const copy_deps = std.build.RunStep.create(b, "copy zfetch deps"); + copy_deps.addArgs(&[_][]const u8{ + "cp", + "zfetch_deps.zig", + "libs/zfetch/deps.zig", + }); + copy_deps.step.dependOn(&zfetch_repo.step); + + // exe.step.dependOn(&zfetch_repo.step); + exe.step.dependOn(©_deps.step); + + // This import won't work unless we're already cloned. The way around + // this is to have a multi-stage build process, but that's a lot of work. + // Instead, I've copied the addPackage and tweaked it for the build prefix + // so we'll have to keep that in sync with upstream + // const zfetch = @import("libs/zfetch/build.zig"); + const zpkg = getZfetchPackage(b, "libs/zfetch") catch unreachable; + exe.addPackage(zpkg); + exe.addPackage(.{ + .name = "iguanaTLS", + .path = .{ .path = "libs/zfetch/libs/iguanaTLS/src/main.zig" }, + }); exe.install(); const run_cmd = exe.run(); @@ -59,3 +87,38 @@ pub fn build(b: *std.build.Builder) void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); } + +fn getDependency(comptime lib_prefix: []const u8, comptime name: []const u8, comptime root: []const u8) !std.build.Pkg { + const path = lib_prefix ++ "/libs/" ++ name ++ "/" ++ root; + + // We don't actually care if the dependency has been checked out, as + // GitRepoStep will handle that for us + // Make sure that the dependency has been checked out. + // std.fs.cwd().access(path, .{}) catch |err| switch (err) { + // error.FileNotFound => { + // std.log.err("zfetch: dependency '{s}' not checked out", .{name}); + // + // return err; + // }, + // else => return err, + // }; + + return std.build.Pkg{ + .name = name, + .path = .{ .path = path }, + }; +} +pub fn getZfetchPackage(b: *std.build.Builder, comptime lib_prefix: []const u8) !std.build.Pkg { + var dependencies = b.allocator.alloc(std.build.Pkg, 4) catch unreachable; + + dependencies[0] = try getDependency(lib_prefix, "iguanaTLS", "src/main.zig"); + dependencies[1] = try getDependency(lib_prefix, "network", "network.zig"); + dependencies[2] = try getDependency(lib_prefix, "uri", "uri.zig"); + dependencies[3] = try getDependency(lib_prefix, "hzzp", "src/main.zig"); + + return std.build.Pkg{ + .name = "zfetch", + .path = .{ .path = lib_prefix ++ "/src/main.zig" }, + .dependencies = dependencies, + }; +} diff --git a/src/clipboard.zig b/src/clipboard.zig index 59cc937..1e6b2d4 100644 --- a/src/clipboard.zig +++ b/src/clipboard.zig @@ -1,28 +1,120 @@ const std = @import("std"); +const zfetch = @import("zfetch"); +const crypt = @import("crypt.zig"); +// const tls = @import("iguanaTLS"); -pub fn clipboardChanged(allocator: std.mem.Allocator, contents: []const u8) !void { - var arena_allocator = std.heap.ArenaAllocator.init(allocator); +// NGINX config isn't allowing ECDHE-RSA-CHACHA20-POLY1305 on TLS 1.2 +// I need: +// +// nginx to allow that +// iguanaTLS to support tls 1.3 +// iguanaTLS to support something else, like ECDHE-ECDSA-CHACHA20-POLY1305 +// In the meantime, I've allowed use of http, since we're encrypting anyway +const clipboard_url = "http://clippy.lerch.org/work2"; +// const clipboard_url = "https://httpbin.org/post"; + +const Self = @This(); + +key: *[crypt.key_size]u8, +allocator: std.mem.Allocator, + +pub fn init(allocator: std.mem.Allocator) !Self { + const key = try getKey(allocator); + return Self{ + .allocator = allocator, + .key = key, + }; +} + +pub fn deinit(self: *Self) void { + self.allocator.free(self.key); +} + +pub fn clipboardChanged(self: *Self, contents: []const u8) !void { + var arena_allocator = std.heap.ArenaAllocator.init(self.allocator); defer arena_allocator.deinit(); const aa = arena_allocator.allocator(); const clip_contents = try aa.dupe(u8, contents); defer aa.free(clip_contents); - // Dear lord, change this - const key = [_]u8{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + var buf: []u8 = try aa.alloc(u8, contents.len); + defer aa.free(buf); + std.mem.copy(u8, buf, contents); + const encrypted = crypt.encryptWithKey(aa, self.key.*, buf) catch |e| { + std.log.err("Could not encrypt clipboard contents: {}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return; }; - const in = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; - var out: [16]u8 = undefined; - var ctx = std.crypto.core.aes.Aes256.initEnc(key); - ctx.encrypt(out[0..], in[0..]); - // Do the work. We are assumed here to - const stdout = std.io.getStdOut().writer(); - const writer = stdout; - try writer.print("{s}", .{clip_contents}); + defer aa.free(encrypted); + post(aa, encrypted) catch |e| { + std.log.err("error posting clipboard contents {}", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return; + }; +} + +fn getKey(allocator: std.mem.Allocator) !*[crypt.key_size]u8 { + const passfile = std.fs.cwd().openFile(".clippy", .{}) catch |e| { + if (e == error.FileNotFound) { + std.log.err("Could not find '.clippy' file. Please add a password to this file", .{}); + } + return e; + }; + defer passfile.close(); + const pass = try passfile.readToEndAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(pass); + + const tmp_key = try crypt.keyFromPassword(allocator, pass, ""); // reuse key - this is slow + return tmp_key; +} + +fn post(allocator: std.mem.Allocator, data: []const u8) !void { + // TODO: Windows + // var cert_reader = std.io.fixedBufferStream( + // @embedFile("/etc/ssl/certs/ca-certificates.crt"), + // ).reader(); + // const trust = try tls.x509.CertificateChain.from_pem(allocator, cert_reader); + try zfetch.init(); + defer zfetch.deinit(); + + var headers = zfetch.Headers.init(allocator); + defer headers.deinit(); + + // try headers.appendValue("Accept", "application/json"); + // try headers.appendValue("Content-Type", "text/plain"); + + var req = try zfetch.Request.init(allocator, clipboard_url, null); + defer req.deinit(); + + try req.do(.PUT, headers, data); + + // Printf debugging + // const stdout = std.io.getStdOut().writer(); + // try stdout.print("status: {d} {s}\n", .{ req.status.code, req.status.reason }); + // try stdout.print("headers:\n", .{}); + // for (req.headers.list.items) |header| { + // try stdout.print(" {s}: {s}\n", .{ header.name, header.value }); + // } + // try stdout.print("body:\n", .{}); + // + // const reader = req.reader(); + // + // var buf: [1024]u8 = undefined; + // while (true) { + // const read = try reader.read(&buf); + // if (read == 0) break; + // + // try stdout.writeAll(buf[0..read]); + // } } test "full integration" { var allocator = std.testing.allocator; - try clipboardChanged(allocator, "hello world"); + var watcher = init(allocator); + defer watcher.deinit(); + try watcher.clipboardChanged("hello world"); } diff --git a/src/crypt.zig b/src/crypt.zig new file mode 100644 index 0000000..950f2a2 --- /dev/null +++ b/src/crypt.zig @@ -0,0 +1,171 @@ +const std = @import("std"); + +pub const key_size = 256 / 8; + +const test_key = [_]u8{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +}; + +/// Get key from password. The symmetric key will be derived using PBKDF2 +/// Salt can be anything, including empty if a salt is not to be used. +/// Function execution is slow by design. Memory allocated for key will be +/// owned by the caller. +pub fn keyFromPassword(allocator: std.mem.Allocator, password: []const u8, salt: []const u8) !*[key_size]u8 { + var key = try allocator.alloc(u8, key_size); + const rounds = 4096; + try std.crypto.pwhash.pbkdf2(key, password, salt, rounds, std.crypto.auth.hmac.HmacSha1); + return key[0..key_size]; +} + +/// Symmetric encryption with a key. Key size is defined as a public constant. +/// Key can be provided directly or through the use of the keyFromPassword +/// function. +/// +/// Encryption currently AES 256, though this should be considered an implementation +/// detail. Padding performed with PKCS#7. +/// +/// Example: +/// +/// const str = "0123456789ABCDEFG"; +/// var buf: [str.len]u8 = undefined; +/// var data = std.mem.copy(u8, buf, str); +/// var key = try keyFromPassword(allocator, "foo", ""); // reuse key - this is slow +/// defer allocator.free(key); +/// const encrypted = try encryptWithKey(allocator, key.*, data); +/// +pub fn encryptWithKey(allocator: std.mem.Allocator, key: [key_size]u8, data: []u8) ![]const u8 { + const block_size = 16; + var in: *[block_size]u8 = undefined; + var out: [block_size]u8 = undefined; + var ctx = std.crypto.core.aes.Aes256.initEnc(key); + + const total_blocks = data.len / block_size; + var current_block: usize = 0; + var encrypted = try std.ArrayList(u8).initCapacity(allocator, block_size * (total_blocks + 1)); + defer encrypted.deinit(); + while (current_block < total_blocks) { + in = @ptrCast(*[block_size]u8, data[(current_block * block_size)..(((current_block + 1) * block_size) - 1)]); + ctx.encrypt(out[0..], in.*[0..]); + encrypted.appendSliceAssumeCapacity(out[0..]); + current_block += 1; + } + + // deal with final block, PKCS#7 padding + { + const padding: u8 = @intCast(u8, block_size - (data.len % block_size)); + var inx: u8 = 0; + for (data[(total_blocks * block_size)..]) |b| { + in[inx] = b; + inx += 1; + } + while (inx < out.len) { + in[inx] = padding; + inx += 1; + } + ctx.encrypt(out[0..], in[0..]); + encrypted.appendSliceAssumeCapacity(out[0..]); + } + return encrypted.toOwnedSlice(); +} + +/// Symmetric decryption with a key. Key size is defined as a public constant. +/// Key can be provided directly or through the use of the keyFromPassword +/// function. +/// +/// Decryption currently AES 256, though this should be considered an implementation +/// detail. Padding assumed to be PKCS#7. +/// +/// Example: +/// +/// const str = "0123456789ABCDEFG"; +/// var buf: [str.len]u8 = undefined; +/// var data = std.mem.copy(u8, buf, str); +/// var key = try keyFromPassword(allocator, "foo", ""); // reuse key - this is slow +/// defer allocator.free(key); +/// const encrypted = try encryptWithKey(allocator, key.*, data); +/// +pub fn decryptWithKey(allocator: std.mem.Allocator, key: [key_size]u8, data: []const u8) ![]const u8 { + const block_size = 16; + var in: *const [block_size]u8 = undefined; + var out: [block_size]u8 = undefined; + var ctx = std.crypto.core.aes.Aes256.initDec(key); + + const total_blocks = data.len / block_size; + var current_block: usize = 0; + var decrypted = try std.ArrayList(u8).initCapacity(allocator, block_size * (total_blocks - 1)); + defer decrypted.deinit(); + while (current_block < total_blocks - 1) { + in = @ptrCast(*const [block_size]u8, data[(current_block * block_size)..(((current_block + 1) * block_size) - 1)]); + ctx.decrypt(out[0..], in.*[0..]); + decrypted.appendSliceAssumeCapacity(out[0..]); + current_block += 1; + } + + // deal with final block, PKCS#7 padding + in = @ptrCast(*const [block_size]u8, data[(current_block * block_size)..(((current_block + 1) * block_size) - 1)]); + ctx.decrypt(out[0..], in.*[0..]); + std.debug.assert(out[block_size - 1] <= block_size); + try decrypted.appendSlice(out[0 .. block_size - out[block_size - 1]]); + return decrypted.toOwnedSlice(); +} + +test "Appendix C.3" { + var in = [_]u8{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; + // const exp_out = [_]u8{ 0x8e, 0xa2, 0xb7, 0xca, 0x51, 0x67, 0x45, 0xbf, 0xea, 0xfc, 0x49, 0x90, 0x4b, 0x49, 0x60, 0x89 }; + var allocator = std.testing.allocator; + const encrypted = try encryptWithKey(allocator, test_key, in[0..]); + defer allocator.free(encrypted); + const size: usize = 32; + try std.testing.expectEqual(size, encrypted.len); +} + +test "correct size, even block" { + const str = "0123456789ABCDEF"; + var allocator = std.testing.allocator; + var buf: [str.len]u8 = undefined; + var data = try std.fmt.bufPrint(&buf, str, .{}); + const encrypted = try encryptWithKey(allocator, test_key, data); + defer allocator.free(encrypted); + const size: usize = 32; + try std.testing.expectEqual(size, encrypted.len); +} + +test "round trip, even block" { + const str = "0123456789ABCDEF"; + var allocator = std.testing.allocator; + var buf: [str.len]u8 = undefined; + var data = try std.fmt.bufPrint(&buf, str, .{}); + const encrypted = try encryptWithKey(allocator, test_key, data); + defer allocator.free(encrypted); + const decrypted = try decryptWithKey(allocator, test_key, encrypted); + defer allocator.free(decrypted); + try std.testing.expectEqual(str.len, decrypted.len); + try std.testing.expectEqualStrings(str, decrypted); +} + +test "round trip, uneven block" { + const str = "0123456789ABCDEFG"; + var allocator = std.testing.allocator; + var buf: [str.len]u8 = undefined; + var data = try std.fmt.bufPrint(&buf, str, .{}); + const encrypted = try encryptWithKey(allocator, test_key, data); + defer allocator.free(encrypted); + const decrypted = try decryptWithKey(allocator, test_key, encrypted); + defer allocator.free(decrypted); + try std.testing.expectEqualStrings(str, decrypted); +} + +test "round trip, uneven block, using password" { + const str = "0123456789ABCDEFG"; + var allocator = std.testing.allocator; + var buf: [str.len]u8 = undefined; + var data = try std.fmt.bufPrint(&buf, str, .{}); + var key = try keyFromPassword(allocator, "foo", ""); + defer allocator.free(key); + const encrypted = try encryptWithKey(allocator, key.*, data); + defer allocator.free(encrypted); + const decrypted = try decryptWithKey(allocator, key.*, encrypted); + defer allocator.free(decrypted); + try std.testing.expectEqualStrings(str, decrypted); +} diff --git a/src/main-linux.zig b/src/main-linux.zig index 7451bec..78fbbe4 100644 --- a/src/main-linux.zig +++ b/src/main-linux.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const clip = @import("clipboard.zig"); +const Clipboard = @import("clipboard.zig"); const c = @cImport({ @cInclude("limits.h"); @@ -22,11 +22,13 @@ pub fn main() !u8 { break; } } - clipboardAction(allocator, watch) catch return 1; + var clip = try Clipboard.init(allocator); + defer clip.deinit(); + clipboardAction(allocator, watch, &clip) catch return 1; return 0; } -pub fn clipboardAction(allocator: std.mem.Allocator, watch: bool) !void { +fn clipboardAction(allocator: std.mem.Allocator, watch: bool, clip: *Clipboard) !void { var display: *c.Display = c.XOpenDisplay(null).?; defer _ = c.XCloseDisplay(display); const default_screen = c.XDefaultScreen(display); @@ -34,13 +36,13 @@ pub fn clipboardAction(allocator: std.mem.Allocator, watch: bool) !void { var window = c.XCreateSimpleWindow(display, c.XDefaultRootWindow(display), 0, 0, 1, 1, 0, color, color); defer _ = c.XDestroyWindow(display, window); // Watch will not return - if (watch) try watchClip(allocator, display, window, "CLIPBOARD"); - var result = (try printSelection(allocator, display, window, "CLIPBOARD", "UTF8_STRING")) or - (try printSelection(allocator, display, window, "CLIPBOARD", "STRING")); + if (watch) try watchClip(allocator, display, window, "CLIPBOARD", clip); + var result = (try printSelection(allocator, display, window, "CLIPBOARD", "UTF8_STRING", clip)) or + (try printSelection(allocator, display, window, "CLIPBOARD", "STRING", clip)); if (!result) return error.ClipboardActionFailed; } -fn watchClip(allocator: std.mem.Allocator, display: *c.Display, window: c.Window, bufname: [*c]const u8) !void { +fn watchClip(allocator: std.mem.Allocator, display: *c.Display, window: c.Window, bufname: [*c]const u8, clip: *Clipboard) !void { var event_base: c_int = undefined; var error_base: c_int = undefined; var event: c.XEvent = undefined; @@ -55,13 +57,20 @@ fn watchClip(allocator: std.mem.Allocator, display: *c.Display, window: c.Window if (event.type == event_base + c.XFixesSelectionNotify and event.xselection.property == bufid) { - if (!try printSelection(allocator, display, window, bufname, "UTF8_STRING")) - _ = try printSelection(allocator, display, window, bufname, "STRING"); + if (!try printSelection(allocator, display, window, bufname, "UTF8_STRING", clip)) + _ = try printSelection(allocator, display, window, bufname, "STRING", clip); } } } -fn printSelection(allocator: std.mem.Allocator, display: *c.Display, window: c.Window, bufname: [*c]const u8, fmtname: [*c]const u8) !bool { +fn printSelection( + allocator: std.mem.Allocator, + display: *c.Display, + window: c.Window, + bufname: [*c]const u8, + fmtname: [*c]const u8, + clip: *Clipboard, +) !bool { var ressize: c_ulong = undefined; var restail: c_ulong = undefined; var resbits: c_int = undefined; @@ -82,7 +91,7 @@ fn printSelection(allocator: std.mem.Allocator, display: *c.Display, window: c.W _ = c.XGetWindowProperty(display, window, propid, 0, c.LONG_MAX / 4, c.True, c.AnyPropertyType, &fmtid, &resbits, &ressize, &restail, &result); defer _ = c.XFree(result); if (fmtid != incrid) - _ = async clip.clipboardChanged(allocator, std.mem.span(result)); // TODO: Ensure we don't start queueing these things up + _ = async clip.clipboardChanged(std.mem.span(result)); // TODO: Ensure we don't start queueing these things up if (fmtid == incrid) { ressize = 1; @@ -101,7 +110,7 @@ fn printSelection(allocator: std.mem.Allocator, display: *c.Display, window: c.W //defer _ = c.XFree(result); // Creates double free error, but not sure why try buffer.appendSlice(try std.fmt.allocPrint(local_allocator, "{s}", .{result})); } - _ = async clip.clipboardChanged(allocator, buffer.items); + _ = async clip.clipboardChanged(buffer.items); } return true; } else // request failed, e.g. owner can't convert to the target format diff --git a/src/main-windows.zig b/src/main-windows.zig index f452ab9..b1487ea 100644 --- a/src/main-windows.zig +++ b/src/main-windows.zig @@ -1,6 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const clip = @import("clipboard.zig"); +const Clipboard = @import("clipboard.zig"); const w = std.os.windows; extern "user32" fn MessageBoxA(hWnd: ?w.HANDLE, lpText: ?w.LPCSTR, lpCaption: ?w.LPCSTR, uType: w.UINT) callconv(w.WINAPI) c_int; @@ -901,6 +901,7 @@ var window_state: union(enum(u8)) { var gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined; var contents: [:0]const u8 = ""; var free_contents = false; +var clip: *Clipboard = undefined; pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCmdLine: w.PWSTR, nCmdShow: w.INT) w.INT { _ = hPrevInstance; @@ -910,6 +911,16 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); + var clippy = Clipboard.init(gpa.allocator()) catch |e| { + std.log.err("Could not initialize common clipboard action. {}. Exiting", .{e}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return 1; + }; + clip = &clippy; + defer clippy.deinit(); + h_instance = hInstance; // Register the window class. @@ -1195,5 +1206,5 @@ fn UpdateContents(hwnd: w.HWND) void { break :blk ""; }; } - _ = async clip.clipboardChanged(allocator, contents); // Notify common handler + _ = async clip.clipboardChanged(contents); // Notify common handler } diff --git a/zfetch_deps.zig b/zfetch_deps.zig new file mode 100644 index 0000000..820efbd --- /dev/null +++ b/zfetch_deps.zig @@ -0,0 +1 @@ +const use_submodules = 1;