const std = @import("std"); const zfetch = @import("zfetch"); const crypt = @import("crypt.zig"); // const tls = @import("iguanaTLS"); // 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 download(self: *Self) ?[]const u8 { const encrypted = get(self.allocator) catch |e| { std.log.err("Could not download remote clipboard contents: {}", .{e}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; defer self.allocator.free(encrypted); return crypt.decryptWithKey(self.allocator, self.key.*, encrypted) catch |e| { std.log.err("Could not decrypt remote clipboard contents: {}", .{e}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } return null; }; } 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); 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; }; 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 get(allocator: std.mem.Allocator) ![]const u8 { // 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(.GET, headers, null); // 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 data = std.ArrayList(u8).init(allocator); defer data.deinit(); const data_writer = data.writer(); var buf: [1024]u8 = undefined; while (true) { const read = try reader.read(&buf); if (read == 0) break; try data_writer.writeAll(buf[0..read]); } return data.toOwnedSlice(); } 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; var watcher = init(allocator); defer watcher.deinit(); try watcher.clipboardChanged("hello world"); }