buninu-linux 0.17.60 → 0.18.62
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/NOTICE.md +46 -46
- package/README.md +4 -4
- package/README.zh.md +4 -4
- package/initramfs/bin/tar +32 -15
- package/initramfs/bin/unzip +74 -19
- package/initramfs/buninu/CHANGELOG.md +19 -0
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +21 -0
- package/initramfs/buninu/apps/bunmsh/package.json +1 -1
- package/initramfs/buninu/apps/bunmsh/src/find.js +135 -56
- package/initramfs/buninu/apps/bunmsh/src/main.js +12 -1
- package/initramfs/buninu/apps/bunmsh/src/shell.js +8 -2
- package/initramfs/buninu/apps/bunmsh/test/find.test.js +103 -0
- package/initramfs/buninu/apps/bunmsh/test/interactive-typeahead.test.js +40 -0
- package/initramfs/buninu/apps/bunmsh/test/posix-bootstrap.test.js +9 -5
- package/initramfs/buninu/apps/bunmsh/test/reference.test.js +2 -5
- package/initramfs/buninu/apps/bunmsh/test/shell2.test.js +6 -5
- package/initramfs/buninu/apps/bunmsh/test/shell3.test.js +8 -7
- package/initramfs/buninu/apps/bunmsh/test/support/terminal.js +10 -0
- package/initramfs/buninu/apps/jsgotty/bunfig.toml +3 -0
- package/initramfs/buninu/apps/jsgotty/gotty.js +14 -1
- package/initramfs/buninu/apps/jsgotty/kitty-flow.js +40 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
- package/initramfs/buninu/apps/jsgotty/package.json +1 -1
- package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +32 -4
- package/initramfs/buninu/apps/jsgotty/tests/kitty-flow-browser.test.js +43 -0
- package/initramfs/buninu/apps/jsgotty/tests/kitty-flow.test.js +55 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/fps-collector.mjs +18 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/fps-debug.js +31 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/js-eval-show-fps.js +35 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/kitty-background-debug.js +24 -0
- package/initramfs/buninu/package.json +1 -1
- package/package.json +1 -1
- package/scripts/arch.js +5 -5
- package/scripts/arch.sh +5 -5
- package/scripts/fetch.js +50 -15
- package/scripts/npm-packages.md +11 -11
- package/test/archive-bounds.test.js +128 -0
- package/test/fetch-npm.test.js +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { afterAll, expect, test } from 'bun:test';
|
|
2
|
+
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { deflateRawSync, gzipSync } from 'node:zlib';
|
|
7
|
+
import { crc32 } from '../initramfs/lib/crc32.js';
|
|
8
|
+
const root=mkdtempSync(join(tmpdir(),'buninu-archive-bounds-'));
|
|
9
|
+
afterAll(()=>rmSync(root,{recursive:true,force:true}));
|
|
10
|
+
let serial=0;
|
|
11
|
+
function run(tool, bytes, flags) {
|
|
12
|
+
const file=join(root,`${++serial}.${tool==='tar'?'tar':'zip'}`);writeFileSync(file,bytes);
|
|
13
|
+
const result=spawnSync(process.execPath,[join(import.meta.dir,'../initramfs/bin',tool),...flags,file],{encoding:'utf8',timeout:5000});
|
|
14
|
+
expect(result.error).toBeUndefined();return result;
|
|
15
|
+
}
|
|
16
|
+
function header(name, size, type='0') {
|
|
17
|
+
const b=Buffer.alloc(512);b.write(name,0,100);b.write('0000644\0',100);
|
|
18
|
+
b.write('0000000\0',108);b.write('0000000\0',116);b.write(size,124,12);
|
|
19
|
+
b.write('00000000000\0',136);b.fill(32,148,156);b.write(type,156);b.write('ustar\0',257);b.write('00',263);
|
|
20
|
+
b.write(b.reduce((a,v)=>a+v,0).toString(8).padStart(6,'0')+'\0 ',148);return b;
|
|
21
|
+
}
|
|
22
|
+
function member(name,data='',type='0') {
|
|
23
|
+
const b=Buffer.from(data);return Buffer.concat([header(name,b.length.toString(8),type),b,Buffer.alloc((512-b.length%512)%512)]);
|
|
24
|
+
}
|
|
25
|
+
function pax(key,value) {
|
|
26
|
+
const body=Buffer.from(` ${key}=${value}\n`);let n=body.length+1;
|
|
27
|
+
while(String(n).length+body.length!==n)n=String(n).length+body.length;
|
|
28
|
+
return Buffer.concat([Buffer.from(String(n)),body]);
|
|
29
|
+
}
|
|
30
|
+
const tar=Buffer.concat([member('hello.txt','hello'),Buffer.alloc(1024)]);
|
|
31
|
+
for(const encoding of ['plain','gzip','zstd']) test(`tar lists and extracts ${encoding}`,()=>{
|
|
32
|
+
const bytes=encoding==='plain'?tar:encoding==='gzip'?gzipSync(tar):Bun.zstdCompressSync(tar);
|
|
33
|
+
const listed=run('tar',bytes,['-tf']);expect(listed.status).toBe(0);expect(listed.stdout).toContain('hello.txt');
|
|
34
|
+
const dest=join(root,`extract-${encoding}`);const result=run('tar',bytes,['-C',dest,'-xf']);
|
|
35
|
+
expect(result.status).toBe(0);expect(readFileSync(join(dest,'hello.txt'),'utf8')).toBe('hello');
|
|
36
|
+
});
|
|
37
|
+
for(const size of ['-1000','77777777777','1x','NaN']) test(`tar refuses size ${size}`,()=>{
|
|
38
|
+
const result=run('tar',header('bad',size),['-tf']);expect(result.status).not.toBe(0);expect(result.stderr).toContain('tar:');
|
|
39
|
+
});
|
|
40
|
+
for(const value of ['-512','NaN','Infinity','9007199254740993']) test(`tar refuses pax size ${value}`,()=>{
|
|
41
|
+
const bytes=Buffer.concat([member('pax',pax('size',value),'x'),header('file','0')]);
|
|
42
|
+
expect(run('tar',bytes,['-tf']).status).not.toBe(0);
|
|
43
|
+
});
|
|
44
|
+
test('tar refuses truncated padding, trailing header and later corrupt checksum',()=>{
|
|
45
|
+
for(const bytes of [Buffer.concat([header('file','1'),Buffer.from('x')]),Buffer.concat([tar,Buffer.from('x')]),Buffer.concat([tar,Buffer.alloc(512,42)])])
|
|
46
|
+
expect(run('tar',bytes,['-tf']).status).not.toBe(0);
|
|
47
|
+
});
|
|
48
|
+
test('tar refuses malformed pax records',()=>{
|
|
49
|
+
for(const data of ['0 size=0\n','999 size=0\n','xx size=0\n','1 size=0\n'])
|
|
50
|
+
expect(run('tar',Buffer.concat([member('pax',data,'x'),member('file')]),['-tf']).status).not.toBe(0);
|
|
51
|
+
});
|
|
52
|
+
test('tar preserves concatenated streams and UTF8 pax names',()=>{
|
|
53
|
+
const bytes=Buffer.concat([tar,member('pax',pax('path','中文.txt'),'x'),member('name','x'),Buffer.alloc(1024)]);
|
|
54
|
+
const listed=run('tar',bytes,['-tf']);expect(listed.status).toBe(0);expect(listed.stdout).toContain('中文.txt');
|
|
55
|
+
const dest=join(root,'concat');expect(run('tar',bytes,['-C',dest,'-xf']).status).toBe(0);
|
|
56
|
+
expect(readFileSync(join(dest,'中文.txt'),'utf8')).toBe('x');
|
|
57
|
+
});
|
|
58
|
+
function zip({method=0,zip64=false,comment=''}={}) {
|
|
59
|
+
const name=Buffer.from('hello.txt'), body=Buffer.from('hello'),raw=method===8?deflateRawSync(body):body;
|
|
60
|
+
const local=Buffer.alloc(30);local.writeUInt32LE(0x04034b50);local.writeUInt16LE(20,4);local.writeUInt16LE(method,8);
|
|
61
|
+
local.writeUInt32LE(crc32(body),14);local.writeUInt32LE(raw.length,18);local.writeUInt32LE(body.length,22);local.writeUInt16LE(name.length,26);
|
|
62
|
+
const start=Buffer.concat([local,name,raw]),h=Buffer.alloc(46),extra=Buffer.alloc(zip64?28:0);
|
|
63
|
+
h.writeUInt32LE(0x02014b50);h.writeUInt16LE(method,10);h.writeUInt32LE(crc32(body),16);
|
|
64
|
+
h.writeUInt32LE(zip64?0xffffffff:raw.length,20);h.writeUInt32LE(zip64?0xffffffff:body.length,24);
|
|
65
|
+
h.writeUInt16LE(name.length,28);h.writeUInt16LE(extra.length,30);h.writeUInt32LE(zip64?0xffffffff:0,42);
|
|
66
|
+
if(zip64){extra.writeUInt16LE(1);extra.writeUInt16LE(24,2);extra.writeBigUInt64LE(5n,4);extra.writeBigUInt64LE(BigInt(raw.length),12);}
|
|
67
|
+
const central=Buffer.concat([h,name,extra]),end=Buffer.alloc(22),extension=[];
|
|
68
|
+
if(zip64){const z=Buffer.alloc(56),l=Buffer.alloc(20);z.writeUInt32LE(0x06064b50);z.writeBigUInt64LE(44n,4);
|
|
69
|
+
z.writeBigUInt64LE(1n,24);z.writeBigUInt64LE(1n,32);z.writeBigUInt64LE(BigInt(central.length),40);z.writeBigUInt64LE(BigInt(start.length),48);
|
|
70
|
+
l.writeUInt32LE(0x07064b50);l.writeBigUInt64LE(BigInt(start.length+central.length),8);l.writeUInt32LE(1,16);extension.push(z,l);}
|
|
71
|
+
end.writeUInt32LE(0x06054b50);end.writeUInt16LE(zip64?65535:1,8);end.writeUInt16LE(zip64?65535:1,10);
|
|
72
|
+
end.writeUInt32LE(zip64?0xffffffff:central.length,12);end.writeUInt32LE(zip64?0xffffffff:start.length,16);end.writeUInt16LE(Buffer.byteLength(comment),20);
|
|
73
|
+
return {bytes:Buffer.concat([start,central,...extension,end,Buffer.from(comment)]),central:start.length,extra:start.length+46+name.length};
|
|
74
|
+
}
|
|
75
|
+
for(const method of [0,8]) for(const zip64 of [false,true]) test(`zip method ${method}, zip64 ${zip64}: list, test, pipe, extract`,()=>{
|
|
76
|
+
const {bytes}=zip({method,zip64});
|
|
77
|
+
for(const flag of ['-l','-t','-p']) {const r=run('unzip',bytes,[flag]);expect(r.status).toBe(0);expect(r.stdout).toContain(flag==='-p'?'hello':flag==='-l'?'hello.txt':'OK');}
|
|
78
|
+
const dest=join(root,`zip-${method}-${zip64}`);expect(run('unzip',bytes,['-d',dest]).status).toBe(0);
|
|
79
|
+
expect(readFileSync(join(dest,'hello.txt'),'utf8')).toBe('hello');
|
|
80
|
+
});
|
|
81
|
+
const mutations={
|
|
82
|
+
'directory allocation':(b,c,e)=>b.writeUInt32LE(0xfffffff0,b.length-10),
|
|
83
|
+
'entry count':(b,c,e)=>{b.writeUInt16LE(200,b.length-14);b.writeUInt16LE(200,b.length-12)},
|
|
84
|
+
'name overrun':(b,c)=>b.writeUInt16LE(65000,c+28),
|
|
85
|
+
'extra record overrun':(b,c,e)=>b.writeUInt16LE(65535,e+2),
|
|
86
|
+
'short ZIP64 field':(b,c,e)=>b.writeUInt16LE(1,e+2),
|
|
87
|
+
'unsafe ZIP64 value':(b,c,e)=>b.writeBigUInt64LE(2n**63n,e+4),
|
|
88
|
+
'missing ZIP64 field':(b,c,e)=>b.writeUInt16LE(42,e),
|
|
89
|
+
'local offset':(b,c,e)=>b.writeBigUInt64LE(2n**60n,e+20),
|
|
90
|
+
'unsafe directory offset':(b,c,e)=>b.writeBigUInt64LE(2n**63n,b.length-98+48),
|
|
91
|
+
};
|
|
92
|
+
for(const [name,mutate] of Object.entries(mutations)) test(`zip rejects ${name}`,()=>{
|
|
93
|
+
const fixture=zip({zip64:!['directory allocation','entry count','name overrun'].includes(name)});mutate(fixture.bytes,fixture.central,fixture.extra);
|
|
94
|
+
const r=run('unzip',fixture.bytes,['-l']);expect(r.status).not.toBe(0);expect(r.stderr).toContain('unzip:');expect(r.stderr).not.toContain('RangeError');
|
|
95
|
+
});
|
|
96
|
+
test('zip rejects local variable-length header crossing directory',()=>{
|
|
97
|
+
const {bytes}=zip();bytes.writeUInt16LE(65000,26);expect(run('unzip',bytes,['-t']).status).not.toBe(0);
|
|
98
|
+
});
|
|
99
|
+
test('deflate cannot inflate beyond declared size',()=>{
|
|
100
|
+
const {bytes,central}=zip({method:8});bytes.writeUInt32LE(1,central+24);expect(run('unzip',bytes,['-t']).status).not.toBe(0);
|
|
101
|
+
});
|
|
102
|
+
test('zip ignores fake EOCD signature within comment',()=>{
|
|
103
|
+
const {bytes}=zip({comment:'PK\x05\x06'+'x'.repeat(30)});expect(run('unzip',bytes,['-t']).status).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const { readTar } = await import('../scripts/fetch.js');
|
|
107
|
+
function fetchArchive(bytes) {
|
|
108
|
+
const file=join(root,`fetch-${++serial}.apk`);writeFileSync(file,gzipSync(bytes));return ()=>readTar(file);
|
|
109
|
+
}
|
|
110
|
+
test('fetch APK reader retains concatenated members and UTF8 PAX paths',()=>{
|
|
111
|
+
const bytes=Buffer.concat([tar,member('pax',pax('path','中文.txt'),'x'),member('fallback','content'),Buffer.alloc(1024)]);
|
|
112
|
+
const archive=fetchArchive(bytes)();expect(archive.get('hello.txt').toString()).toBe('hello');
|
|
113
|
+
expect(archive.get('中文.txt').toString()).toBe('content');
|
|
114
|
+
});
|
|
115
|
+
test('fetch APK reader rejects unsafe sizes, truncated blocks and corrupt checksums',()=>{
|
|
116
|
+
for(const bytes of [header('bad','-1000'),header('bad','77777777777'),Buffer.concat([tar,Buffer.from('x')]),
|
|
117
|
+
Buffer.concat([tar,Buffer.alloc(512,42)]),Buffer.concat([header('bad','1'),Buffer.from('x')]),
|
|
118
|
+
Buffer.concat([member('pax',pax('size','-512'),'x'),header('bad','0')]),
|
|
119
|
+
member('pax','999 path=bad\n','x')]) expect(fetchArchive(bytes)).toThrow();
|
|
120
|
+
});
|
|
121
|
+
test('fetch APK reader honors GNU long names and keeps link metadata without creating links',()=>{
|
|
122
|
+
const name='folder/'.repeat(20)+'file.txt';
|
|
123
|
+
const link=header('alias','0','2');link.write('target',157);link.fill(32,148,156);
|
|
124
|
+
link.write(link.reduce((a,v)=>a+v,0).toString(8).padStart(6,'0')+'\0 ',148);
|
|
125
|
+
const archive=fetchArchive(Buffer.concat([member('././@LongLink',name+'\0','L'),member('short','content'),link,Buffer.alloc(1024)]))();
|
|
126
|
+
expect(archive.get(name).toString()).toBe('content');
|
|
127
|
+
expect([...archive.links()]).toEqual([['alias',{target:'target',hard:false}]]);
|
|
128
|
+
});
|
package/test/fetch-npm.test.js
CHANGED
|
@@ -23,7 +23,7 @@ const url = `http://127.0.0.1:${server.port}/archive`;
|
|
|
23
23
|
|
|
24
24
|
test("five exact registry pins share an architecture-independent cache", () => {
|
|
25
25
|
expect(npmPackages.map(({ name, version }) => `${name}@${version}`)).toEqual([
|
|
26
|
-
"buninu-browser@0.9.
|
|
26
|
+
"buninu-browser@0.9.2", "buninu-land@0.4.1", "@drxiaozhi/casty@1.6.2",
|
|
27
27
|
"@drxiaozhi/jspulse@0.2.3", "@drxiaozhi/jspinyin@0.2.0",
|
|
28
28
|
]);
|
|
29
29
|
expect(basename(npmDownloadsDir)).toBe("npm");
|