diff --git a/src/syncer.js b/src/syncer.js index 51932e25b..46bdd8883 100644 --- a/src/syncer.js +++ b/src/syncer.js @@ -10,6 +10,11 @@ import util from 'node:util'; const { log } = logger('syncer'); +// JSON.stringify does not escape U+2028/U+2029 and readline treats them as line terminators +// https://github.com/tc39/proposal-json-superset +function jsonLineStringify(obj) { + return JSON.stringify(obj).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); +} function readCache(cacheFile) { assert.strictEqual(typeof cacheFile, 'string'); @@ -111,7 +116,7 @@ async function sync(dataLayout, cacheFile) { if (!entryStat.isDirectory() && !entryStat.isFile()) continue; // ignore non-files and dirs if (entryStat.isSymbolicLink()) continue; - safe.fs.appendFileSync(newCacheFd, JSON.stringify({ path: entryPath, stat: { mtime: entryStat.mtime.getTime(), size: entryStat.size, inode: entryStat.inode, mode: entryStat.mode } }) + '\n'); + safe.fs.appendFileSync(newCacheFd, jsonLineStringify({ path: entryPath, stat: { mtime: entryStat.mtime.getTime(), size: entryStat.size, inode: entryStat.inode, mode: entryStat.mode } }) + '\n'); if (curCacheIndex !== cache.length && cache[curCacheIndex].path < entryPath) { // files disappeared. first advance cache as needed advanceCache(entryPath); @@ -185,7 +190,7 @@ async function finalize(integrityMap, cacheFile) { cacheEntry.integrity = integrityMap.get(cacheEntry.path); // { size, sha256 } if (typeof cacheEntry.integrity === 'undefined') throw new BoxError(BoxError.INTERNAL_ERROR, `No integrity information for ${cacheEntry.path}`); } - safe.fs.appendFileSync(tempCacheFd, JSON.stringify(cacheEntry) + '\n'); + safe.fs.appendFileSync(tempCacheFd, jsonLineStringify(cacheEntry) + '\n'); } safe.fs.closeSync(tempCacheFd);