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
|
||||
libxfixes-libXfixes-5.0.3
|
||||
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 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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
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 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
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