@push.rocks/smartlinuxrepo 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/alpine/apkindexreader.d.ts +87 -0
- package/dist_ts/alpine/apkindexreader.js +193 -0
- package/dist_ts/arch/indexreader.d.ts +35 -0
- package/dist_ts/arch/indexreader.js +45 -0
- package/dist_ts/arch/reader.js +2 -2
- package/dist_ts/debian/indexbuilder.js +8 -23
- package/dist_ts/debian/indexreader.d.ts +107 -0
- package/dist_ts/debian/indexreader.js +274 -0
- package/dist_ts/debian/reader.js +6 -2
- package/dist_ts/debian/rules.d.ts +18 -0
- package/dist_ts/debian/rules.js +81 -2
- package/dist_ts/index.d.ts +14 -4
- package/dist_ts/index.js +9 -3
- package/dist_ts/readers/decompress.d.ts +14 -5
- package/dist_ts/readers/decompress.js +401 -26
- package/dist_ts/readers/document.d.ts +36 -0
- package/dist_ts/readers/document.js +110 -0
- package/dist_ts/rpm/repodata.d.ts +3 -2
- package/dist_ts/rpm/repodata.js +5 -4
- package/dist_ts/rpm/repodatareader.d.ts +173 -0
- package/dist_ts/rpm/repodatareader.js +414 -0
- package/dist_ts/rpm/xmlreader.d.ts +70 -0
- package/dist_ts/rpm/xmlreader.js +576 -0
- package/dist_ts/shared/errors.d.ts +12 -4
- package/dist_ts/shared/errors.js +1 -1
- package/dist_ts/shared/limits.d.ts +9 -2
- package/dist_ts/shared/limits.js +5 -2
- package/dist_ts/shared/types.d.ts +2 -0
- package/dist_ts/signing/verifiers.d.ts +43 -0
- package/dist_ts/signing/verifiers.js +81 -2
- package/package.json +1 -1
- package/readme.md +56 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/alpine/apkindexreader.ts +265 -0
- package/ts/arch/indexreader.ts +66 -0
- package/ts/arch/reader.ts +3 -2
- package/ts/debian/indexbuilder.ts +13 -23
- package/ts/debian/indexreader.ts +372 -0
- package/ts/debian/reader.ts +7 -2
- package/ts/debian/rules.ts +83 -1
- package/ts/index.ts +40 -2
- package/ts/readers/decompress.ts +442 -32
- package/ts/readers/document.ts +136 -0
- package/ts/rpm/repodata.ts +4 -3
- package/ts/rpm/repodatareader.ts +582 -0
- package/ts/rpm/xmlreader.ts +680 -0
- package/ts/shared/errors.ts +13 -4
- package/ts/shared/limits.ts +10 -2
- package/ts/shared/types.ts +3 -0
- package/ts/signing/verifiers.ts +113 -1
package/ts/rpm/repodata.ts
CHANGED
|
@@ -23,9 +23,10 @@ import { element, textElement, XML_HEADER } from './xml.js';
|
|
|
23
23
|
* createrepo_c 1.2.6 `cr_is_primary` (`src/misc.h:111-119`): paths under
|
|
24
24
|
* `/etc/`, exactly `/usr/lib/sendmail`, or containing `bin/` anywhere. It is
|
|
25
25
|
* applied to every file entry, directories and ghosts included
|
|
26
|
-
* (`src/xml_dump.c:206`).
|
|
26
|
+
* (`src/xml_dump.c:206`). `buildRpmIndex` lists such files in `primary`
|
|
27
|
+
* besides the executable ones, where Gitea lists only the executable ones.
|
|
27
28
|
*/
|
|
28
|
-
export function
|
|
29
|
+
export function isRpmPrimaryPath(pathArg: string): boolean {
|
|
29
30
|
return pathArg.startsWith('/etc/') || pathArg === '/usr/lib/sendmail' || pathArg.includes('bin/');
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -128,7 +129,7 @@ export function renderRepomd(dataArg: IRepomdData[]): string {
|
|
|
128
129
|
|
|
129
130
|
function renderPrimaryPackage(recordArg: IRpmPackageRecord): string {
|
|
130
131
|
const metadata = recordArg.metadata;
|
|
131
|
-
const files = metadata.files.filter((file) => file.isExecutable ||
|
|
132
|
+
const files = metadata.files.filter((file) => file.isExecutable || isRpmPrimaryPath(file.path));
|
|
132
133
|
return element(
|
|
133
134
|
'package',
|
|
134
135
|
[['type', 'rpm']],
|
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { LinuxRepoError, limitExceeded } from '../shared/errors.js';
|
|
2
|
+
import { resolveLinuxPackageLimits, type ILinuxPackageLimits } from '../shared/limits.js';
|
|
3
|
+
import type { TLinuxCompression } from '../shared/types.js';
|
|
4
|
+
import { assertLinuxCompression, assertReaderBytes, assertReaderInput, readIndexDocument } from '../readers/document.js';
|
|
5
|
+
import { scanXmlDocument, type IXmlAttribute, type IXmlFrameScan, type IXmlUnitHandler } from './xmlreader.js';
|
|
6
|
+
|
|
7
|
+
/** One attribute of a root element, its value decoded. */
|
|
8
|
+
export interface IRpmXmlAttribute {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly value: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The parts of a repodata document outside its units, as exact bytes. */
|
|
14
|
+
export interface IRpmXmlFrame {
|
|
15
|
+
/** The bytes before the root start tag (XML declaration, comments, whitespace). */
|
|
16
|
+
readonly prolog: Uint8Array;
|
|
17
|
+
readonly rootName: string;
|
|
18
|
+
/** The exact root start tag, `<metadata …>`. */
|
|
19
|
+
readonly rootStartTag: Uint8Array;
|
|
20
|
+
/** The root's attributes in order, values decoded. */
|
|
21
|
+
readonly rootAttributes: readonly IRpmXmlAttribute[];
|
|
22
|
+
/** The root's `xmlns` value; `''` when absent. */
|
|
23
|
+
readonly namespace: string;
|
|
24
|
+
/** The exact root end tag, `</metadata>`; empty for an empty-element root. */
|
|
25
|
+
readonly rootEndTag: Uint8Array;
|
|
26
|
+
/** The bytes after the root end tag. */
|
|
27
|
+
readonly trailer: Uint8Array;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Which repodata document `readRpmRepodata` reads; the caller names it from the repomd `type`. */
|
|
31
|
+
export type TRpmRepodataDocument = 'primary' | 'filelists' | 'other';
|
|
32
|
+
|
|
33
|
+
/** One `<file>` of a package. */
|
|
34
|
+
export interface IRpmRepodataFile {
|
|
35
|
+
readonly path: string;
|
|
36
|
+
/** The `type` attribute as written (`dir`, `ghost`); `''` when absent. */
|
|
37
|
+
readonly type: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One `<package>` unit of primary, filelists or other. */
|
|
41
|
+
export interface IRpmRepodataPackage {
|
|
42
|
+
/** The element from `<` of its start tag to `>` of its end tag (a view into `decoded`). */
|
|
43
|
+
readonly bytes: Uint8Array;
|
|
44
|
+
/** The element's offset in `decoded`. */
|
|
45
|
+
readonly offset: number;
|
|
46
|
+
/** primary: the text of `<checksum pkgid="YES">`; filelists and other: the `pkgid` attribute. */
|
|
47
|
+
readonly pkgid: string;
|
|
48
|
+
/** primary: the `<name>` and `<arch>` text; filelists and other: the attributes. `''` when absent. */
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly arch: string;
|
|
51
|
+
/** The `<version>` attributes `epoch`, `ver`, `rel`; `''` when absent. */
|
|
52
|
+
readonly epoch: string;
|
|
53
|
+
readonly version: string;
|
|
54
|
+
readonly release: string;
|
|
55
|
+
/** primary's `<size>` attributes (`''` when absent); null when there is no `<size>` element (always in filelists and other). */
|
|
56
|
+
readonly size: { readonly package: string; readonly installed: string; readonly archive: string } | null;
|
|
57
|
+
/** primary `<format><file>`, filelists `<file>`, in order; `[]` when none (always in `other`). */
|
|
58
|
+
readonly files: readonly IRpmRepodataFile[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A primary, filelists or other document read into its frame and packages. */
|
|
62
|
+
export interface IRpmRepodata extends IRpmXmlFrame {
|
|
63
|
+
/** The decompressed document (a copy for `'none'`); every `offset` indexes into it. */
|
|
64
|
+
readonly decoded: Uint8Array;
|
|
65
|
+
readonly document: TRpmRepodataDocument;
|
|
66
|
+
/** Every `<package>` in document order, duplicates included. */
|
|
67
|
+
readonly packages: readonly IRpmRepodataPackage[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A `<checksum>` or `<open-checksum>` of repomd. */
|
|
71
|
+
export interface IRpmRepomdChecksum {
|
|
72
|
+
/** The `type` attribute; `''` when absent. */
|
|
73
|
+
readonly type: string;
|
|
74
|
+
readonly value: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** One `<data>` unit of repomd. */
|
|
78
|
+
export interface IRpmRepomdData {
|
|
79
|
+
readonly bytes: Uint8Array;
|
|
80
|
+
readonly offset: number;
|
|
81
|
+
readonly type: string;
|
|
82
|
+
/** The `<location href>`. */
|
|
83
|
+
readonly location: string;
|
|
84
|
+
readonly checksum: IRpmRepomdChecksum;
|
|
85
|
+
readonly openChecksum: IRpmRepomdChecksum | null;
|
|
86
|
+
/** The `<timestamp>`, `<size>` and `<open-size>` text as written; null when absent. */
|
|
87
|
+
readonly timestamp: string | null;
|
|
88
|
+
readonly size: string | null;
|
|
89
|
+
readonly openSize: string | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A root child of repomd other than `<data>` (createrepo_c's `<revision>`, `<tags>`), raw. */
|
|
93
|
+
export interface IRpmRepomdElement {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly bytes: Uint8Array;
|
|
96
|
+
readonly offset: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A `repomd.xml` read into its frame, `<data>` units and other elements. */
|
|
100
|
+
export interface IRpmRepomd extends IRpmXmlFrame {
|
|
101
|
+
readonly data: readonly IRpmRepomdData[];
|
|
102
|
+
readonly otherElements: readonly IRpmRepomdElement[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One package of an `<update>`'s `<pkglist>`. */
|
|
106
|
+
export interface IRpmUpdateInfoPackage {
|
|
107
|
+
/** The enclosing `<collection short>`; `''` when absent. */
|
|
108
|
+
readonly collection: string;
|
|
109
|
+
/** The `<package>` attributes; `''` when absent. */
|
|
110
|
+
readonly name: string;
|
|
111
|
+
readonly epoch: string;
|
|
112
|
+
readonly version: string;
|
|
113
|
+
readonly release: string;
|
|
114
|
+
readonly arch: string;
|
|
115
|
+
readonly src: string;
|
|
116
|
+
/** The `<filename>` text; `''` when absent. */
|
|
117
|
+
readonly fileName: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** One `<update>` unit of updateinfo. */
|
|
121
|
+
export interface IRpmUpdateInfoUpdate {
|
|
122
|
+
readonly bytes: Uint8Array;
|
|
123
|
+
readonly offset: number;
|
|
124
|
+
/** The `<id>` text: the unit key. */
|
|
125
|
+
readonly id: string;
|
|
126
|
+
/** Every package of every collection, in order. */
|
|
127
|
+
readonly packages: readonly IRpmUpdateInfoPackage[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** An `updateinfo` document read into its frame and updates. */
|
|
131
|
+
export interface IRpmUpdateInfo extends IRpmXmlFrame {
|
|
132
|
+
/** The decompressed document (a copy for `'none'`); every `offset` indexes into it. */
|
|
133
|
+
readonly decoded: Uint8Array;
|
|
134
|
+
/** Every `<update>` in document order, duplicates included. */
|
|
135
|
+
readonly updates: readonly IRpmUpdateInfoUpdate[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const DOCUMENTS: ReadonlyMap<string, string> = new Map<TRpmRepodataDocument, string>([
|
|
139
|
+
['primary', 'metadata'],
|
|
140
|
+
['filelists', 'filelists'],
|
|
141
|
+
['other', 'otherdata'],
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Reads a `primary`, `filelists` or `other` document (`.xml`, `.xml.gz`,
|
|
146
|
+
* `.xml.xz`, `.xml.zst`; the caller names `compression` and `document`) into
|
|
147
|
+
* its frame and `<package>` units. The XML rules are the strict scanner's
|
|
148
|
+
* (see `IRpmXmlFrame`; any refusal is `malformed-index`): the root must be
|
|
149
|
+
* `<metadata>`, `<filelists>` or `<otherdata>` for the document, and its
|
|
150
|
+
* children `<package>` elements with only whitespace between them, which is
|
|
151
|
+
* not returned. A package without its key (primary `<checksum pkgid="YES">`,
|
|
152
|
+
* filelists and other `pkgid`) is `missing-field` (`pkgid`); a package with
|
|
153
|
+
* two of an element the view reads (`<name>`, `<arch>`, `<version>`, `<size>`,
|
|
154
|
+
* a key `<checksum>`) is `malformed-index`.
|
|
155
|
+
*
|
|
156
|
+
* Bounds: the decoded document by `maxIndexBytes`, packages and files per
|
|
157
|
+
* package by `maxEntries`, one package by `maxMetadataBytes`, one start tag
|
|
158
|
+
* by `maxHeaderBytes`. Duplicates are kept; all bytes are views into `decoded`.
|
|
159
|
+
*/
|
|
160
|
+
export async function readRpmRepodata(inputArg: {
|
|
161
|
+
bytes: Uint8Array;
|
|
162
|
+
compression: TLinuxCompression;
|
|
163
|
+
document: TRpmRepodataDocument;
|
|
164
|
+
limits?: Partial<ILinuxPackageLimits>;
|
|
165
|
+
}): Promise<IRpmRepodata> {
|
|
166
|
+
assertReaderInput(inputArg);
|
|
167
|
+
assertReaderBytes(inputArg.bytes);
|
|
168
|
+
assertLinuxCompression(inputArg.compression);
|
|
169
|
+
const document = inputArg.document;
|
|
170
|
+
const rootName = typeof document === 'string' ? DOCUMENTS.get(document) : undefined;
|
|
171
|
+
if (rootName === undefined) {
|
|
172
|
+
throw new LinuxRepoError('invalid-input', "document must be 'primary', 'filelists' or 'other'", {
|
|
173
|
+
field: 'document',
|
|
174
|
+
value: typeof document === 'string' ? document : typeof document,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const limits = resolveLinuxPackageLimits(inputArg.limits);
|
|
178
|
+
const decoded = await readIndexDocument(inputArg.bytes, inputArg.compression, limits);
|
|
179
|
+
const packages: IRpmRepodataPackage[] = [];
|
|
180
|
+
const frame = scanXmlDocument(decoded, {
|
|
181
|
+
rootName,
|
|
182
|
+
unitName: 'package',
|
|
183
|
+
keepOtherChildren: false,
|
|
184
|
+
limits,
|
|
185
|
+
createUnit: (startArg) => new PackageHandler(document, startArg, limits),
|
|
186
|
+
unit: (handlerArg, startArg, endArg) => {
|
|
187
|
+
packages.push((handlerArg as PackageHandler).result(decoded.subarray(startArg, endArg)));
|
|
188
|
+
},
|
|
189
|
+
other: () => undefined,
|
|
190
|
+
});
|
|
191
|
+
return { ...frameOf(decoded, rootName, frame), decoded, document, packages };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reads an `updateinfo` document into its frame and `<update>` units: the
|
|
196
|
+
* root must be `<updates>`, the unit key is the `<id>` text (none is
|
|
197
|
+
* `missing-field`, two are `malformed-index`), and the packages are the
|
|
198
|
+
* `<pkglist><collection short><package …><filename>` entries in order (a
|
|
199
|
+
* package with two `<filename>` elements is `malformed-index`). XML rules,
|
|
200
|
+
* bounds and ownership as `readRpmRepodata`; packages per update count against
|
|
201
|
+
* `maxEntries`.
|
|
202
|
+
*/
|
|
203
|
+
export async function readRpmUpdateInfo(inputArg: {
|
|
204
|
+
bytes: Uint8Array;
|
|
205
|
+
compression: TLinuxCompression;
|
|
206
|
+
limits?: Partial<ILinuxPackageLimits>;
|
|
207
|
+
}): Promise<IRpmUpdateInfo> {
|
|
208
|
+
assertReaderInput(inputArg);
|
|
209
|
+
assertReaderBytes(inputArg.bytes);
|
|
210
|
+
assertLinuxCompression(inputArg.compression);
|
|
211
|
+
const limits = resolveLinuxPackageLimits(inputArg.limits);
|
|
212
|
+
const decoded = await readIndexDocument(inputArg.bytes, inputArg.compression, limits);
|
|
213
|
+
const updates: IRpmUpdateInfoUpdate[] = [];
|
|
214
|
+
const frame = scanXmlDocument(decoded, {
|
|
215
|
+
rootName: 'updates',
|
|
216
|
+
unitName: 'update',
|
|
217
|
+
keepOtherChildren: false,
|
|
218
|
+
limits,
|
|
219
|
+
createUnit: (startArg) => new UpdateHandler(startArg, limits),
|
|
220
|
+
unit: (handlerArg, startArg, endArg) => {
|
|
221
|
+
updates.push((handlerArg as UpdateHandler).result(decoded.subarray(startArg, endArg)));
|
|
222
|
+
},
|
|
223
|
+
other: () => undefined,
|
|
224
|
+
});
|
|
225
|
+
return { ...frameOf(decoded, 'updates', frame), decoded, updates };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Reads a plain `repomd.xml` into its frame, `<data>` units and the other
|
|
230
|
+
* root children (kept raw, in `otherElements`). A `<data>` without a `type`
|
|
231
|
+
* attribute, `<checksum>` or `<location href>` is `missing-field`; a `<data>`
|
|
232
|
+
* with two of `<checksum>`, `<open-checksum>`, `<location>`, `<timestamp>`,
|
|
233
|
+
* `<size>` or `<open-size>` is `malformed-index`. XML rules and bounds as
|
|
234
|
+
* `readRpmRepodata` (the plain input counts against `maxIndexBytes`); all
|
|
235
|
+
* bytes are views into a copy of the input.
|
|
236
|
+
*/
|
|
237
|
+
export async function readRpmRepomd(inputArg: {
|
|
238
|
+
bytes: Uint8Array;
|
|
239
|
+
limits?: Partial<ILinuxPackageLimits>;
|
|
240
|
+
}): Promise<IRpmRepomd> {
|
|
241
|
+
assertReaderInput(inputArg);
|
|
242
|
+
assertReaderBytes(inputArg.bytes);
|
|
243
|
+
const limits = resolveLinuxPackageLimits(inputArg.limits);
|
|
244
|
+
const decoded = await readIndexDocument(inputArg.bytes, 'none', limits);
|
|
245
|
+
const data: IRpmRepomdData[] = [];
|
|
246
|
+
const otherElements: IRpmRepomdElement[] = [];
|
|
247
|
+
const frame = scanXmlDocument(decoded, {
|
|
248
|
+
rootName: 'repomd',
|
|
249
|
+
unitName: 'data',
|
|
250
|
+
keepOtherChildren: true,
|
|
251
|
+
limits,
|
|
252
|
+
createUnit: (startArg) => new RepomdDataHandler(startArg),
|
|
253
|
+
unit: (handlerArg, startArg, endArg) => {
|
|
254
|
+
data.push((handlerArg as RepomdDataHandler).result(decoded.subarray(startArg, endArg)));
|
|
255
|
+
},
|
|
256
|
+
other: (nameArg, startArg, endArg) => {
|
|
257
|
+
otherElements.push({ name: nameArg, bytes: decoded.subarray(startArg, endArg), offset: startArg });
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
return { ...frameOf(decoded, 'repomd', frame), data, otherElements };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function frameOf(decodedArg: Uint8Array, rootNameArg: string, frameArg: IXmlFrameScan): IRpmXmlFrame {
|
|
264
|
+
return {
|
|
265
|
+
prolog: decodedArg.subarray(0, frameArg.rootStart),
|
|
266
|
+
rootName: rootNameArg,
|
|
267
|
+
rootStartTag: decodedArg.subarray(frameArg.rootStart, frameArg.rootStartEnd),
|
|
268
|
+
rootAttributes: frameArg.rootAttributes.map((attribute) => ({ name: attribute.name, value: attribute.value })),
|
|
269
|
+
namespace: attributeOf(frameArg.rootAttributes, 'xmlns'),
|
|
270
|
+
rootEndTag: decodedArg.subarray(frameArg.rootEndStart, frameArg.rootEnd),
|
|
271
|
+
trailer: decodedArg.subarray(frameArg.rootEnd),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function attributeOf(attributesArg: readonly IXmlAttribute[], nameArg: string): string {
|
|
276
|
+
return attributesArg.find((attribute) => attribute.name === nameArg)?.value ?? '';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function hasAttribute(attributesArg: readonly IXmlAttribute[], nameArg: string): boolean {
|
|
280
|
+
return attributesArg.some((attribute) => attribute.name === nameArg);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The path of open element names inside one unit, for the handlers' context. */
|
|
284
|
+
abstract class UnitHandler implements IXmlUnitHandler {
|
|
285
|
+
protected readonly path: string[] = [];
|
|
286
|
+
|
|
287
|
+
constructor(protected readonly offset: number) {}
|
|
288
|
+
|
|
289
|
+
public start(depthArg: number, nameArg: string, attributesArg: readonly IXmlAttribute[]): boolean {
|
|
290
|
+
const capture = this.open(depthArg, nameArg, attributesArg);
|
|
291
|
+
this.path.push(nameArg);
|
|
292
|
+
return capture;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public end(depthArg: number, nameArg: string, textArg: string | null): void {
|
|
296
|
+
this.path.pop();
|
|
297
|
+
this.close(depthArg, nameArg, textArg);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The parent element's name at `depthArg` (the unit element at depth 1). */
|
|
301
|
+
protected parent(): string | undefined {
|
|
302
|
+
return this.path[this.path.length - 1];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
protected duplicate(whatArg: string): LinuxRepoError {
|
|
306
|
+
return new LinuxRepoError('malformed-index', `a unit holds two ${whatArg} elements`, { field: whatArg, value: this.offset });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
protected abstract open(depthArg: number, nameArg: string, attributesArg: readonly IXmlAttribute[]): boolean;
|
|
310
|
+
protected abstract close(depthArg: number, nameArg: string, textArg: string | null): void;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
class PackageHandler extends UnitHandler {
|
|
314
|
+
private pkgid: string | null = null;
|
|
315
|
+
private name: string | null = null;
|
|
316
|
+
private arch: string | null = null;
|
|
317
|
+
private version: { epoch: string; version: string; release: string } | null = null;
|
|
318
|
+
private size: { package: string; installed: string; archive: string } | null = null;
|
|
319
|
+
private readonly files: IRpmRepodataFile[] = [];
|
|
320
|
+
private fileType = '';
|
|
321
|
+
|
|
322
|
+
constructor(
|
|
323
|
+
private readonly document: TRpmRepodataDocument,
|
|
324
|
+
offsetArg: number,
|
|
325
|
+
private readonly limits: Readonly<ILinuxPackageLimits>,
|
|
326
|
+
) {
|
|
327
|
+
super(offsetArg);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
protected open(depthArg: number, nameArg: string, attributesArg: readonly IXmlAttribute[]): boolean {
|
|
331
|
+
if (depthArg === 0) {
|
|
332
|
+
if (this.document !== 'primary') {
|
|
333
|
+
if (!hasAttribute(attributesArg, 'pkgid')) {
|
|
334
|
+
throw this.missingKey();
|
|
335
|
+
}
|
|
336
|
+
this.pkgid = attributeOf(attributesArg, 'pkgid');
|
|
337
|
+
this.name = attributeOf(attributesArg, 'name');
|
|
338
|
+
this.arch = attributeOf(attributesArg, 'arch');
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
const parent = this.parent();
|
|
343
|
+
if (depthArg === 1) {
|
|
344
|
+
if (nameArg === 'version') {
|
|
345
|
+
if (this.version !== null) {
|
|
346
|
+
throw this.duplicate('<version>');
|
|
347
|
+
}
|
|
348
|
+
this.version = {
|
|
349
|
+
epoch: attributeOf(attributesArg, 'epoch'),
|
|
350
|
+
version: attributeOf(attributesArg, 'ver'),
|
|
351
|
+
release: attributeOf(attributesArg, 'rel'),
|
|
352
|
+
};
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
if (this.document === 'primary') {
|
|
356
|
+
if (nameArg === 'name' || nameArg === 'arch') {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
if (nameArg === 'checksum') {
|
|
360
|
+
return attributeOf(attributesArg, 'pkgid') === 'YES';
|
|
361
|
+
}
|
|
362
|
+
if (nameArg === 'size') {
|
|
363
|
+
if (this.size !== null) {
|
|
364
|
+
throw this.duplicate('<size>');
|
|
365
|
+
}
|
|
366
|
+
this.size = {
|
|
367
|
+
package: attributeOf(attributesArg, 'package'),
|
|
368
|
+
installed: attributeOf(attributesArg, 'installed'),
|
|
369
|
+
archive: attributeOf(attributesArg, 'archive'),
|
|
370
|
+
};
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
if (this.document === 'filelists' && nameArg === 'file') {
|
|
376
|
+
return this.openFile(attributesArg);
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
if (depthArg === 2 && this.document === 'primary' && parent === 'format' && nameArg === 'file') {
|
|
381
|
+
return this.openFile(attributesArg);
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
protected close(depthArg: number, nameArg: string, textArg: string | null): void {
|
|
387
|
+
if (textArg === null) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (nameArg === 'file') {
|
|
391
|
+
this.files.push({ path: textArg, type: this.fileType });
|
|
392
|
+
} else if (nameArg === 'name') {
|
|
393
|
+
if (this.name !== null) {
|
|
394
|
+
throw this.duplicate('<name>');
|
|
395
|
+
}
|
|
396
|
+
this.name = textArg;
|
|
397
|
+
} else if (nameArg === 'arch') {
|
|
398
|
+
if (this.arch !== null) {
|
|
399
|
+
throw this.duplicate('<arch>');
|
|
400
|
+
}
|
|
401
|
+
this.arch = textArg;
|
|
402
|
+
} else if (nameArg === 'checksum') {
|
|
403
|
+
if (this.pkgid !== null) {
|
|
404
|
+
throw this.duplicate('<checksum pkgid="YES">');
|
|
405
|
+
}
|
|
406
|
+
this.pkgid = textArg;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private openFile(attributesArg: readonly IXmlAttribute[]): boolean {
|
|
411
|
+
if (this.files.length + 1 > this.limits.maxEntries) {
|
|
412
|
+
throw limitExceeded('maxEntries', this.limits.maxEntries, 'files per package');
|
|
413
|
+
}
|
|
414
|
+
this.fileType = attributeOf(attributesArg, 'type');
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private missingKey(): LinuxRepoError {
|
|
419
|
+
return new LinuxRepoError('missing-field', `a ${this.document} <package> has no pkgid`, { field: 'pkgid', value: this.offset });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
public result(bytesArg: Uint8Array): IRpmRepodataPackage {
|
|
423
|
+
if (this.pkgid === null) {
|
|
424
|
+
throw this.missingKey();
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
bytes: bytesArg,
|
|
428
|
+
offset: this.offset,
|
|
429
|
+
pkgid: this.pkgid,
|
|
430
|
+
name: this.name ?? '',
|
|
431
|
+
arch: this.arch ?? '',
|
|
432
|
+
epoch: this.version?.epoch ?? '',
|
|
433
|
+
version: this.version?.version ?? '',
|
|
434
|
+
release: this.version?.release ?? '',
|
|
435
|
+
size: this.size,
|
|
436
|
+
files: this.files,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
class UpdateHandler extends UnitHandler {
|
|
442
|
+
private id: string | null = null;
|
|
443
|
+
private collection = '';
|
|
444
|
+
private pending: { attributes: readonly IXmlAttribute[]; fileName: string | null } | null = null;
|
|
445
|
+
private readonly packages: IRpmUpdateInfoPackage[] = [];
|
|
446
|
+
|
|
447
|
+
constructor(
|
|
448
|
+
offsetArg: number,
|
|
449
|
+
private readonly limits: Readonly<ILinuxPackageLimits>,
|
|
450
|
+
) {
|
|
451
|
+
super(offsetArg);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
protected open(depthArg: number, nameArg: string, attributesArg: readonly IXmlAttribute[]): boolean {
|
|
455
|
+
const parent = this.parent();
|
|
456
|
+
if (depthArg === 1 && nameArg === 'id') {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
if (depthArg === 2 && parent === 'pkglist' && nameArg === 'collection') {
|
|
460
|
+
this.collection = attributeOf(attributesArg, 'short');
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
if (depthArg === 3 && parent === 'collection' && this.path[1] === 'pkglist' && nameArg === 'package') {
|
|
464
|
+
if (this.packages.length + 1 > this.limits.maxEntries) {
|
|
465
|
+
throw limitExceeded('maxEntries', this.limits.maxEntries, 'packages per update');
|
|
466
|
+
}
|
|
467
|
+
this.pending = { attributes: attributesArg, fileName: null };
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
if (depthArg === 4 && parent === 'package' && this.pending !== null && nameArg === 'filename') {
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
protected close(depthArg: number, nameArg: string, textArg: string | null): void {
|
|
477
|
+
if (depthArg === 1 && nameArg === 'id' && textArg !== null) {
|
|
478
|
+
if (this.id !== null) {
|
|
479
|
+
throw this.duplicate('<id>');
|
|
480
|
+
}
|
|
481
|
+
this.id = textArg;
|
|
482
|
+
} else if (depthArg === 4 && nameArg === 'filename' && textArg !== null && this.pending !== null) {
|
|
483
|
+
if (this.pending.fileName !== null) {
|
|
484
|
+
throw this.duplicate('<filename>');
|
|
485
|
+
}
|
|
486
|
+
this.pending.fileName = textArg;
|
|
487
|
+
} else if (depthArg === 3 && nameArg === 'package' && this.pending !== null) {
|
|
488
|
+
const attributes = this.pending.attributes;
|
|
489
|
+
this.packages.push({
|
|
490
|
+
collection: this.collection,
|
|
491
|
+
name: attributeOf(attributes, 'name'),
|
|
492
|
+
epoch: attributeOf(attributes, 'epoch'),
|
|
493
|
+
version: attributeOf(attributes, 'version'),
|
|
494
|
+
release: attributeOf(attributes, 'release'),
|
|
495
|
+
arch: attributeOf(attributes, 'arch'),
|
|
496
|
+
src: attributeOf(attributes, 'src'),
|
|
497
|
+
fileName: this.pending.fileName ?? '',
|
|
498
|
+
});
|
|
499
|
+
this.pending = null;
|
|
500
|
+
} else if (depthArg === 2 && nameArg === 'collection') {
|
|
501
|
+
this.collection = '';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public result(bytesArg: Uint8Array): IRpmUpdateInfoUpdate {
|
|
506
|
+
if (this.id === null) {
|
|
507
|
+
throw new LinuxRepoError('missing-field', 'an <update> has no <id>', { field: 'id', value: this.offset });
|
|
508
|
+
}
|
|
509
|
+
return { bytes: bytesArg, offset: this.offset, id: this.id, packages: this.packages };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
class RepomdDataHandler extends UnitHandler {
|
|
514
|
+
private type: string | null = null;
|
|
515
|
+
private readonly values = new Map<string, { type: string; text: string | null }>();
|
|
516
|
+
private pendingType = '';
|
|
517
|
+
|
|
518
|
+
protected open(depthArg: number, nameArg: string, attributesArg: readonly IXmlAttribute[]): boolean {
|
|
519
|
+
if (depthArg === 0) {
|
|
520
|
+
if (hasAttribute(attributesArg, 'type')) {
|
|
521
|
+
this.type = attributeOf(attributesArg, 'type');
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
if (depthArg !== 1) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
switch (nameArg) {
|
|
529
|
+
case 'checksum':
|
|
530
|
+
case 'open-checksum':
|
|
531
|
+
case 'timestamp':
|
|
532
|
+
case 'size':
|
|
533
|
+
case 'open-size':
|
|
534
|
+
case 'location':
|
|
535
|
+
if (this.values.has(nameArg)) {
|
|
536
|
+
throw this.duplicate(`<${nameArg}>`);
|
|
537
|
+
}
|
|
538
|
+
if (nameArg === 'location') {
|
|
539
|
+
this.values.set(nameArg, { type: '', text: hasAttribute(attributesArg, 'href') ? attributeOf(attributesArg, 'href') : null });
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
this.pendingType = attributeOf(attributesArg, 'type');
|
|
543
|
+
return true;
|
|
544
|
+
default:
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
protected close(depthArg: number, nameArg: string, textArg: string | null): void {
|
|
550
|
+
if (depthArg === 1 && textArg !== null) {
|
|
551
|
+
this.values.set(nameArg, { type: this.pendingType, text: textArg });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
public result(bytesArg: Uint8Array): IRpmRepomdData {
|
|
556
|
+
const missing = (fieldArg: string) =>
|
|
557
|
+
new LinuxRepoError('missing-field', `a repomd <data> has no ${fieldArg}`, { field: fieldArg, value: this.offset });
|
|
558
|
+
if (this.type === null) {
|
|
559
|
+
throw missing('type');
|
|
560
|
+
}
|
|
561
|
+
const checksum = this.values.get('checksum');
|
|
562
|
+
if (checksum === undefined || checksum.text === null) {
|
|
563
|
+
throw missing('checksum');
|
|
564
|
+
}
|
|
565
|
+
const location = this.values.get('location');
|
|
566
|
+
if (location === undefined || location.text === null) {
|
|
567
|
+
throw missing('location');
|
|
568
|
+
}
|
|
569
|
+
const openChecksum = this.values.get('open-checksum');
|
|
570
|
+
return {
|
|
571
|
+
bytes: bytesArg,
|
|
572
|
+
offset: this.offset,
|
|
573
|
+
type: this.type,
|
|
574
|
+
location: location.text,
|
|
575
|
+
checksum: { type: checksum.type, value: checksum.text },
|
|
576
|
+
openChecksum: openChecksum === undefined || openChecksum.text === null ? null : { type: openChecksum.type, value: openChecksum.text },
|
|
577
|
+
timestamp: this.values.get('timestamp')?.text ?? null,
|
|
578
|
+
size: this.values.get('size')?.text ?? null,
|
|
579
|
+
openSize: this.values.get('open-size')?.text ?? null,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|