codekingpro/portable-devtools
114k
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.TargetFile = exports.MetaFile = void 0;
7const crypto_1 = __importDefault(require("crypto"));
8const util_1 = __importDefault(require("util"));
9const error_1 = require("./error");
10const utils_1 = require("./utils");
11// A container with information about a particular metadata file.
12//
13// This class is used for Timestamp and Snapshot metadata.
14class MetaFile {
15 version;
16 length;
17 hashes;
18 unrecognizedFields;
19 constructor(opts) {
20 if (opts.version <= 0) {
21 throw new error_1.ValueError('Metafile version must be at least 1');
22 }
23 if (opts.length !== undefined) {
24 validateLength(opts.length);
25 }
26 this.version = opts.version;
27 this.length = opts.length;
28 this.hashes = opts.hashes;
29 this.unrecognizedFields = opts.unrecognizedFields || {};
30 }
31 equals(other) {
32 if (!(other instanceof MetaFile)) {
33 return false;
34 }
35 return (this.version === other.version &&
36 this.length === other.length &&
37 util_1.default.isDeepStrictEqual(this.hashes, other.hashes) &&
38 util_1.default.isDeepStrictEqual(this.unrecognizedFields, other.unrecognizedFields));
39 }
40 verify(data) {
41 // Verifies that the given data matches the expected length.
42 if (this.length !== undefined) {
43 if (data.length !== this.length) {
44 throw new error_1.LengthOrHashMismatchError(`Expected length ${this.length} but got ${data.length}`);
45 }
46 }
47 // Verifies that the given data matches the supplied hashes.
48 if (this.hashes) {
49 Object.entries(this.hashes).forEach(([key, value]) => {
50 let hash;
51 try {
52 hash = crypto_1.default.createHash(key);
53 }
54 catch (e) {
55 throw new error_1.LengthOrHashMismatchError(`Hash algorithm ${key} not supported`);
56 }
57 const observedHash = hash.update(data).digest('hex');
58 if (observedHash !== value) {
59 throw new error_1.LengthOrHashMismatchError(`Expected hash ${value} but got ${observedHash}`);
60 }
61 });
62 }
63 }
64 toJSON() {
65 const json = {
66 version: this.version,
67 ...this.unrecognizedFields,
68 };
69 if (this.length !== undefined) {
70 json.length = this.length;
71 }
72 if (this.hashes) {
73 json.hashes = this.hashes;
74 }
75 return json;
76 }
77 static fromJSON(data) {
78 const { version, length, hashes, ...rest } = data;
79 if (typeof version !== 'number') {
80 throw new TypeError('version must be a number');
81 }
82 if (utils_1.guard.isDefined(length) && typeof length !== 'number') {
83 throw new TypeError('length must be a number');
84 }
85 if (utils_1.guard.isDefined(hashes) && !utils_1.guard.isStringRecord(hashes)) {
86 throw new TypeError('hashes must be string keys and values');
87 }
88 return new MetaFile({
89 version,
90 length,
91 hashes,
92 unrecognizedFields: rest,
93 });
94 }
95}
96exports.MetaFile = MetaFile;
97// Container for info about a particular target file.
98//
99// This class is used for Target metadata.
100class TargetFile {
101 length;
102 path;
103 hashes;
104 unrecognizedFields;
105 constructor(opts) {
106 validateLength(opts.length);
107 this.length = opts.length;
108 this.path = opts.path;
109 this.hashes = opts.hashes;
110 this.unrecognizedFields = opts.unrecognizedFields || {};
111 }
112 get custom() {
113 const custom = this.unrecognizedFields['custom'];
114 if (!custom || Array.isArray(custom) || !(typeof custom === 'object')) {
115 return {};
116 }
117 return custom;
118 }
119 equals(other) {
120 if (!(other instanceof TargetFile)) {
121 return false;
122 }
123 return (this.length === other.length &&
124 this.path === other.path &&
125 util_1.default.isDeepStrictEqual(this.hashes, other.hashes) &&
126 util_1.default.isDeepStrictEqual(this.unrecognizedFields, other.unrecognizedFields));
127 }
128 async verify(stream) {
129 let observedLength = 0;
130 // Create a digest for each hash algorithm
131 const digests = Object.keys(this.hashes).reduce((acc, key) => {
132 try {
133 acc[key] = crypto_1.default.createHash(key);
134 }
135 catch (e) {
136 throw new error_1.LengthOrHashMismatchError(`Hash algorithm ${key} not supported`);
137 }
138 return acc;
139 }, {});
140 // Read stream chunk by chunk
141 for await (const chunk of stream) {
142 // Keep running tally of stream length
143 observedLength += chunk.length;
144 // Append chunk to each digest
145 Object.values(digests).forEach((digest) => {
146 digest.update(chunk);
147 });
148 }
149 // Verify length matches expected value
150 if (observedLength !== this.length) {
151 throw new error_1.LengthOrHashMismatchError(`Expected length ${this.length} but got ${observedLength}`);
152 }
153 // Verify each digest matches expected value
154 Object.entries(digests).forEach(([key, value]) => {
155 const expected = this.hashes[key];
156 const actual = value.digest('hex');
157 if (actual !== expected) {
158 throw new error_1.LengthOrHashMismatchError(`Expected hash ${expected} but got ${actual}`);
159 }
160 });
161 }
162 toJSON() {
163 return {
164 length: this.length,
165 hashes: this.hashes,
166 ...this.unrecognizedFields,
167 };
168 }
169 static fromJSON(path, data) {
170 const { length, hashes, ...rest } = data;
171 if (typeof length !== 'number') {
172 throw new TypeError('length must be a number');
173 }
174 if (!utils_1.guard.isStringRecord(hashes)) {
175 throw new TypeError('hashes must have string keys and values');
176 }
177 return new TargetFile({
178 length,
179 path,
180 hashes,
181 unrecognizedFields: rest,
182 });
183 }
184}
185exports.TargetFile = TargetFile;
186// Check that supplied length if valid
187function validateLength(length) {
188 if (length < 0) {
189 throw new error_1.ValueError('Length must be at least 0');
190 }
191}
192 