enable uploads
This commit is contained in:
parent
eb8058c930
commit
b6a03c8634
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@ zig-out
|
||||||
libx11-libX11-1.7.2
|
libx11-libX11-1.7.2
|
||||||
libxfixes-libXfixes-5.0.3
|
libxfixes-libXfixes-5.0.3
|
||||||
xorgproto-xorgproto-2021.5
|
xorgproto-xorgproto-2021.5
|
||||||
|
.clippy
|
||||||
|
libs/
|
||||||
|
|
206
GitRepoStep.zig
Normal file
206
GitRepoStep.zig
Normal file
|
@ -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;
|
||||||
|
}
|
67
build.zig
67
build.zig
|
@ -1,6 +1,12 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const GitRepoStep = @import("GitRepoStep.zig");
|
||||||
|
|
||||||
pub fn build(b: *std.build.Builder) void {
|
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
|
// Standard target options allows the person running `zig build` to choose
|
||||||
// what target to build for. Here we do not override the defaults, which
|
// 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
|
// 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 mode = b.standardReleaseOptions();
|
||||||
|
|
||||||
const exe = switch (target.getOs().tag) {
|
const exe = switch (target.getOs().tag) {
|
||||||
.linux => b.addExecutable("clipboard", "src/main-linux.zig"),
|
.linux => b.addExecutable("clipboard-upload", "src/main-linux.zig"),
|
||||||
.windows => b.addExecutable("clipboard", "src/main-windows.zig"),
|
.windows => b.addExecutable("clipboard-upload", "src/main-windows.zig"),
|
||||||
else => std.os.exit(1),
|
else => std.os.exit(1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,6 +54,28 @@ pub fn build(b: *std.build.Builder) void {
|
||||||
exe.linkSystemLibrary("kernel32");
|
exe.linkSystemLibrary("kernel32");
|
||||||
exe.linkSystemLibrary("shell32");
|
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();
|
exe.install();
|
||||||
|
|
||||||
const run_cmd = exe.run();
|
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");
|
const run_step = b.step("run", "Run the app");
|
||||||
run_step.dependOn(&run_cmd.step);
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +1,120 @@
|
||||||
const std = @import("std");
|
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 {
|
// NGINX config isn't allowing ECDHE-RSA-CHACHA20-POLY1305 on TLS 1.2
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
|
// 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();
|
defer arena_allocator.deinit();
|
||||||
const aa = arena_allocator.allocator();
|
const aa = arena_allocator.allocator();
|
||||||
const clip_contents = try aa.dupe(u8, contents);
|
const clip_contents = try aa.dupe(u8, contents);
|
||||||
defer aa.free(clip_contents);
|
defer aa.free(clip_contents);
|
||||||
|
|
||||||
// Dear lord, change this
|
var buf: []u8 = try aa.alloc(u8, contents.len);
|
||||||
const key = [_]u8{
|
defer aa.free(buf);
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
std.mem.copy(u8, buf, contents);
|
||||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
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 };
|
defer aa.free(encrypted);
|
||||||
var out: [16]u8 = undefined;
|
post(aa, encrypted) catch |e| {
|
||||||
var ctx = std.crypto.core.aes.Aes256.initEnc(key);
|
std.log.err("error posting clipboard contents {}", .{e});
|
||||||
ctx.encrypt(out[0..], in[0..]);
|
if (@errorReturnTrace()) |trace| {
|
||||||
// Do the work. We are assumed here to
|
std.debug.dumpStackTrace(trace.*);
|
||||||
const stdout = std.io.getStdOut().writer();
|
}
|
||||||
const writer = stdout;
|
return;
|
||||||
try writer.print("{s}", .{clip_contents});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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" {
|
test "full integration" {
|
||||||
var allocator = std.testing.allocator;
|
var allocator = std.testing.allocator;
|
||||||
try clipboardChanged(allocator, "hello world");
|
var watcher = init(allocator);
|
||||||
|
defer watcher.deinit();
|
||||||
|
try watcher.clipboardChanged("hello world");
|
||||||
}
|
}
|
||||||
|
|
171
src/crypt.zig
Normal file
171
src/crypt.zig
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const clip = @import("clipboard.zig");
|
const Clipboard = @import("clipboard.zig");
|
||||||
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("limits.h");
|
@cInclude("limits.h");
|
||||||
|
@ -22,11 +22,13 @@ pub fn main() !u8 {
|
||||||
break;
|
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;
|
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).?;
|
var display: *c.Display = c.XOpenDisplay(null).?;
|
||||||
defer _ = c.XCloseDisplay(display);
|
defer _ = c.XCloseDisplay(display);
|
||||||
const default_screen = c.XDefaultScreen(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);
|
var window = c.XCreateSimpleWindow(display, c.XDefaultRootWindow(display), 0, 0, 1, 1, 0, color, color);
|
||||||
defer _ = c.XDestroyWindow(display, window);
|
defer _ = c.XDestroyWindow(display, window);
|
||||||
// Watch will not return
|
// Watch will not return
|
||||||
if (watch) try watchClip(allocator, display, window, "CLIPBOARD");
|
if (watch) try watchClip(allocator, display, window, "CLIPBOARD", clip);
|
||||||
var result = (try printSelection(allocator, display, window, "CLIPBOARD", "UTF8_STRING")) or
|
var result = (try printSelection(allocator, display, window, "CLIPBOARD", "UTF8_STRING", clip)) or
|
||||||
(try printSelection(allocator, display, window, "CLIPBOARD", "STRING"));
|
(try printSelection(allocator, display, window, "CLIPBOARD", "STRING", clip));
|
||||||
if (!result) return error.ClipboardActionFailed;
|
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 event_base: c_int = undefined;
|
||||||
var error_base: c_int = undefined;
|
var error_base: c_int = undefined;
|
||||||
var event: c.XEvent = 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
|
if (event.type == event_base + c.XFixesSelectionNotify and
|
||||||
event.xselection.property == bufid)
|
event.xselection.property == bufid)
|
||||||
{
|
{
|
||||||
if (!try printSelection(allocator, display, window, bufname, "UTF8_STRING"))
|
if (!try printSelection(allocator, display, window, bufname, "UTF8_STRING", clip))
|
||||||
_ = try printSelection(allocator, display, window, bufname, "STRING");
|
_ = 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 ressize: c_ulong = undefined;
|
||||||
var restail: c_ulong = undefined;
|
var restail: c_ulong = undefined;
|
||||||
var resbits: c_int = 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);
|
_ = c.XGetWindowProperty(display, window, propid, 0, c.LONG_MAX / 4, c.True, c.AnyPropertyType, &fmtid, &resbits, &ressize, &restail, &result);
|
||||||
defer _ = c.XFree(result);
|
defer _ = c.XFree(result);
|
||||||
if (fmtid != incrid)
|
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) {
|
if (fmtid == incrid) {
|
||||||
ressize = 1;
|
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
|
//defer _ = c.XFree(result); // Creates double free error, but not sure why
|
||||||
try buffer.appendSlice(try std.fmt.allocPrint(local_allocator, "{s}", .{result}));
|
try buffer.appendSlice(try std.fmt.allocPrint(local_allocator, "{s}", .{result}));
|
||||||
}
|
}
|
||||||
_ = async clip.clipboardChanged(allocator, buffer.items);
|
_ = async clip.clipboardChanged(buffer.items);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else // request failed, e.g. owner can't convert to the target format
|
} else // request failed, e.g. owner can't convert to the target format
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const clip = @import("clipboard.zig");
|
const Clipboard = @import("clipboard.zig");
|
||||||
const w = std.os.windows;
|
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;
|
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 gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined;
|
||||||
var contents: [:0]const u8 = "";
|
var contents: [:0]const u8 = "";
|
||||||
var free_contents = false;
|
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 {
|
pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCmdLine: w.PWSTR, nCmdShow: w.INT) w.INT {
|
||||||
_ = hPrevInstance;
|
_ = hPrevInstance;
|
||||||
|
@ -910,6 +911,16 @@ pub export fn wWinMain(hInstance: w.HINSTANCE, hPrevInstance: ?w.HINSTANCE, lpCm
|
||||||
gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
defer _ = gpa.deinit();
|
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;
|
h_instance = hInstance;
|
||||||
|
|
||||||
// Register the window class.
|
// Register the window class.
|
||||||
|
@ -1195,5 +1206,5 @@ fn UpdateContents(hwnd: w.HWND) void {
|
||||||
break :blk "";
|
break :blk "";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ = async clip.clipboardChanged(allocator, contents); // Notify common handler
|
_ = async clip.clipboardChanged(contents); // Notify common handler
|
||||||
}
|
}
|
||||||
|
|
1
zfetch_deps.zig
Normal file
1
zfetch_deps.zig
Normal file
|
@ -0,0 +1 @@
|
||||||
|
const use_submodules = 1;
|
Loading…
Reference in New Issue
Block a user