| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | const Package = @This(); |
| 2 | 2 | |
| 3 | const builtin = @import("builtin"); |
| 3 | 4 | const std = @import("std"); |
| 4 | 5 | const fs = std.fs; |
| 5 | 6 | const mem = std.mem; |
| ... | ... | @@ -298,16 +299,37 @@ fn fetchAndUnpack( |
| 298 | 299 | // Check if the expected_hash is already present in the global package |
| 299 | 300 | // cache, and thereby avoid both fetching and unpacking. |
| 300 | 301 | if (expected_hash) |h| cached: { |
| 301 | | if (h.len != 2 * Hash.digest_length) { |
| 302 | const hex_multihash_len = 2 * multihash_len; |
| 303 | if (h.len >= 2) { |
| 304 | const their_multihash_func = std.fmt.parseInt(u8, h[0..2], 16) catch |err| { |
| 305 | return reportError( |
| 306 | ini, |
| 307 | comp_directory, |
| 308 | h.ptr, |
| 309 | "invalid multihash value: unable to parse hash function: {s}", |
| 310 | .{@errorName(err)}, |
| 311 | ); |
| 312 | }; |
| 313 | if (@intToEnum(MultihashFunction, their_multihash_func) != multihash_function) { |
| 314 | return reportError( |
| 315 | ini, |
| 316 | comp_directory, |
| 317 | h.ptr, |
| 318 | "unsupported hash function: only sha2-256 is supported", |
| 319 | .{}, |
| 320 | ); |
| 321 | } |
| 322 | } |
| 323 | if (h.len != hex_multihash_len) { |
| 302 | 324 | return reportError( |
| 303 | 325 | ini, |
| 304 | 326 | comp_directory, |
| 305 | 327 | h.ptr, |
| 306 | 328 | "wrong hash size. expected: {d}, found: {d}", |
| 307 | | .{ Hash.digest_length, h.len }, |
| 329 | .{ hex_multihash_len, h.len }, |
| 308 | 330 | ); |
| 309 | 331 | } |
| 310 | | const hex_digest = h[0 .. 2 * Hash.digest_length]; |
| 332 | const hex_digest = h[0..hex_multihash_len]; |
| 311 | 333 | const pkg_dir_sub_path = "p" ++ s ++ hex_digest; |
| 312 | 334 | var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) { |
| 313 | 335 | error.FileNotFound => break :cached, |
| ... | ... | @@ -396,8 +418,8 @@ fn fetchAndUnpack( |
| 396 | 418 | const pkg_dir_sub_path = "p" ++ s ++ hexDigest(actual_hash); |
| 397 | 419 | try renameTmpIntoCache(global_cache_directory.handle, tmp_dir_sub_path, pkg_dir_sub_path); |
| 398 | 420 | |
| 421 | const actual_hex = hexDigest(actual_hash); |
| 399 | 422 | if (expected_hash) |h| { |
| 400 | | const actual_hex = hexDigest(actual_hash); |
| 401 | 423 | if (!mem.eql(u8, h, &actual_hex)) { |
| 402 | 424 | return reportError( |
| 403 | 425 | ini, |
| ... | ... | @@ -413,7 +435,7 @@ fn fetchAndUnpack( |
| 413 | 435 | comp_directory, |
| 414 | 436 | url.ptr, |
| 415 | 437 | "url field is missing corresponding hash field: hash={s}", |
| 416 | | .{std.fmt.fmtSliceHexLower(&actual_hash)}, |
| 438 | .{&actual_hex}, |
| 417 | 439 | ); |
| 418 | 440 | } |
| 419 | 441 | |
| ... | ... | @@ -440,6 +462,12 @@ fn unpackTarball( |
| 440 | 462 | |
| 441 | 463 | try std.tar.pipeToFileSystem(out_dir, decompress.reader(), .{ |
| 442 | 464 | .strip_components = 1, |
| 465 | // TODO: we would like to set this to executable_bit_only, but two |
| 466 | // things need to happen before that: |
| 467 | // 1. the tar implementation needs to support it |
| 468 | // 2. the hashing algorithm here needs to support detecting the is_executable |
| 469 | // bit on Windows from the ACLs (see the isExecutable function). |
| 470 | .mode_mode = .ignore, |
| 443 | 471 | }); |
| 444 | 472 | } |
| 445 | 473 | |
| ... | ... | @@ -468,7 +496,7 @@ const HashedFile = struct { |
| 468 | 496 | hash: [Hash.digest_length]u8, |
| 469 | 497 | failure: Error!void, |
| 470 | 498 | |
| 471 | | const Error = fs.File.OpenError || fs.File.ReadError; |
| 499 | const Error = fs.File.OpenError || fs.File.ReadError || fs.File.StatError; |
| 472 | 500 | |
| 473 | 501 | fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool { |
| 474 | 502 | _ = context; |
| ... | ... | @@ -544,6 +572,8 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void |
| 544 | 572 | var buf: [8000]u8 = undefined; |
| 545 | 573 | var file = try dir.openFile(hashed_file.path, .{}); |
| 546 | 574 | var hasher = Hash.init(.{}); |
| 575 | hasher.update(hashed_file.path); |
| 576 | hasher.update(&.{ 0, @boolToInt(try isExecutable(file)) }); |
| 547 | 577 | while (true) { |
| 548 | 578 | const bytes_read = try file.read(&buf); |
| 549 | 579 | if (bytes_read == 0) break; |
| ... | ... | @@ -552,6 +582,19 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void |
| 552 | 582 | hasher.final(&hashed_file.hash); |
| 553 | 583 | } |
| 554 | 584 | |
| 585 | fn isExecutable(file: fs.File) !bool { |
| 586 | if (builtin.os.tag == .windows) { |
| 587 | // TODO check the ACL on Windows. |
| 588 | // Until this is implemented, this could be a false negative on |
| 589 | // Windows, which is why we do not yet set executable_bit_only above |
| 590 | // when unpacking the tarball. |
| 591 | return false; |
| 592 | } else { |
| 593 | const stat = try file.stat(); |
| 594 | return (stat.mode & std.os.S.IXUSR) != 0; |
| 595 | } |
| 596 | } |
| 597 | |
| 555 | 598 | const hex_charset = "0123456789abcdef"; |
| 556 | 599 | |
| 557 | 600 | fn hex64(x: u64) [16]u8 { |
| ... | ... | @@ -570,11 +613,30 @@ test hex64 { |
| 570 | 613 | try std.testing.expectEqualStrings("[00efcdab78563412]", s); |
| 571 | 614 | } |
| 572 | 615 | |
| 573 | | fn hexDigest(digest: [Hash.digest_length]u8) [Hash.digest_length * 2]u8 { |
| 574 | | var result: [Hash.digest_length * 2]u8 = undefined; |
| 616 | const multihash_function: MultihashFunction = switch (Hash) { |
| 617 | std.crypto.hash.sha2.Sha256 => .@"sha2-256", |
| 618 | else => @compileError("unreachable"), |
| 619 | }; |
| 620 | comptime { |
| 621 | // We avoid unnecessary uleb128 code in hexDigest by asserting here the |
| 622 | // values are small enough to be contained in the one-byte encoding. |
| 623 | assert(@enumToInt(multihash_function) < 127); |
| 624 | assert(Hash.digest_length < 127); |
| 625 | } |
| 626 | const multihash_len = 1 + 1 + Hash.digest_length; |
| 627 | |
| 628 | fn hexDigest(digest: [Hash.digest_length]u8) [multihash_len * 2]u8 { |
| 629 | var result: [multihash_len * 2]u8 = undefined; |
| 630 | |
| 631 | result[0] = hex_charset[@enumToInt(multihash_function) >> 4]; |
| 632 | result[1] = hex_charset[@enumToInt(multihash_function) & 15]; |
| 633 | |
| 634 | result[2] = hex_charset[Hash.digest_length >> 4]; |
| 635 | result[3] = hex_charset[Hash.digest_length & 15]; |
| 636 | |
| 575 | 637 | for (digest) |byte, i| { |
| 576 | | result[i * 2 + 0] = hex_charset[byte >> 4]; |
| 577 | | result[i * 2 + 1] = hex_charset[byte & 15]; |
| 638 | result[4 + i * 2] = hex_charset[byte >> 4]; |
| 639 | result[5 + i * 2] = hex_charset[byte & 15]; |
| 578 | 640 | } |
| 579 | 641 | return result; |
| 580 | 642 | } |
| ... | ... | @@ -607,3 +669,21 @@ fn renameTmpIntoCache( |
| 607 | 669 | break; |
| 608 | 670 | } |
| 609 | 671 | } |
| 672 | |
| 673 | const MultihashFunction = enum(u16) { |
| 674 | identity = 0x00, |
| 675 | sha1 = 0x11, |
| 676 | @"sha2-256" = 0x12, |
| 677 | @"sha2-512" = 0x13, |
| 678 | @"sha3-512" = 0x14, |
| 679 | @"sha3-384" = 0x15, |
| 680 | @"sha3-256" = 0x16, |
| 681 | @"sha3-224" = 0x17, |
| 682 | @"sha2-384" = 0x20, |
| 683 | @"sha2-256-trunc254-padded" = 0x1012, |
| 684 | @"sha2-224" = 0x1013, |
| 685 | @"sha2-512-224" = 0x1014, |
| 686 | @"sha2-512-256" = 0x1015, |
| 687 | @"blake2b-256" = 0xb220, |
| 688 | _, |
| 689 | }; |