import * as fs from "node:fs"; import Logger from "../services/logger.js"; import { createTemp, createTempDir } from "./create-temp.js"; import { downloadUrl } from "./download-url.js"; import { addFile } from "../services/drive/add-file.js"; import { Users } from "../models/index.js"; import * as tar from "tar-stream"; import gunzip from "gunzip-maybe"; import decompress from "decompress"; import * as Path from "node:path"; const logger = new Logger("process-masto-notes"); export async function processMastoNotes(fn, url, uid) { // Create temp file const [path, cleanup] = await createTemp(); const [unzipPath, unzipCleanup] = await createTempDir(); logger.info(`Temp file is ${path}`); try { // write content at URL to temp file await downloadUrl(url, path); return await processMastoFile(fn, path, unzipPath, uid); } finally{ cleanup(); //unzipCleanup(); } } function processMastoFile(fn, path, dir, uid) { return new Promise(async (resolve, reject)=>{ const user = await Users.findOneBy({ id: uid }); try { logger.info(`Start unzip ${path}`); fn.endsWith("tar.gz") ? await unzipTarGz(path, dir) : await unzipZip(path, dir); logger.info(`Unzip to ${dir}`); const outbox = JSON.parse(fs.readFileSync(`${dir}/outbox.json`)); for (const note of outbox.orderedItems){ // Skip if attachment is undefined or not iterable if (note.object.attachment == null || !note.object.attachment[Symbol.iterator]) { continue; } for (const attachment of note.object.attachment){ const url = attachment.url.replaceAll("..", ""); if (url.indexOf("\0") !== -1) { logger.error(`Found Poison Null Bytes Attack: ${url}`); reject(); return; } try { const fpath = Path.resolve(`${dir}${url}`); if (!fpath.startsWith(dir)) { logger.error(`Found Path Attack: ${url}`); reject(); return; } logger.info(fpath); const driveFile = await addFile({ user: user, path: fpath }); attachment.driveFile = driveFile; } catch (e) { logger.error(`Skipped adding file to drive: ${url}`); } } } resolve(outbox); } catch (e) { logger.error(`Error on extract masto note package: ${fn}`); reject(e); } }); } function createFileDir(fn) { if (!fs.existsSync(fn)) { fs.mkdirSync(fn, { recursive: true }); fs.rmdirSync(fn); } } function unzipZip(fn, dir) { return new Promise(async (resolve, reject)=>{ try { decompress(fn, dir).then((files)=>{ resolve(files); }); } catch (e) { reject(); } }); } function unzipTarGz(fn, dir) { return new Promise(async (resolve, reject)=>{ const onErr = (err)=>{ logger.error(`pipe broken: ${err}`); reject(); }; try { const extract = tar.extract().on("error", onErr); dir = dir.endsWith("/") ? dir : dir + "/"; const ls = []; extract.on("entry", function(header, stream, next) { try { ls.push(dir + header.name); createFileDir(dir + header.name); stream.on("error", onErr).pipe(fs.createWriteStream(dir + header.name)).on("error", onErr); next(); } catch (e) { logger.error(`create dir error:${e}`); reject(); } }); extract.on("finish", function() { resolve(ls); }); fs.createReadStream(fn).on("error", onErr).pipe(gunzip()).on("error", onErr).pipe(extract).on("error", onErr); } catch (e) { logger.error(`unzipTarGz error: ${e}`); reject(); } }); }