mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
core: 跨站发送题目
This commit is contained in:
1 parent
61a941b989
commit
bf81742fb5
8 files changed
+169
-75
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.17.22",
|
||||
"version": "2.18.0",
|
||||
"bin": "bin/hydrooj.js",
|
||||
"main": "dist/loader.js",
|
||||
"typings": "dist/loader.d.ts",
|
||||
|
||||
@@ -26,7 +26,7 @@ export const builtinModel = [
|
||||
export const builtinHandler = [
|
||||
'home', 'problem', 'record', 'judge', 'user',
|
||||
'contest', 'training', 'discussion', 'manage', 'import.syzoj',
|
||||
'misc', 'homework', 'domain',
|
||||
'misc', 'homework', 'domain', 'remote',
|
||||
];
|
||||
|
||||
export const builtinScript = [
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { isSafeInteger, flatten } from 'lodash';
|
||||
import yaml from 'js-yaml';
|
||||
import fs from 'fs-extra';
|
||||
import { FilterQuery, ObjectID } from 'mongodb';
|
||||
import superagent from 'superagent';
|
||||
import AdmZip from 'adm-zip';
|
||||
import {
|
||||
NoProblemError, BadRequestError, PermissionError,
|
||||
SolutionNotFoundError, ProblemNotFoundError, ValidationError,
|
||||
NoProblemError, PermissionError, ValidationError,
|
||||
SolutionNotFoundError, ProblemNotFoundError,
|
||||
} from '../error';
|
||||
import {
|
||||
Pdoc, User, Rdoc, PathComponent,
|
||||
} from '../interface';
|
||||
import paginate from '../lib/paginate';
|
||||
import { isTitle, isContent, isPid } from '../lib/validator';
|
||||
import { ProblemAdd } from '../lib/ui';
|
||||
import * as problem from '../model/problem';
|
||||
import * as record from '../model/record';
|
||||
import * as domain from '../model/domain';
|
||||
import * as system from '../model/system';
|
||||
import * as token from '../model/token';
|
||||
import * as user from '../model/user';
|
||||
import * as solution from '../model/solution';
|
||||
import { PERM, PRIV, CONSTANT } from '../model/builtin';
|
||||
@@ -623,63 +618,7 @@ export class ProblemCreateHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
export class ProblemImportHandler extends Handler {
|
||||
async get() {
|
||||
this.response.body = { type: 'Hydro' };
|
||||
this.response.template = 'problem_import.html';
|
||||
}
|
||||
|
||||
async post({ domainId }) {
|
||||
if (!this.request.files.file) throw new ValidationError('file');
|
||||
const stat = await fs.stat(this.request.files.file.path);
|
||||
if (stat.size > 128 * 1024 * 1024) throw new BadRequestError('File too large');
|
||||
const zip = new AdmZip(this.request.files.file.path);
|
||||
const pdoc = JSON.parse(zip.getEntry('problem.json').getData().toString());
|
||||
const pid = await problem.add(domainId, pdoc.pid, pdoc.title, pdoc.content, this.user._id, pdoc.tags, pdoc.category);
|
||||
const entries = zip.getEntries();
|
||||
for (const entry of entries) {
|
||||
if (entry.name.startsWith('testdata/')) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await problem.addTestdata(domainId, pid, entry.name.split('testdata/')[1], entry.getData());
|
||||
} else if (entry.name.startsWith('additional_file/')) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await problem.addAdditionalFile(domainId, pid, entry.name.split('testdata/')[1], entry.getData());
|
||||
}
|
||||
}
|
||||
await problem.edit(domainId, pid, { html: pdoc.html });
|
||||
this.response.redirect = this.url('problem_detail', { pid });
|
||||
}
|
||||
}
|
||||
|
||||
export class ProblemSendHandler extends Handler {
|
||||
async get() {
|
||||
this.response.template = 'problem_send.html';
|
||||
}
|
||||
|
||||
@post('target', Types.String)
|
||||
@post('pids', Types.Array)
|
||||
async post(domainId: string, target: any, pids: number[]) {
|
||||
target = target.split('@');
|
||||
const getHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
|
||||
const pdocs = await problem.getList(domainId, pids, getHidden, true);
|
||||
const getData = this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
|
||||
if (!getData) {
|
||||
for (const pid in pdocs) {
|
||||
if (pdocs[pid].owner !== this.user._id) {
|
||||
throw new PermissionError(PERM.PERM_READ_PROBLEM_DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
const [source, expire] = system.getMany(['server.url', 'session.expire_seconds']);
|
||||
const tokenId = await token.createOrUpdate(token.TYPE_EXPORT, expire, { domainId, pids });
|
||||
await superagent.post(`${target[1]}/d/${target[0]}/problem/receive`)
|
||||
.send({ source, domainId, tokenId });
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
ProblemAdd('problem_import', {}, 'copy', 'Import From Hydro');
|
||||
Route('problem_main', '/p', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_category', '/p/category/:category', ProblemCategoryHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_random', '/problem/random', ProblemRandomHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
@@ -695,8 +634,6 @@ export async function apply() {
|
||||
Route('problem_solution_raw', '/p/:pid/solution/:psid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
|
||||
Route('problem_import', '/problem/import', ProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
|
||||
Route('problem_send', '/problem/send', ProblemSendHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Connection('problem_pretest_conn', '/conn/pretest', ProblemPretestConnectionHandler, PERM.PERM_SUBMIT_PROBLEM);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import superagent from 'superagent';
|
||||
import { PassThrough } from 'stream';
|
||||
import { PermissionError, InvalidTokenError } from '../error';
|
||||
import { ProblemAdd } from '../lib/ui';
|
||||
import { PERM } from '../model/builtin';
|
||||
import * as token from '../model/token';
|
||||
import * as problem from '../model/problem';
|
||||
import * as system from '../model/system';
|
||||
import {
|
||||
Handler, Types, Route, post,
|
||||
} from '../service/server';
|
||||
import storage from '../service/storage';
|
||||
|
||||
export class ProblemSendHandler extends Handler {
|
||||
async get() {
|
||||
this.response.template = 'problem_send.html';
|
||||
}
|
||||
|
||||
@post('target', Types.String)
|
||||
@post('pids', Types.Array)
|
||||
async postSend(domainId: string, target: any, _pids: (number | string)[]) {
|
||||
target = target.split('@');
|
||||
const getHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
|
||||
const getData = this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
|
||||
const pdocs = await problem.getList(domainId, _pids, getHidden, true, ['docId', 'owner']);
|
||||
const pids = new Set<number>();
|
||||
for (const key in pdocs) {
|
||||
pids.add(pdocs[key].docId);
|
||||
if (!getData && pdocs[key].owner !== this.user._id) {
|
||||
throw new PermissionError(PERM.PERM_READ_PROBLEM_DATA);
|
||||
}
|
||||
}
|
||||
const [url, expire] = system.getMany(['server.url', 'session.saved_expire_seconds']);
|
||||
const tokenId = await token.createOrUpdate(token.TYPE_EXPORT, expire, { domainId, pids: Array.from(pids) });
|
||||
await superagent.post(`${target[1]}/d/${target[0]}/problem/receive`)
|
||||
.send({
|
||||
operation: 'request', url: `${url}d/${domainId}/problem/send`, tokenId, expire,
|
||||
});
|
||||
this.back();
|
||||
}
|
||||
|
||||
@post('token', Types.String)
|
||||
async postInfo(domainId: string, tokenId: string) {
|
||||
const data = await token.get(tokenId, token.TYPE_EXPORT);
|
||||
if (!data) throw new InvalidTokenError(tokenId);
|
||||
const r = {};
|
||||
const pdocs = await problem.getMulti(domainId, { docId: { $in: data.pids } }, ['title', 'docId']).toArray();
|
||||
for (const pdoc of pdocs) r[pdoc.docId] = pdoc.title;
|
||||
this.response.body = { pdocs: r, pids: data.pids };
|
||||
}
|
||||
|
||||
@post('token', Types.String, true)
|
||||
@post('pids', Types.Array)
|
||||
async postFetch(domainId: string, tokenId: string, pids: number[]) {
|
||||
pids = pids.map((i) => (+i ? +i : i));
|
||||
const pdocs = await problem.getMulti(domainId, { docId: { $in: pids } }, problem.PROJECTION_PUBLIC).toArray();
|
||||
const data = await token.get(tokenId, token.TYPE_EXPORT);
|
||||
if (!data) throw new InvalidTokenError(tokenId);
|
||||
for (const pid of pids) if (!data.pids.includes(pid)) throw new InvalidTokenError(tokenId);
|
||||
const links = [];
|
||||
for (const pdoc of pdocs) {
|
||||
const files = await storage.list(`problem/${domainId}/${pdoc.docId}/`, true);
|
||||
const l = [];
|
||||
for (const file of files) {
|
||||
l.push([
|
||||
file.name,
|
||||
await storage.signDownloadLink(file.prefix + file.name, file.name, false, 'user'),
|
||||
]);
|
||||
}
|
||||
links.push(l);
|
||||
}
|
||||
this.response.body = { pdocs, links };
|
||||
}
|
||||
}
|
||||
|
||||
export class ProblemReceiveHandler extends Handler {
|
||||
async get({ domainId }) {
|
||||
this.checkPerm(PERM.PERM_CREATE_PROBLEM);
|
||||
const requests = await token.getMulti(token.TYPE_IMPORT, { domainId }).toArray();
|
||||
this.response.template = 'problem_receive.html';
|
||||
this.response.body = { requests };
|
||||
}
|
||||
|
||||
@post('url', Types.String)
|
||||
@post('tokenId', Types.String)
|
||||
@post('expire', Types.UnsignedInt)
|
||||
async postRequest(domainId: string, url: string, tokenId: string, expire: number) {
|
||||
const res = await superagent.post(url)
|
||||
.send({ operation: 'info', token: tokenId });
|
||||
const [id] = await token.add(token.TYPE_IMPORT, expire, {
|
||||
domainId, tokenId, source: url, pdocs: res.body.pdocs, pids: res.body.pids,
|
||||
});
|
||||
this.response.body = { token: id };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
async syncFiles(domainId: string, baseUrl: string, pid: number, files: [string, string][]) {
|
||||
for (const file of files) {
|
||||
const content = new PassThrough();
|
||||
superagent.get(file[1].startsWith('/') ? baseUrl + file[1] : file[1]).pipe(content);
|
||||
if (file[0].startsWith('testdata/')) {
|
||||
await problem.addTestdata(domainId, pid, file[0].split('testdata/')[1], content);
|
||||
} else if (file[0].startsWith('additional_file/')) {
|
||||
await problem.addAdditionalFile(domainId, pid, file[0].split('additional_file/')[1], content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@post('tokenId', Types.String)
|
||||
@post('pids', Types.Array, true)
|
||||
async postConfirm(domainId: string, tokenId: string, filterPid?: number[]) {
|
||||
this.checkPerm(PERM.PERM_CREATE_PROBLEM);
|
||||
const data = await token.get(tokenId, token.TYPE_IMPORT);
|
||||
if (!data) throw new InvalidTokenError(tokenId);
|
||||
const res = await superagent.post(data.source).send({
|
||||
operation: 'fetch',
|
||||
token: data.tokenId,
|
||||
pids: filterPid.length ? filterPid.map((i) => (+i ? +i : i)) : data.pids,
|
||||
});
|
||||
const tasks = [];
|
||||
const pids = [];
|
||||
const { pdocs } = res.body;
|
||||
const files = res.body.links;
|
||||
const source = new URL(data.source);
|
||||
const baseUrl = `${source.protocol}//${source.host}`;
|
||||
for (let i = 0; i < pdocs.length; i++) {
|
||||
const pdoc = pdocs[i];
|
||||
if (pdoc.pid) {
|
||||
const exist = await problem.get(domainId, pdoc.pid);
|
||||
if (exist) pdoc.pid = undefined;
|
||||
}
|
||||
const pid = await problem.add(domainId, pdoc.pid, pdoc.title, pdoc.content, this.user._id, pdoc.tag, pdoc.category);
|
||||
pids.push(pid);
|
||||
tasks.push(this.syncFiles(domainId, baseUrl, pid, files[i]));
|
||||
await problem.edit(domainId, pid, { html: pdoc.html });
|
||||
}
|
||||
this.back();
|
||||
this.response.body = { pids };
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
ProblemAdd('problem_receive', {}, 'copy', 'Import From Hydro');
|
||||
Route('problem_receive', '/problem/receive', ProblemReceiveHandler);
|
||||
Route('problem_send', '/problem/send', ProblemSendHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
}
|
||||
|
||||
global.Hydro.handler.remote = apply;
|
||||
@@ -37,7 +37,7 @@ export interface SystemKeys {
|
||||
'problem.categories': string,
|
||||
'session.keys': string[],
|
||||
'session.secure': boolean,
|
||||
'session.expire_seconds': number,
|
||||
'session.saved_expire_seconds': number,
|
||||
'session.unsaved_expire_seconds': number,
|
||||
'user.quota': number,
|
||||
}
|
||||
@@ -408,9 +408,7 @@ export interface ContestRule {
|
||||
}
|
||||
|
||||
export type ContestRules = Dictionary<ContestRule>;
|
||||
|
||||
export type ProblemImporter = (url: string, handler: any) =>
|
||||
Promise<[Pdoc, fs.ReadStream?]> | [Pdoc, fs.ReadStream?];
|
||||
export type ProblemImporter = (url: string, handler: any) => Promise<[Pdoc, fs.ReadStream?]> | [Pdoc, fs.ReadStream?];
|
||||
|
||||
export interface Script {
|
||||
run: (args: any, report: Function) => any,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Dictionary, pick } from 'lodash';
|
||||
import { STATUS } from './builtin';
|
||||
import * as document from './document';
|
||||
import * as domain from './domain';
|
||||
import { buildProjection } from '../utils';
|
||||
import { ProblemStatusDoc, Pdict } from '../interface';
|
||||
import { Content } from '../loader';
|
||||
import { ArrayKeys, NumberKeys, Projection } from '../typeutils';
|
||||
@@ -193,14 +194,15 @@ export async function random(domainId: string, query: FilterQuery<Pdoc>): Promis
|
||||
|
||||
export async function getList(
|
||||
domainId: string, pids: Array<number | string>,
|
||||
getHidden = false, doThrow = true,
|
||||
getHidden = false, doThrow = true, projection = PROJECTION_PUBLIC,
|
||||
): Promise<Pdict> {
|
||||
pids = Array.from(new Set(pids));
|
||||
pids = Array.from(new Set(pids)).map((i) => (+i ? +i : i));
|
||||
const r = {};
|
||||
const l = {};
|
||||
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
|
||||
if (!getHidden) q.hidden = false;
|
||||
const pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q).toArray();
|
||||
const pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q)
|
||||
.project(buildProjection(projection)).toArray();
|
||||
for (const pdoc of pdocs) {
|
||||
r[pdoc.docId] = pdoc;
|
||||
l[pdoc.pid] = pdoc;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FilterQuery } from 'mongodb';
|
||||
import { TokenDoc } from '../interface';
|
||||
import db from '../service/db';
|
||||
import * as bus from '../service/bus';
|
||||
@@ -10,6 +11,7 @@ export const TYPE_CHANGEMAIL = 3;
|
||||
export const TYPE_OAUTH = 4;
|
||||
export const TYPE_LOSTPASS = 5;
|
||||
export const TYPE_EXPORT = 6;
|
||||
export const TYPE_IMPORT = 7;
|
||||
|
||||
function ensureIndexes() {
|
||||
return Promise.all([
|
||||
@@ -38,6 +40,10 @@ export async function get(tokenId: string, tokenType: number): Promise<TokenDoc
|
||||
return await coll.findOne({ _id: tokenId, tokenType });
|
||||
}
|
||||
|
||||
export function getMulti(tokenType: number, query: FilterQuery<TokenDoc> = {}) {
|
||||
return coll.find({ tokenType, ...query });
|
||||
}
|
||||
|
||||
export async function update(
|
||||
tokenId: string, tokenType: number, expireSeconds: number,
|
||||
data: object,
|
||||
@@ -98,10 +104,12 @@ global.Hydro.model.token = {
|
||||
TYPE_REGISTRATION,
|
||||
TYPE_LOSTPASS,
|
||||
TYPE_EXPORT,
|
||||
TYPE_IMPORT,
|
||||
|
||||
add,
|
||||
createOrUpdate,
|
||||
get,
|
||||
getMulti,
|
||||
update,
|
||||
del,
|
||||
delByUid,
|
||||
|
||||
@@ -541,7 +541,7 @@ export class Handler extends HandlerCommon {
|
||||
|
||||
async saveCookie() {
|
||||
const expireSeconds = this.session.save
|
||||
? system.get('session.expire_seconds')
|
||||
? system.get('session.saved_expire_seconds')
|
||||
: system.get('session.unsaved_expire_seconds');
|
||||
const $update = {
|
||||
updateIp: this.request.ip,
|
||||
|
||||
Reference in new issue
Block a user