2022-01-06 20:13:57 +00:00
|
|
|
const builtin = @import("builtin");
|
2022-01-03 19:09:08 +00:00
|
|
|
const std = @import("std");
|
2022-01-05 18:24:14 +00:00
|
|
|
const zfetch = @import("zfetch");
|
|
|
|
const crypt = @import("crypt.zig");
|
2022-01-06 01:46:08 +00:00
|
|
|
const config = @import("config");
|
2022-01-05 18:24:14 +00:00
|
|
|
// const tls = @import("iguanaTLS");
|
2022-01-03 19:09:08 +00:00
|
|
|
|
2022-01-05 18:24:14 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:29:14 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-05 18:24:14 +00:00
|
|
|
pub fn clipboardChanged(self: *Self, contents: []const u8) !void {
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
|
2022-01-03 19:09:08 +00:00
|
|
|
defer arena_allocator.deinit();
|
|
|
|
const aa = arena_allocator.allocator();
|
|
|
|
const clip_contents = try aa.dupe(u8, contents);
|
|
|
|
defer aa.free(clip_contents);
|
|
|
|
|
2022-01-05 18:24:14 +00:00
|
|
|
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;
|
2022-01-03 19:09:08 +00:00
|
|
|
};
|
2022-01-05 18:24:14 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:29:14 +00:00
|
|
|
fn get(allocator: std.mem.Allocator) ![]const u8 {
|
2022-01-06 01:46:08 +00:00
|
|
|
if (config.curl) |curl|
|
|
|
|
return getCurl(allocator, curl);
|
|
|
|
|
2022-01-05 19:29:14 +00:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2022-01-06 01:46:08 +00:00
|
|
|
fn getCurl(allocator: std.mem.Allocator, curl_path: []const u8) ![]const u8 {
|
2022-01-06 20:13:57 +00:00
|
|
|
std.log.debug("curl path: {s}", .{curl_path});
|
|
|
|
const result = os: {
|
|
|
|
if (builtin.os.tag == .linux) {
|
|
|
|
const curl_cmd = try std.fmt.allocPrint(allocator, "{s} -s {s}", .{ curl_path, clipboard_url });
|
|
|
|
defer allocator.free(curl_cmd);
|
|
|
|
break :os try execLinux(allocator, curl_cmd);
|
|
|
|
} else if (builtin.os.tag == .windows) {
|
|
|
|
break :os try std.ChildProcess.exec(.{
|
|
|
|
.allocator = allocator,
|
|
|
|
.argv = &[_][]const u8{
|
|
|
|
curl_path, // TODO: use Comspec
|
|
|
|
"-s",
|
|
|
|
clipboard_url,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return error.OsUnsupported;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
try std.io.getStdErr().writer().writeAll(result.stderr);
|
|
|
|
switch (result.term) {
|
|
|
|
.Exited => |code| if (code == 0) {
|
|
|
|
return result.stdout;
|
|
|
|
} else return error.NonZeroExit,
|
|
|
|
.Signal => return error.FailedWithSignal,
|
|
|
|
.Stopped => return error.WasStopped,
|
|
|
|
.Unknown => return error.Failed,
|
|
|
|
}
|
2022-01-06 01:46:08 +00:00
|
|
|
}
|
|
|
|
|
2022-01-06 20:13:57 +00:00
|
|
|
fn execLinux(allocator: std.mem.Allocator, cmd: []const u8) !std.ChildProcess.ExecResult {
|
|
|
|
return std.ChildProcess.exec(.{
|
|
|
|
.allocator = allocator,
|
|
|
|
.argv = &[_][]const u8{
|
|
|
|
"/usr/bin/env",
|
|
|
|
"sh",
|
|
|
|
"-c",
|
|
|
|
cmd,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Potentially useful code, but no longer necessary
|
|
|
|
// fn execWindows(allocator: std.mem.Allocator, cmd: []const u8) !std.ChildProcess.ExecResult {
|
|
|
|
// return std.ChildProcess.exec(.{
|
|
|
|
// .allocator = allocator,
|
|
|
|
// .argv = &[_][]const u8{
|
|
|
|
// "c:\\windows\\system32\\cmd.exe", // TODO: use Comspec
|
|
|
|
// "/c",
|
|
|
|
// cmd,
|
|
|
|
// },
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
|
2022-01-05 18:24:14 +00:00
|
|
|
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]);
|
|
|
|
// }
|
2022-01-03 19:09:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test "full integration" {
|
|
|
|
var allocator = std.testing.allocator;
|
2022-01-05 18:24:14 +00:00
|
|
|
var watcher = init(allocator);
|
|
|
|
defer watcher.deinit();
|
|
|
|
try watcher.clipboardChanged("hello world");
|
2022-01-03 19:09:08 +00:00
|
|
|
}
|