enable uploads
This commit is contained in:
		
							parent
							
								
									eb8058c930
								
							
						
					
					
						commit
						b6a03c8634
					
				
					 8 changed files with 586 additions and 31 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		
		Reference in a new issue