mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: change to @zip.js/zip.js
This commit is contained in:
17 files changed
+129
-108
No files matched your search
@@ -4,7 +4,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "base.mjs",
|
"main": "base.mjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-react/eslint-plugin": "^1.45.3",
|
"@eslint-react/eslint-plugin": "^1.47.2",
|
||||||
"@eslint/compat": "^1.2.8",
|
"@eslint/compat": "^1.2.8",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@stylistic/eslint-plugin": "^4.2.0",
|
"@stylistic/eslint-plugin": "^4.2.0",
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@
|
|||||||
"mongodb-memory-server": "10.1.4",
|
"mongodb-memory-server": "10.1.4",
|
||||||
"nyc": "^17.1.0",
|
"nyc": "^17.1.0",
|
||||||
"ora": "^8.2.0",
|
"ora": "^8.2.0",
|
||||||
"oxlint": "^0.16.5",
|
"oxlint": "^0.16.6",
|
||||||
"package-json": "^10.0.1",
|
"package-json": "^10.0.1",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"simple-git": "^3.27.0",
|
"simple-git": "^3.27.0",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { Readable } from 'stream';
|
||||||
import decodeHTML from 'decode-html';
|
import decodeHTML from 'decode-html';
|
||||||
import xml2js from 'xml2js';
|
import xml2js from 'xml2js';
|
||||||
import {
|
import {
|
||||||
_, AdmZip, BadRequestError, buildContent, Context, FileTooLargeError, fs, Handler,
|
_, BadRequestError, buildContent, Context, FileTooLargeError, fs, Handler, PERM, ProblemConfigFile,
|
||||||
PERM, ProblemConfigFile, ProblemModel, ProblemType, Schema, SolutionModel, SystemModel, ValidationError, yaml,
|
ProblemModel, ProblemType, Schema, SolutionModel, SystemModel, ValidationError, yaml, Zip,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
const knownRemoteMapping = {
|
const knownRemoteMapping = {
|
||||||
@@ -93,17 +94,17 @@ class FpsProblemImportHandler extends Handler {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof FileTooLargeError) throw e;
|
if (e instanceof FileTooLargeError) throw e;
|
||||||
console.log(e);
|
console.log(e);
|
||||||
let zip: AdmZip;
|
const zip = new Zip.ZipReader(Readable.toWeb(fs.createReadStream(file.filepath)));
|
||||||
|
let entries: Zip.Entry[];
|
||||||
try {
|
try {
|
||||||
zip = new AdmZip(file.filepath);
|
entries = await zip.getEntries();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new ValidationError('zip', null, err.message);
|
throw new ValidationError('zip', null, err.message);
|
||||||
}
|
}
|
||||||
for (const entry of zip.getEntries()) {
|
for (const entry of entries) {
|
||||||
try {
|
try {
|
||||||
const buf = entry.getData();
|
if (entry.uncompressedSize > SystemModel.get('import-fps.limit')) throw new FileTooLargeError();
|
||||||
if (buf.byteLength > SystemModel.get('import-fps.limit')) throw new FileTooLargeError();
|
const content = entry.getData(new Zip.TextWriter());
|
||||||
const content = buf.toString();
|
|
||||||
const result = await xml2js.parseStringPromise(content);
|
const result = await xml2js.parseStringPromise(content);
|
||||||
tasks.push(result);
|
tasks.push(result);
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hydrooj/common": "workspace:^",
|
"@hydrooj/common": "workspace:^",
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"adm-zip": "0.5.16",
|
"@zip.js/zip.js": "^2.7.60",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
"schemastery": "^3.16.1",
|
"schemastery": "^3.16.1",
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/adm-zip": "^0.5.7",
|
|
||||||
"@types/shell-quote": "^1.7.5",
|
"@types/shell-quote": "^1.7.5",
|
||||||
"@types/ws": "^8.18.1"
|
"@types/ws": "^8.18.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { createReadStream } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import AdmZip from 'adm-zip';
|
import { Readable } from 'stream';
|
||||||
|
import { ZipReader } from '@zip.js/zip.js';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import { JudgeResultBody, parseLang, STATUS } from '@hydrooj/common';
|
import { JudgeResultBody, parseLang, STATUS } from '@hydrooj/common';
|
||||||
import { findFileSync, fs, noop } from '@hydrooj/utils';
|
import {
|
||||||
|
extractZip, findFileSync, fs, noop,
|
||||||
|
} from '@hydrooj/utils';
|
||||||
import { getConfig } from '../config';
|
import { getConfig } from '../config';
|
||||||
import { FormatError, SystemError } from '../error';
|
import { FormatError, SystemError } from '../error';
|
||||||
import { Session } from '../interface';
|
import { Session } from '../interface';
|
||||||
@@ -151,16 +155,11 @@ export default class VJ4 implements Session {
|
|||||||
fs.createWriteStream(tmpFilePath),
|
fs.createWriteStream(tmpFilePath),
|
||||||
);
|
);
|
||||||
fs.ensureDirSync(path.dirname(savePath));
|
fs.ensureDirSync(path.dirname(savePath));
|
||||||
const zip = new AdmZip(tmpFilePath);
|
const zip = new ZipReader(Readable.toWeb(createReadStream(tmpFilePath)));
|
||||||
const entries = zip.getEntries();
|
const entries = await zip.getEntries();
|
||||||
if (entries.length > 512) throw new FormatError('Too many files');
|
if (entries.length > 512) throw new FormatError('Too many files');
|
||||||
if (Math.sum(entries.map((i) => i.header.size)) > 256 * 1024 * 1024) throw new FormatError('File too large');
|
if (Math.sum(entries.map((i) => i.uncompressedSize)) > 256 * 1024 * 1024) throw new FormatError('File too large');
|
||||||
await new Promise((resolve, reject) => {
|
await extractZip(entries, savePath, { strip: true });
|
||||||
zip.extractAllToAsync(savePath, true, false, (e) => {
|
|
||||||
if (e) reject(e);
|
|
||||||
else resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await fs.unlink(tmpFilePath);
|
await fs.unlink(tmpFilePath);
|
||||||
await this.processData(savePath).catch(noop);
|
await this.processData(savePath).catch(noop);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -177,15 +176,10 @@ export default class VJ4 implements Session {
|
|||||||
const w = fs.createWriteStream(tmpFilePath);
|
const w = fs.createWriteStream(tmpFilePath);
|
||||||
await pipeRequest(this.get(`records/${rid}/data`), w);
|
await pipeRequest(this.get(`records/${rid}/data`), w);
|
||||||
fs.ensureDirSync(path.dirname(savePath));
|
fs.ensureDirSync(path.dirname(savePath));
|
||||||
const zip = new AdmZip(tmpFilePath);
|
const zip = new ZipReader(Readable.toWeb(createReadStream(tmpFilePath)));
|
||||||
const entries = zip.getEntries();
|
const entries = await zip.getEntries();
|
||||||
if (entries.length > 512) throw new FormatError('Too many files');
|
if (entries.length > 512) throw new FormatError('Too many files');
|
||||||
await new Promise((resolve, reject) => {
|
await extractZip(entries, savePath, { strip: true });
|
||||||
zip.extractAllToAsync(savePath, true, false, (e) => {
|
|
||||||
if (e) reject(e);
|
|
||||||
else resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await fs.unlink(tmpFilePath);
|
await fs.unlink(tmpFilePath);
|
||||||
await this.processData(savePath);
|
await this.processData(savePath);
|
||||||
return savePath;
|
return savePath;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"@simplewebauthn/server": "13.1.1",
|
"@simplewebauthn/server": "13.1.1",
|
||||||
"@undefined-moe/require-resolve-hook": "^1.1.0",
|
"@undefined-moe/require-resolve-hook": "^1.1.0",
|
||||||
|
"@zip.js/zip.js": "^2.7.60",
|
||||||
"adm-zip": "0.5.16",
|
"adm-zip": "0.5.16",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"cordis": "4.0.0-beta.1",
|
"cordis": "4.0.0-beta.1",
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
"mongodb": "^6.15.0",
|
"mongodb": "^6.15.0",
|
||||||
"mongodb-uri": "^0.9.7",
|
"mongodb-uri": "^0.9.7",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.1",
|
||||||
"notp": "^2.0.3",
|
"notp": "^2.0.3",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import child from 'child_process';
|
import child from 'child_process';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import AdmZip from 'adm-zip';
|
import { BlobReader, ZipReader } from '@zip.js/zip.js';
|
||||||
import { CAC } from 'cac';
|
import { CAC } from 'cac';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
@@ -34,7 +34,7 @@ function downloadAndExtractTgz(url: string, dest: string) {
|
|||||||
}
|
}
|
||||||
async function downloadAndExtractZip(url: string, dest: string) {
|
async function downloadAndExtractZip(url: string, dest: string) {
|
||||||
const res = await superagent.get(url).responseType('arraybuffer');
|
const res = await superagent.get(url).responseType('arraybuffer');
|
||||||
await extractZip(new AdmZip(res.body), dest, true, true);
|
await extractZip(new ZipReader(new BlobReader(new Blob([res.body]))), dest, { strip: true, overwrite: true });
|
||||||
}
|
}
|
||||||
const types = {
|
const types = {
|
||||||
'.tgz': downloadAndExtractTgz,
|
'.tgz': downloadAndExtractTgz,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import AdmZip from 'adm-zip';
|
import { Readable } from 'stream';
|
||||||
|
import { BlobWriter, TextReader, ZipWriter } from '@zip.js/zip.js';
|
||||||
import { stringify as toCSV } from 'csv-stringify/sync';
|
import { stringify as toCSV } from 'csv-stringify/sync';
|
||||||
import { escapeRegExp, pick } from 'lodash';
|
import { escapeRegExp, pick } from 'lodash';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import {
|
import {
|
||||||
Counter, sortFiles, streamToBuffer, Time, yaml,
|
Counter, sortFiles, Time, yaml,
|
||||||
} from '@hydrooj/utils/lib/utils';
|
} from '@hydrooj/utils/lib/utils';
|
||||||
import { Context, Service } from '../context';
|
import { Context, Service } from '../context';
|
||||||
import {
|
import {
|
||||||
@@ -399,7 +400,7 @@ export class ContestCodeHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const zip = new AdmZip();
|
const zip = new ZipWriter(new BlobWriter('application/zip'), { bufferedWrite: true });
|
||||||
const rdocs = await record.getMulti(domainId, {
|
const rdocs = await record.getMulti(domainId, {
|
||||||
_id: { $in: Array.from(Object.keys(rnames)).map((id) => new ObjectId(id)) },
|
_id: { $in: Array.from(Object.keys(rnames)).map((id) => new ObjectId(id)) },
|
||||||
}).toArray();
|
}).toArray();
|
||||||
@@ -407,15 +408,15 @@ export class ContestCodeHandler extends Handler {
|
|||||||
if (rdoc.files?.code) {
|
if (rdoc.files?.code) {
|
||||||
const [id, filename] = rdoc.files?.code?.split('#') || [];
|
const [id, filename] = rdoc.files?.code?.split('#') || [];
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
zip.addFile(
|
await zip.add(
|
||||||
`${rnames[rdoc._id.toHexString()]}.${filename || 'txt'}`,
|
`${rnames[rdoc._id.toHexString()]}.${filename || 'txt'}`,
|
||||||
await streamToBuffer(await storage.get(`submission/${id}`)),
|
Readable.toWeb(await storage.get(`submission/${id}`)),
|
||||||
);
|
);
|
||||||
} else if (rdoc.code) {
|
} else if (rdoc.code) {
|
||||||
zip.addFile(`${rnames[rdoc._id.toHexString()]}.${rdoc.lang}`, Buffer.from(rdoc.code));
|
await zip.add(`${rnames[rdoc._id.toHexString()]}.${rdoc.lang}`, new TextReader(rdoc.code));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this.binary(zip.toBuffer(), `${tdoc.title}.zip`);
|
this.binary(await zip.close(), `${tdoc.title}.zip`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import AdmZip from 'adm-zip';
|
import { createReadStream } from 'fs';
|
||||||
|
import { PassThrough, Readable, Writable } from 'stream';
|
||||||
|
import { Entry, ZipReader } from '@zip.js/zip.js';
|
||||||
import { readFile } from 'fs-extra';
|
import { readFile } from 'fs-extra';
|
||||||
import {
|
import {
|
||||||
escapeRegExp, flattenDeep, intersection, pick,
|
escapeRegExp, flattenDeep, intersection, pick,
|
||||||
@@ -709,20 +711,24 @@ export class ProblemFilesHandler extends ProblemDetailHandler {
|
|||||||
filename ||= file.originalFilename || String.random(16);
|
filename ||= file.originalFilename || String.random(16);
|
||||||
const files = [];
|
const files = [];
|
||||||
if (filename.endsWith('.zip') && type === 'testdata') {
|
if (filename.endsWith('.zip') && type === 'testdata') {
|
||||||
let zip: AdmZip;
|
const zip = new ZipReader(Readable.toWeb(createReadStream(file.filepath)));
|
||||||
|
let entries: Entry[];
|
||||||
try {
|
try {
|
||||||
zip = new AdmZip(file.filepath);
|
entries = await zip.getEntries();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ValidationError('zip', null, e.message);
|
throw new ValidationError('zip', null, e.message);
|
||||||
}
|
}
|
||||||
const entries = zip.getEntries();
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.name || entry.isDirectory) continue;
|
if (!entry.filename || entry.directory) continue;
|
||||||
files.push({
|
files.push({
|
||||||
type,
|
type,
|
||||||
name: sanitize(entry.name),
|
name: sanitize(entry.filename),
|
||||||
size: entry.header.size,
|
size: entry.uncompressedSize,
|
||||||
data: () => entry.getData(),
|
data: () => {
|
||||||
|
const pass = new PassThrough();
|
||||||
|
entry.getData(Writable.toWeb(pass));
|
||||||
|
return pass;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AdmZip from 'adm-zip';
|
import * as Zip from '@zip.js/zip.js';
|
||||||
|
import _AdmZip from 'adm-zip';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ObjectId as ObjectID } from 'mongodb';
|
import { ObjectId as ObjectID } from 'mongodb';
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
@@ -10,11 +11,14 @@ export * from './utils';
|
|||||||
export * from './interface';
|
export * from './interface';
|
||||||
export * from './typeutils';
|
export * from './typeutils';
|
||||||
export {
|
export {
|
||||||
Schema, AdmZip, superagent, _, ObjectID,
|
Schema, superagent, _, ObjectID, Zip,
|
||||||
};
|
};
|
||||||
|
/** @deprecated Use ZipReader/ZipWriter instead */
|
||||||
|
export const AdmZip = _AdmZip;
|
||||||
export const definePlugin = <T = never>(args: {
|
export const definePlugin = <T = never>(args: {
|
||||||
using?: keyof Context[];
|
inject?: keyof Context[] | Record<keyof Context, any>;
|
||||||
apply: (ctx: Context, config: T) => Promise<void> | void;
|
apply: (ctx: Context, config: T) => Promise<void> | void;
|
||||||
schema?: Schema<T>;
|
schema?: Schema<T>;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
Config?: Schema<T>;
|
||||||
}) => args;
|
}) => args;
|
||||||
@@ -2,14 +2,16 @@
|
|||||||
import child from 'child_process';
|
import child from 'child_process';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import AdmZip from 'adm-zip';
|
import { Readable } from 'stream';
|
||||||
|
import { Entry, ZipReader } from '@zip.js/zip.js';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { keyBy, pick } from 'lodash';
|
import { keyBy, pick } from 'lodash';
|
||||||
import { Filter, ObjectId } from 'mongodb';
|
import { Filter, ObjectId } from 'mongodb';
|
||||||
import type { Readable } from 'stream';
|
|
||||||
import { ProblemConfigFile, ProblemType } from '@hydrooj/common';
|
import { ProblemConfigFile, ProblemType } from '@hydrooj/common';
|
||||||
import { Logger, size, streamToBuffer } from '@hydrooj/utils/lib/utils';
|
import {
|
||||||
|
extractZip, Logger, size, streamToBuffer,
|
||||||
|
} from '@hydrooj/utils/lib/utils';
|
||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error';
|
import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error';
|
||||||
import type {
|
import type {
|
||||||
@@ -456,18 +458,14 @@ export class ProblemModel {
|
|||||||
try {
|
try {
|
||||||
if (filepath.endsWith('.zip')) {
|
if (filepath.endsWith('.zip')) {
|
||||||
tmpdir = path.join(os.tmpdir(), 'hydro', `${Math.random()}.import`);
|
tmpdir = path.join(os.tmpdir(), 'hydro', `${Math.random()}.import`);
|
||||||
let zip: AdmZip;
|
const zip = new ZipReader(Readable.toWeb(fs.createReadStream(filepath)));
|
||||||
|
let entries: Entry[];
|
||||||
try {
|
try {
|
||||||
zip = new AdmZip(filepath);
|
entries = await zip.getEntries();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ValidationError('zip', null, e.message);
|
throw new ValidationError('zip', null, e.message);
|
||||||
}
|
}
|
||||||
await new Promise((resolve, reject) => {
|
await extractZip(entries, tmpdir);
|
||||||
zip.extractAllToAsync(tmpdir, true, false, (err) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (fs.statSync(filepath).isDirectory()) {
|
} else if (fs.statSync(filepath).isDirectory()) {
|
||||||
tmpdir = filepath;
|
tmpdir = filepath;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { Readable } from 'stream';
|
||||||
import {
|
import {
|
||||||
AdmZip, buildContent, Context, fs, Handler, PERM,
|
buildContent, Context, extractZip, fs, Handler, PERM,
|
||||||
ProblemConfigFile, ProblemModel, ValidationError, yaml,
|
ProblemConfigFile, ProblemModel, ValidationError, yaml, Zip,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-hoj');
|
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-hoj');
|
||||||
@@ -11,15 +12,11 @@ fs.ensureDirSync(tmpdir);
|
|||||||
|
|
||||||
class ImportHojHandler extends Handler {
|
class ImportHojHandler extends Handler {
|
||||||
async fromFile(domainId: string, zipfile: string) {
|
async fromFile(domainId: string, zipfile: string) {
|
||||||
let zip: AdmZip;
|
const zip = new Zip.ZipReader(Readable.toWeb(fs.createReadStream(zipfile)));
|
||||||
try {
|
|
||||||
zip = new AdmZip(zipfile);
|
|
||||||
} catch (e) {
|
|
||||||
throw new ValidationError('zip', null, e.message);
|
|
||||||
}
|
|
||||||
const tmp = path.resolve(tmpdir, String.random(32));
|
const tmp = path.resolve(tmpdir, String.random(32));
|
||||||
await new Promise((resolve, reject) => {
|
await extractZip(zip, tmp, {
|
||||||
zip.extractAllToAsync(tmp, true, false, (err) => (err ? reject(err) : resolve(null)));
|
strip: true,
|
||||||
|
parseError: (e) => new ValidationError('zip', null, e.message),
|
||||||
});
|
});
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { Readable } from 'stream';
|
||||||
import {
|
import {
|
||||||
AdmZip, buildContent, Context, FileTooLargeError, fs, Handler, PERM,
|
buildContent, Context, extractZip, FileTooLargeError, fs, Handler, PERM,
|
||||||
ProblemConfigFile, ProblemModel, Schema, ValidationError, yaml,
|
ProblemConfigFile, ProblemModel, Schema, ValidationError, yaml, Zip,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-qduoj');
|
const tmpdir = path.join(os.tmpdir(), 'hydro', 'import-qduoj');
|
||||||
@@ -44,15 +45,11 @@ const ProblemSchema = Schema.object({
|
|||||||
|
|
||||||
class ImportQduojHandler extends Handler {
|
class ImportQduojHandler extends Handler {
|
||||||
async fromFile(domainId: string, zipfile: string) {
|
async fromFile(domainId: string, zipfile: string) {
|
||||||
let zip: AdmZip;
|
const zip = new Zip.ZipReader(Readable.toWeb(fs.createReadStream(zipfile)));
|
||||||
try {
|
|
||||||
zip = new AdmZip(zipfile);
|
|
||||||
} catch (e) {
|
|
||||||
throw new ValidationError('zip', null, e.message);
|
|
||||||
}
|
|
||||||
const tmp = path.resolve(tmpdir, String.random(32));
|
const tmp = path.resolve(tmpdir, String.random(32));
|
||||||
await new Promise((resolve, reject) => {
|
await extractZip(zip, tmp, {
|
||||||
zip.extractAllToAsync(tmp, true, false, (err) => (err ? reject(err) : resolve(null)));
|
strip: true,
|
||||||
|
parseError: (e) => new ValidationError('zip', null, e.message),
|
||||||
});
|
});
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import mariadb from 'mariadb';
|
import mariadb from 'mariadb';
|
||||||
import xml2js from 'xml2js';
|
import xml2js from 'xml2js';
|
||||||
import {
|
import {
|
||||||
AdmZip, ContestModel, DomainModel, fs, MessageModel, moment,
|
ContestModel, DomainModel, fs, MessageModel, moment,
|
||||||
noop, NotFoundError, ObjectId, postJudge, ProblemConfigFile, ProblemModel, RecordDoc, RecordModel,
|
noop, NotFoundError, ObjectId, postJudge, ProblemConfigFile, ProblemModel, RecordDoc, RecordModel,
|
||||||
STATUS, SubtaskType, SystemModel, Time, UserModel, ValidationError, yaml,
|
STATUS, SubtaskType, SystemModel, Time, UserModel, ValidationError, yaml, Zip,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
Accepted: STATUS.STATUS_ACCEPTED,
|
Accepted: STATUS.STATUS_ACCEPTED,
|
||||||
@@ -376,13 +376,16 @@ export async function run({
|
|||||||
};
|
};
|
||||||
const content = JSON.parse(rdoc.content);
|
const content = JSON.parse(rdoc.content);
|
||||||
try {
|
try {
|
||||||
let zip: AdmZip;
|
const zip = new Zip.ZipReader(
|
||||||
|
new Zip.BlobReader(new Blob([await fs.readFile(`${dataDir}/opt/uoj/web/app/storage${content.file_name}`)])),
|
||||||
|
);
|
||||||
|
let entries: Zip.Entry[];
|
||||||
try {
|
try {
|
||||||
zip = new AdmZip(await fs.readFile(`${dataDir}/opt/uoj/web/app/storage${content.file_name}`));
|
entries = await zip.getEntries();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ValidationError('zip', null, e.message);
|
throw new ValidationError('zip', null, e.message);
|
||||||
}
|
}
|
||||||
data.code = zip.getEntries().find((i) => i.name.endsWith('answer.code')).getData().toString();
|
data.code = await entries.find((i) => i.filename.endsWith('answer.code'))?.getData(new Zip.TextWriter()) || '';
|
||||||
} catch { /* ignore no code */ }
|
} catch { /* ignore no code */ }
|
||||||
const result = JSON.parse(Buffer.from(rdoc.result, 'base64').toString('utf8'));
|
const result = JSON.parse(Buffer.from(rdoc.result, 'base64').toString('utf8'));
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
AdmZip, avatar, ContestModel, ContestNotEndedError, Context, db, findFileSync,
|
avatar, ContestModel, ContestNotEndedError, Context, db, findFileSync,
|
||||||
ForbiddenError, fs, ObjectId, parseTimeMS, PERM, ProblemConfig, ProblemModel,
|
ForbiddenError, fs, ObjectId, parseTimeMS, PERM, ProblemConfig, ProblemModel,
|
||||||
STATUS, STATUS_SHORT_TEXTS, STATUS_TEXTS, Time, UserModel,
|
STATUS, STATUS_SHORT_TEXTS, STATUS_TEXTS, Time, UserModel, Zip,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import { ResolverInput } from './interface';
|
import { ResolverInput } from './interface';
|
||||||
|
|
||||||
@@ -248,19 +248,19 @@ export function apply(ctx: Context) {
|
|||||||
finalized: moment().format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
finalized: moment().format('YYYY-MM-DDTHH:mm:ss.SSS+08:00'),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
const zip = new AdmZip();
|
const zip = new Zip.ZipWriter(new Zip.BlobWriter());
|
||||||
zip.addFile('event-feed.ndjson', Buffer.from(eventfeed.concat(submissions).concat(endState).map((i) => JSON.stringify(i)).join('\n')));
|
await zip.add('event-feed.ndjson', new Zip.TextReader(eventfeed.concat(submissions).concat(endState).map((i) => JSON.stringify(i)).join('\n')));
|
||||||
zip.addFile('contest/logo.png', fs.readFileSync(findFileSync('@hydrooj/onsite-toolkit/public/logo.png')));
|
await zip.add('contest/logo.png', new Zip.BlobReader(new Blob([fs.readFileSync(findFileSync('@hydrooj/onsite-toolkit/public/logo.png'))])));
|
||||||
for (const i of ['teams', 'organizations']) {
|
for (const i of ['teams', 'organizations']) {
|
||||||
zip.addFile(`${i}/`, Buffer.alloc(0));
|
zip.add(`${i}/`, null, { directory: true });
|
||||||
}
|
}
|
||||||
for (const i of teams) {
|
for (const i of teams) {
|
||||||
zip.addFile(`teams/${i.team_id}/photo.download.txt`, Buffer.from(i.avatar));
|
zip.add(`teams/${i.team_id}/photo.download.txt`, new Zip.TextReader(i.avatar));
|
||||||
}
|
}
|
||||||
for (const i of organizations) {
|
for (const i of organizations) {
|
||||||
zip.addFile(`organizations/${orgId[i]}/photo.download.txt`, Buffer.from(avatar(teams.find((j) => j.organization === i)?.avatar || 'no photo, find and download it yourself')));
|
zip.add(`organizations/${orgId[i]}/photo.download.txt`, new Zip.TextReader(avatar(teams.find((j) => j.organization === i)?.avatar || 'no photo, find and download it yourself')));
|
||||||
}
|
}
|
||||||
this.binary(zip.toBuffer(), `contest-${tdoc._id}-cdp.zip`);
|
this.binary(await zip.close(), `contest-${tdoc._id}-cdp.zip`);
|
||||||
},
|
},
|
||||||
supportedRules: ['acm'],
|
supportedRules: ['acm'],
|
||||||
});
|
});
|
||||||
|
|||||||
+30
-10
@@ -1,9 +1,9 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Duplex, PassThrough } from 'stream';
|
import { Duplex, PassThrough, Writable } from 'stream';
|
||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
import type AdmZip from 'adm-zip';
|
import type { Entry, ZipReader } from '@zip.js/zip.js';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import moment, { isMoment, Moment } from 'moment-timezone';
|
import moment, { isMoment, Moment } from 'moment-timezone';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
@@ -319,22 +319,42 @@ export function sanitizePath(pathname: string) {
|
|||||||
return parts.join(path.sep);
|
return parts.join(path.sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExtractZipConfig {
|
||||||
|
overwrite?: boolean;
|
||||||
|
strip?: boolean;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
parseError?: (err: Error) => Error;
|
||||||
|
}
|
||||||
|
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
export async function extractZip(zip: AdmZip, dest: string, overwrite = false, strip = false) {
|
export async function extractZip<T>(zipOrEntries: ZipReader<T> | Entry[], dest: string, config: ExtractZipConfig = {}) {
|
||||||
const entries = zip.getEntries();
|
const { overwrite = false, strip = false, signal } = config;
|
||||||
const shouldStrip = strip ? entries.every((i) => i.entryName.startsWith(entries[0].entryName)) : false;
|
let entries: Entry[];
|
||||||
|
if (Array.isArray(zipOrEntries)) entries = zipOrEntries;
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
entries = await zipOrEntries.getEntries();
|
||||||
|
} catch (e) {
|
||||||
|
if (config.parseError) throw config.parseError(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const shouldStrip = strip ? entries.every((i) => i.filename.startsWith(entries[0].filename)) : false;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const name = shouldStrip ? entry.entryName.substring(entries[0].entryName.length) : entry.entryName;
|
const name = shouldStrip ? entry.filename.substring(entries[0].filename.length) : entry.filename;
|
||||||
const d = sanitize(dest, canonical(name));
|
const d = sanitize(dest, canonical(name));
|
||||||
if (entry.isDirectory) {
|
if (entry.directory) {
|
||||||
await fs.mkdir(d, { recursive: true });
|
await fs.mkdir(d, { recursive: true });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const content = entry.getData();
|
if (fs.existsSync(d) && !overwrite) continue;
|
||||||
|
const content = await entry.getData(Writable.toWeb(fs.createWriteStream(d)), { signal });
|
||||||
if (!content) throw new Error('CANT_EXTRACT_FILE');
|
if (!content) throw new Error('CANT_EXTRACT_FILE');
|
||||||
if (!fs.existsSync(d) || overwrite) await fs.writeFile(d, content);
|
await fs.utimes(d, entry.lastModDate, entry.lastModDate);
|
||||||
await fs.utimes(d, entry.header.time, entry.header.time);
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
[Symbol.asyncDispose]: () => fs.remove(dest),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pipeRequest(req: superagent.Request, w: fs.WriteStream, timeout?: number, name?: string) {
|
export async function pipeRequest(req: superagent.Request, w: fs.WriteStream, timeout?: number, name?: string) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"superagent-charset": "^1.2.0",
|
"superagent-charset": "^1.2.0",
|
||||||
"superagent-proxy": "^3.0.0"
|
"superagent-proxy": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user