@@ -11,7 +11,11 @@ import fs from 'fs';
1111import path from 'path' ;
1212
1313const ZIMS_DIR = '/library/zims/content/' ;
14- const BASE_URL = 'https://download.kiwix.org/zim/wikipedia/' ;
14+ // ADFA-5042: root of the Kiwix ZIM mirror. Each requested id MUST carry its own project subdirectory
15+ // (e.g. "zimit/foo.zim", "other/bar.zim", "gutenberg/baz.zim") — ZIMs are not all under wikipedia/.
16+ const BASE_URL = 'https://download.kiwix.org/zim/' ;
17+ // Allowed id shape: subdir segment(s) + filename. Guards the outbound URL (no "..", no traversal).
18+ const SAFE_ID = / ^ [ A - Z a - z 0 - 9 . _ - ] + ( \/ [ A - Z a - z 0 - 9 . _ - ] + ) * $ / ;
1519const INDEXER = '/usr/bin/iiab-make-kiwix-lib' ;
1620const SAFETY_BUFFER_BYTES = 5 * 1024 * 1024 * 1024 ; // keep >=5 GB free
1721
@@ -75,15 +79,24 @@ function cleanupMetadata(): void {
7579}
7680
7781const kiwixRunner : ( ctx : RunnerContext ) => Promise < void > = async ( ctx ) => {
78- const zims = ctx . ids . map ( ( z ) => path . basename ( z ) ) . filter ( ( z ) => z . endsWith ( '.zim' ) ) ;
79- if ( zims . length === 0 ) throw new Error ( 'no ZIMs requested' ) ;
82+ // ADFA-5042: keep each id's project subdir for the URL; use basename only for the local file/display.
83+ const ids = ctx . ids . map ( String ) . map ( ( z ) => z . replace ( / ^ \/ + / , '' ) ) . filter ( ( z ) => z . endsWith ( '.zim' ) ) ;
84+ if ( ids . length === 0 ) throw new Error ( 'no ZIMs requested' ) ;
85+ for ( const id of ids ) {
86+ // Require the project subdir — every ZIM on the mirror lives under one (/zim/<project>/…).
87+ if ( id . includes ( '..' ) || ! id . includes ( '/' ) || ! SAFE_ID . test ( id ) ) {
88+ throw new Error ( `invalid ZIM id (expected "<project>/<file>.zim"): ${ id } ` ) ;
89+ }
90+ }
91+ const files = ids . map ( ( z ) => path . basename ( z ) ) ;
8092
8193 assertFreeSpace ( ) ;
8294 ctx . throwIfCanceled ( ) ;
8395
8496 // --- Download phase -----------------------------------------------------
85- ctx . update ( { phase : 'downloading' , percent : 0 , speed : 0 , detail : zims . join ( ', ' ) } ) ;
86- const urls = zims . map ( ( z ) => BASE_URL + z ) ;
97+ ctx . update ( { phase : 'downloading' , percent : 0 , speed : 0 , detail : files . join ( ', ' ) } ) ;
98+ // Each id already carries its project subdir on the mirror (/zim/<project>/<file>).
99+ const urls = ids . map ( ( z ) => BASE_URL + z ) ;
87100
88101 await new Promise < void > ( ( resolve , reject ) => {
89102 const dl = ctx . spawn ( '/usr/bin/aria2c' , [ ...ARIA2_ARGS , ...urls ] ) ;
0 commit comments