- zip entries carry flag bit 11 when the name is non-ASCII, so an extractor reads `BKP_` plus an accented filename back by name instead of CP437 - loadfile and stagedata name the staged CSV after the libds the request arrived with, before a hook can re-point the submission - stagedata writes that CSV at all (%mp_ds2csv does), so a grid submission's audit pack carries the same artefact as an upload's - loadfile writes no BKP_ file: loadfile.sas copies one only for a second, non-CSV fileref, which the mock does not accept - getauditfile says access is unenforced rather than that there is no permission store (MPE_SECURITY is seeded) - the special-missings clip no longer claims the estate carries a valid key
105 lines
3.5 KiB
JavaScript
105 lines
3.5 KiB
JavaScript
const nodePath = require('path')
|
|
|
|
let appLoc = nodePath.join(..._program.split('services')[0].split('/'))
|
|
const sasjsRoot = nodePath.resolve(weboutPath, '..', '..', '..')
|
|
const driveRoot = nodePath.resolve(sasjsRoot, 'drive')
|
|
|
|
// Load shared DC mock utilities
|
|
eval(
|
|
fs.readFileSync(
|
|
nodePath.resolve(driveRoot, 'files', appLoc, 'services', 'dcMockUtils.js'),
|
|
'utf8'
|
|
)
|
|
)
|
|
|
|
const dcConf = loadDcSettings(driveRoot, appLoc)
|
|
const mpeDataDir = dcConf.dataDir
|
|
const stagingDir = nodePath.resolve(mpeDataDir, 'staging')
|
|
const mpeLoadTableData = makeTableLoader(mpeDataDir)
|
|
|
|
// ─── Parse input ──────────────────────────────────────────────────────────────
|
|
// The submission id arrives as the `table` URL param, as in getauditfile.sas.
|
|
|
|
let tableId = ''
|
|
if (typeof table !== 'undefined' && table) tableId = String(table).trim()
|
|
|
|
// libds comes from MPE_SUBMIT, the same lookup the SAS service makes before it
|
|
// decides the request is valid.
|
|
let libds = ''
|
|
if (tableId) {
|
|
const submit = mpeLoadTableData('MPE_SUBMIT')
|
|
const row =
|
|
submit && submit.rows
|
|
? submit.rows.find((r) => r.TABLE_ID === tableId)
|
|
: null
|
|
if (row) {
|
|
libds = (String(row.BASE_LIB || '') + '.' + String(row.BASE_DS || '')).toUpperCase()
|
|
}
|
|
}
|
|
|
|
// ─── Respond ──────────────────────────────────────────────────────────────────
|
|
|
|
if (!tableId || !libds) {
|
|
// getauditfile.sas aborts with the same message when either is missing.
|
|
webOutOpen()
|
|
webOutObj([{ MSG: 'Missing: libds table', MAC: 'getauditfile' }], 'sasjsAbort')
|
|
webOutObj([{ STATUS: 'ERROR', DSID: '' }], 'sasparams')
|
|
webOutClose()
|
|
} else {
|
|
// The SAS service also checks EDIT or APPROVE access (mpe_accesscheck). The
|
|
// mock seeds MPE_SECURITY but no mock service enforces it, so access is not
|
|
// emulated here.
|
|
|
|
// The approval folder is zipped as it stands. getauditfile.sas lists it with
|
|
// mp_dirlist and adds each file to the package under contents/.
|
|
const stageFolder = nodePath.resolve(stagingDir, tableId)
|
|
const names = []
|
|
if (fs.existsSync(stageFolder)) {
|
|
for (const name of fs.readdirSync(stageFolder)) {
|
|
const absolute = nodePath.resolve(stageFolder, name)
|
|
let stat
|
|
try {
|
|
stat = fs.statSync(absolute)
|
|
} catch (err) {
|
|
continue
|
|
}
|
|
if (stat.isFile()) names.push(name)
|
|
}
|
|
}
|
|
names.sort()
|
|
|
|
const entries = names.map((name) => ({
|
|
name: 'contents/' + name,
|
|
content: fs
|
|
.readFileSync(nodePath.resolve(stageFolder, name), { encoding: 'utf8' })
|
|
.toString()
|
|
}))
|
|
|
|
const archive = mpZipPackage(entries)
|
|
const streamed = mpStreamfile({
|
|
filename: tableId + '.zip',
|
|
contentType: 'application/zip',
|
|
bytes: archive
|
|
})
|
|
|
|
if (!streamed) {
|
|
// The browser host returns a text body and ignores the streamed headers, so
|
|
// the archive cannot reach the browser as a download. The pack is described
|
|
// instead, which is what a reviewer opening the tab gets there.
|
|
_webout = JSON.stringify(
|
|
{
|
|
table: tableId,
|
|
libds: libds,
|
|
archive: tableId + '.zip',
|
|
note: 'The browser mock host streams text only, so this lists the archive contents.',
|
|
contents: entries.map((entry) => ({
|
|
name: entry.name,
|
|
bytes: entry.content.length
|
|
}))
|
|
},
|
|
null,
|
|
2
|
|
)
|
|
}
|
|
}
|