import assert from 'node:assert/strict';
import {execFileSync} from 'node:child_process';
import {unpackPackage,packageURL,Packages} from '../services/packages.mjs';
const source='https://vulpes-os.org/marketplace/index.php?download=42';
function zip(extra={},manifest={}) {
return new Uint8Array(execFileSync('python3',['-c',`import sys,io,zipfile,json
b=io.BytesIO();args=json.loads(sys.argv[1])
with zipfile.ZipFile(b,'w',zipfile.ZIP_DEFLATED) as z:
z.writestr('manifest.webapp',json.dumps(dict(name='Test',launch_path='/index.html',icons={'128':'/icon.png'},**args['manifest'])))
z.writestr('index.html','
Test
');z.writestr('icon.png',b'icon')
for name,value in args['extra'].items():z.writestr(name,value)
sys.stdout.buffer.write(b.getvalue())`,JSON.stringify({extra,manifest})]));
}
assert.equal(packageURL(source+'&lang=fr'),source);
for(const url of ['http://vulpes-os.org/marketplace/index.php?download=42','https://evil.test/marketplace/index.php?download=42','https://vulpes-os.org/x?download=42','https://vulpes-os.org/marketplace/index.php?download=../42'])assert.throws(()=>packageURL(url));
const bytes=zip({}, {type:'certified',permissions:{settings:{access:'readwrite'},'webapps-manage':{}}});
const item=await unpackPackage(bytes,source);
assert.equal(item.manifest.type,'web');assert.deepEqual(item.manifest.permissions,{});assert.match(item.id,/^user-[a-f0-9]{24}$/);
for(const name of ['../escape','/absolute','dir/../../escape','dir\\escape','a:b'])await assert.rejects(unpackPackage(zip({[name]:'bad'}),source));
await assert.rejects(unpackPackage(bytes.slice(0,-2),source));
const corrupt=bytes.slice();corrupt[100]^=255;await assert.rejects(unpackPackage(corrupt,source));
let saved=null,changes=0;
const service=new Packages({save:async value=>saved=value,changed:async()=>changes++,uuid:()=> 'token',fetchPackage:async()=>new Response(bytes)});
const prepared=await service.prepare(source);assert.equal(saved,null);assert.equal(prepared.name,'Test');
assert.equal(await service.install(prepared.token),item.id);assert.equal(changes,1);assert.equal(saved.id,item.id);
await assert.rejects(service.install(prepared.token),/INSTALL_EXPIRED/);
console.log('Package URL policy, ZIP boundaries/CRC, permission stripping and one-use confirmation: OK');
// Cancellation during download must not produce a confirmation token.
let finishDownload;
const cancelled=new Packages({save:async()=>assert.fail('unexpected save'),changed:async()=>{},
fetchPackage:()=>new Promise(resolve=>{finishDownload=resolve;})});
const pending=cancelled.prepare(source);cancelled.cancel();finishDownload(new Response(bytes));
await assert.rejects(pending,/DOWNLOAD_CANCELLED/);assert.equal(cancelled.pending.size,0);
console.log('Cancelled download cannot be installed: OK');