@push.rocks/smartlinuxrepo 1.1.0 → 1.1.2
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/arch/reader.js +2 -2
- package/dist_ts/debian/control.d.ts +7 -1
- package/dist_ts/debian/control.js +26 -4
- package/dist_ts/debian/indexbuilder.js +8 -10
- package/dist_ts/debian/reader.js +6 -2
- package/dist_ts/debian/rules.d.ts +7 -0
- package/dist_ts/debian/rules.js +20 -1
- package/dist_ts/readers/decompress.d.ts +13 -3
- package/dist_ts/readers/decompress.js +401 -26
- package/dist_ts/shared/architecture.d.ts +5 -1
- package/dist_ts/shared/architecture.js +6 -2
- package/dist_ts/shared/limits.d.ts +1 -1
- package/package.json +1 -1
- package/readme.md +4 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/arch/reader.ts +1 -1
- package/ts/debian/control.ts +25 -3
- package/ts/debian/indexbuilder.ts +11 -9
- package/ts/debian/reader.ts +5 -1
- package/ts/debian/rules.ts +21 -0
- package/ts/readers/decompress.ts +441 -29
- package/ts/shared/architecture.ts +5 -1
- package/ts/shared/limits.ts +1 -1
package/ts/arch/reader.ts
CHANGED
|
@@ -53,7 +53,7 @@ export async function readArchPackage(
|
|
|
53
53
|
const walked = await withDecompressingReader(
|
|
54
54
|
source,
|
|
55
55
|
container.decoder,
|
|
56
|
-
{ maxOutputBytes: limits.maxPayloadBytes, outputLimit: 'maxPayloadBytes' },
|
|
56
|
+
{ maxOutputBytes: limits.maxPayloadBytes, outputLimit: 'maxPayloadBytes', maxZstdFrames: limits.maxEntries },
|
|
57
57
|
async (payloadArg) => {
|
|
58
58
|
const result = await walkPayload(
|
|
59
59
|
new TarReader(payloadArg, { maxEntries: limits.maxEntries, maxHeaderBytes: limits.maxHeaderBytes }),
|
package/ts/debian/control.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { isIndexableArchitecture } from '../shared/architecture.js';
|
|
1
2
|
import { LinuxRepoError, limitExceeded } from '../shared/errors.js';
|
|
2
3
|
import { isValidUrl } from '../shared/url.js';
|
|
3
4
|
import type { IDebianPackageMetadata } from '../shared/types.js';
|
|
4
5
|
import { fromByteString, goTrimSpace, toByteString } from './gostring.js';
|
|
6
|
+
import { hasPoolFileNameRefusedCharacter } from './rules.js';
|
|
5
7
|
import { parseMailAddress } from './mailaddress.js';
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -29,7 +31,13 @@ export interface IDebianControl {
|
|
|
29
31
|
* verbatim, and the Depends text split on `,` (`['']` when there is none).
|
|
30
32
|
*
|
|
31
33
|
* Gitea's scanner refuses lines over 64 KiB; the codec accepts lines up to
|
|
32
|
-
* `maxLineBytes` (`maxHeaderBytes`), which is at least that.
|
|
34
|
+
* `maxLineBytes` (`maxHeaderBytes`), which is at least that. A version that
|
|
35
|
+
* matches Gitea's pattern but holds `/` or `?` (both inside the revision's
|
|
36
|
+
* range) is refused with `invalid-field` (`Version`): no pool file name can
|
|
37
|
+
* hold it (`hasPoolFileNameRefusedCharacter`), where Gitea accepts the upload
|
|
38
|
+
* and then cannot serve it. An Architecture that fails `isIndexableArchitecture`
|
|
39
|
+
* or `hasPoolFileNameRefusedCharacter` (decoded) is refused with
|
|
40
|
+
* `invalid-field` (`Architecture`), where Gitea accepts any non-empty value.
|
|
33
41
|
*/
|
|
34
42
|
export function parseDebianControlFile(bytesArg: Uint8Array, maxLineBytesArg: number): IDebianControl {
|
|
35
43
|
const text = toByteString(bytesArg);
|
|
@@ -121,7 +129,10 @@ export function parseDebianControlFile(bytesArg: Uint8Array, maxLineBytesArg: nu
|
|
|
121
129
|
if (version === '') {
|
|
122
130
|
throw new LinuxRepoError('missing-field', 'the control file has no Version field', { field: 'Version' });
|
|
123
131
|
}
|
|
124
|
-
|
|
132
|
+
// a departure from Gitea's version pattern: the revision range admits
|
|
133
|
+
// `/` and `?`, which the pool file name cannot hold, so the version is
|
|
134
|
+
// refused here rather than by buildDebianIndex
|
|
135
|
+
if (!VERSION_PATTERN.test(version) || hasPoolFileNameRefusedCharacter(version)) {
|
|
125
136
|
throw new LinuxRepoError('invalid-field', 'the package version is invalid', {
|
|
126
137
|
field: 'Version',
|
|
127
138
|
value: fromByteString(version),
|
|
@@ -132,11 +143,22 @@ export function parseDebianControlFile(bytesArg: Uint8Array, maxLineBytesArg: nu
|
|
|
132
143
|
field: 'Architecture',
|
|
133
144
|
});
|
|
134
145
|
}
|
|
146
|
+
// a departure from Gitea, which accepts any non-empty Architecture: the
|
|
147
|
+
// file name embeds it, so it must pass the builder's rules; checked on the
|
|
148
|
+
// decoded text the record carries (on the byte string, `\s` and `\p{Cc}`
|
|
149
|
+
// would match the UTF-8 continuation bytes 0x85 and 0xA0)
|
|
150
|
+
const decodedArchitecture = fromByteString(architecture);
|
|
151
|
+
if (!isIndexableArchitecture(decodedArchitecture) || hasPoolFileNameRefusedCharacter(decodedArchitecture)) {
|
|
152
|
+
throw new LinuxRepoError('invalid-field', 'the package architecture cannot name an index path or pool file', {
|
|
153
|
+
field: 'Architecture',
|
|
154
|
+
value: decodedArchitecture,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
135
157
|
|
|
136
158
|
return {
|
|
137
159
|
name,
|
|
138
160
|
version,
|
|
139
|
-
architecture:
|
|
161
|
+
architecture: decodedArchitecture,
|
|
140
162
|
metadata: {
|
|
141
163
|
control: fromByteString(goTrimSpace(control)),
|
|
142
164
|
maintainer: fromByteString(maintainer),
|
|
@@ -2,11 +2,11 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import { isIndexableArchitecture } from '../shared/architecture.js';
|
|
3
3
|
import { LinuxRepoError } from '../shared/errors.js';
|
|
4
4
|
import type { IDebianPackageRecord, ILinuxIndexFile } from '../shared/types.js';
|
|
5
|
-
import {
|
|
5
|
+
import { assertString, assertTimestamp } from '../shared/validation.js';
|
|
6
6
|
import type { IOpenPgpSigner } from '../signing/signers.js';
|
|
7
7
|
import { createCleartextSignedMessage, createDetachedArmoredSignature } from '../signing/openpgpframing.js';
|
|
8
8
|
import { compareUtf8, goTrimSpace, utf8ByteString } from './gostring.js';
|
|
9
|
-
import { DEBIAN_SYMBOL_PATTERN, isDebianReleaseValue, isDebianSymbol } from './rules.js';
|
|
9
|
+
import { DEBIAN_SYMBOL_PATTERN, hasPoolFileNameRefusedCharacter, isDebianReleaseValue, isDebianSymbol } from './rules.js';
|
|
10
10
|
|
|
11
11
|
/** The fields the `Packages` stanza writes itself; a control file's own copies are dropped. */
|
|
12
12
|
const INDEX_OWNED_FIELDS = new Set(['filename', 'size', 'md5sum', 'sha1', 'sha256', 'sha512']);
|
|
@@ -234,7 +234,8 @@ export function renderDebianPackagesStanza(input: {
|
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
236
|
* Refuses a record the index cannot list: wrong format, an architecture that
|
|
237
|
-
* is not a path segment, a file name
|
|
237
|
+
* is not a path segment, a file name apt cannot fetch as one pool path
|
|
238
|
+
* segment (`hasPoolFileNameRefusedCharacter`), digests that are not
|
|
238
239
|
* hex of the right length, or a control text that is not one stanza (empty,
|
|
239
240
|
* or holding a blank line that would start another stanza).
|
|
240
241
|
*/
|
|
@@ -249,12 +250,13 @@ function assertRecord(recordArg: IDebianPackageRecord): void {
|
|
|
249
250
|
{ field: 'record.architecture', value: String(recordArg.fileName) },
|
|
250
251
|
);
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
throw new LinuxRepoError(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
assertString(recordArg.fileName, 'record.fileName');
|
|
254
|
+
if (recordArg.fileName === '' || hasPoolFileNameRefusedCharacter(recordArg.fileName)) {
|
|
255
|
+
throw new LinuxRepoError(
|
|
256
|
+
'invalid-input',
|
|
257
|
+
'record.fileName must be non-empty and contain no "/", "#", "?", whitespace or control characters',
|
|
258
|
+
{ field: 'record.fileName', value: recordArg.fileName },
|
|
259
|
+
);
|
|
258
260
|
}
|
|
259
261
|
const digests = recordArg.digests;
|
|
260
262
|
if (digests === null || typeof digests !== 'object' || !Number.isSafeInteger(digests.size) || digests.size < 0) {
|
package/ts/debian/reader.ts
CHANGED
|
@@ -89,7 +89,11 @@ async function readControl(inputArg: PackageByteSource, limitsArg: Readonly<ILin
|
|
|
89
89
|
const control = await withDecompressingReader(
|
|
90
90
|
member.body,
|
|
91
91
|
compression,
|
|
92
|
-
{
|
|
92
|
+
{
|
|
93
|
+
maxOutputBytes: limitsArg.maxDecompressedMetadataBytes,
|
|
94
|
+
outputLimit: 'maxDecompressedMetadataBytes',
|
|
95
|
+
maxZstdFrames: limitsArg.maxEntries,
|
|
96
|
+
},
|
|
93
97
|
async (decompressed) => {
|
|
94
98
|
const tar = new TarReader(decompressed, {
|
|
95
99
|
maxEntries: limitsArg.maxEntries,
|
package/ts/debian/rules.ts
CHANGED
|
@@ -21,3 +21,24 @@ export function isDebianSymbol(valueArg: string): boolean {
|
|
|
21
21
|
export function isDebianReleaseValue(valueArg: string): boolean {
|
|
22
22
|
return typeof valueArg === 'string' && valueArg !== '' && !hasControlCharacter(valueArg) && valueArg === valueArg.trim();
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The characters a pool file name (`<name>_<version>_<architecture>.deb`,
|
|
27
|
+
* written as a `Packages` `Filename`) must not hold: `/`, which would make it
|
|
28
|
+
* more than one path segment; whitespace and control characters, which split
|
|
29
|
+
* or end the field; and `#` and `?`, which apt sends unescaped in the request
|
|
30
|
+
* path (apt 2.6.1 `pkgAcquire::URIEncode` escapes only `%`, `+`, `~`, space,
|
|
31
|
+
* control and non-ASCII bytes), so a server reads the rest as a fragment or a
|
|
32
|
+
* query and apt cannot fetch the file.
|
|
33
|
+
*/
|
|
34
|
+
const POOL_FILE_NAME_REFUSED_PATTERN = /[/#?\s\p{Cc}]/u;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether a text holds a character a pool file name must not hold. The one
|
|
38
|
+
* rule both `buildDebianIndex` (on `record.fileName`) and `readDebianPackage`
|
|
39
|
+
* (on the version and the decoded architecture) apply, so the reader never
|
|
40
|
+
* returns a record the builder refuses for its file name.
|
|
41
|
+
*/
|
|
42
|
+
export function hasPoolFileNameRefusedCharacter(textArg: string): boolean {
|
|
43
|
+
return POOL_FILE_NAME_REFUSED_PATTERN.test(textArg);
|
|
44
|
+
}
|