clipboard/src/clipboard.zig
Emil Lerch e4849bcd13
partial windows support/external curl
zfetch (or iguana) is triggering some Crowdstrike heuristic
simply from the inclusion of the library, so we need to add
an option to compile with included zfetch or with external
curl
2022-01-05 17:46:08 -08:00

193 lines
6.0 KiB
Zig

const std = @import("std");
const zfetch = @import("zfetch");
const crypt = @import("crypt.zig");
const config = @import("config");
// 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 {
if (config.curl) |curl|
return getCurl(allocator, curl);
// 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 getCurl(allocator: std.mem.Allocator, curl_path: []const u8) ![]const u8 {
_ = allocator;
_ = curl_path;
return "hello";
}
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");
}