diff --git a/packages/fps-importer/handler.ts b/packages/fps-importer/handler.ts index dbca57d1e..3d1c53562 100644 --- a/packages/fps-importer/handler.ts +++ b/packages/fps-importer/handler.ts @@ -10,7 +10,7 @@ import { buildContent } from 'hydrooj/dist/lib/content'; import { ProblemAdd } from 'hydrooj/dist/lib/ui'; import * as solution from 'hydrooj/dist/model/solution'; import { PERM } from 'hydrooj/dist/model/builtin'; -import { BadRequestError, ValidationError } from 'hydrooj/dist/error'; +import { FileTooLargeError, ValidationError } from 'hydrooj/dist/error'; class FpsProblemImportHandler extends Handler { async get() { @@ -64,7 +64,7 @@ class FpsProblemImportHandler extends Handler { const tasks = []; try { const file = await fs.stat(this.request.files.file.path); - if (file.size > 64 * 1024 * 1024) throw new BadRequestError('File too large'); + if (file.size > 64 * 1024 * 1024) throw new FileTooLargeError('64m'); const content = fs.readFile(this.request.files.file.path).toString(); const result = await xml2js.parseStringPromise(content); tasks.push(result); @@ -73,7 +73,7 @@ class FpsProblemImportHandler extends Handler { for (const entry of zip.getEntries()) { try { const buf = entry.getData(); - if (buf.byteLength > 64 * 1024 * 1024) throw new BadRequestError('File too large'); + if (buf.byteLength > 64 * 1024 * 1024) throw new FileTooLargeError('64m'); const content = buf.toString(); const result = await xml2js.parseStringPromise(content); tasks.push(result); diff --git a/packages/fps-importer/package.json b/packages/fps-importer/package.json index ca197abc5..fe9ece0d6 100644 --- a/packages/fps-importer/package.json +++ b/packages/fps-importer/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/fps-importer", - "version": "1.1.8", + "version": "1.1.9", "description": "Import FPS problems", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index f051c38d4..de121b529 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.20.8", + "version": "2.20.9", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/error.ts b/packages/hydrooj/src/error.ts index 799a1cf8f..8000f3f50 100644 --- a/packages/hydrooj/src/error.ts +++ b/packages/hydrooj/src/error.ts @@ -1,6 +1,7 @@ /* eslint-disable max-len */ /* eslint-disable func-names */ import { isClass } from './utils'; +import { PERMS } from './model/builtin'; interface IHydroError { new(...args: any[]): HydroError @@ -10,8 +11,9 @@ function isHydroError(item: any): item is IHydroError { return isClass(item); } -const Err = (name: string, ...info: Array string) | string | number>) => { +const Err = (name: string, ...info: Array string) | ((_super: Function, ...args: any) => HydroError) | string | number>) => { let Class: IHydroError; + let constr: (_super: Function, ...args: any[]) => HydroError; let msg: () => string; let code: number; for (const item of info) { @@ -21,20 +23,29 @@ const Err = (name: string, ...info: Array string) | string msg = function () { return item; }; } else if (isHydroError(item)) { Class = item; - } else if (typeof item === 'function') { + } else if (typeof item === 'function' && item.length === 0) { + // @ts-ignore msg = item; + } else if (typeof item === 'function') { + // @ts-ignore + constr = item; } } const HydroError = class extends Class { }; HydroError.prototype.name = name; if (msg) HydroError.prototype.msg = msg; if (code) HydroError.prototype.code = code; + if (constr) { + const original = HydroError.prototype.constructor; + HydroError.prototype.constructor = function (...args: any[]) { + constr.call(this, original.bind(this, ...args)); + }; + } return HydroError; }; export class HydroError extends Error { params: any[]; - code: number; constructor(...params: any[]) { @@ -65,7 +76,12 @@ export const InvalidTokenError = Err('InvalidTokenError', ForbiddenError); export const BlacklistedError = Err('BlacklistedError', ForbiddenError, 'Address or user {0} is blacklisted.'); export const VerifyPasswordError = Err('VerifyPasswordError', ForbiddenError, "Passwords don't match."); export const OpcountExceededError = Err('OpcountExceededError', ForbiddenError, 'Too frequent operations of {0} (limit: {2} operations in {1} seconds).'); -export const PermissionError = Err('PermissionError', ForbiddenError, "You don't have the required permission ({0}) in this domain."); +export const PermissionError = Err('PermissionError', ForbiddenError, function () { + if (typeof this.params[0] === 'bigint') { + this.params[0] = PERMS.find(({ key }) => key === this.params[0])?.desc || this.params[0]; + } + return "You don't have the required permission ({0}) in this domain."; +}); export const PrivilegeError = Err('PrivilegeError', ForbiddenError, function () { if (this.params.includes(global.Hydro.model.builtin.PRIV.PRIV_USER_PROFILE)) { return "You're not logged in."; @@ -89,6 +105,7 @@ export const DomainAlreadyExistsError = Err('DomainAlreadyExistsError', Forbidde export const DomainJoinForbiddenError = Err('DomainJoinForbiddenError', ForbiddenError, 'You are not allowed to join the domain. The link is either invalid or expired.'); export const DomainJoinAlreadyMemberError = Err('DomainJoinAlreadyMemberError', ForbiddenError, 'Failed to join the domain. You are already a member.'); export const InvalidJoinInvitationCodeError = Err('InvalidJoinInvitationCodeError', ForbiddenError, 'The invitation code you provided is invalid.'); +export const CurrentPasswordError = Err('CurrentPasswordError', ForbiddenError, "Current password doesn't match."); export const UserNotFoundError = Err('UserNotFoundError', NotFoundError, 'User {0} not found.'); export const NoProblemError = Err('NoProblemError', NotFoundError, 'No problem.'); @@ -105,6 +122,7 @@ export const DiscussionNotFoundError = Err('DiscussionNotFoundError', DocumentNo export const DiscussionNodeNotFoundError = Err('DiscussionNodeNotFoundError', DocumentNotFoundError, 'Discussion node {1} not found.'); export const InvalidOperationError = Err('InvalidOperationError', MethodNotAllowedError); +export const FileTooLargeError = Err('FileTooLargeError', ValidationError, 'The uploaded file is too long.'); global.Hydro.error = { HydroError, @@ -152,14 +170,11 @@ global.Hydro.error = { DiscussionNotFoundError, RoleAlreadyExistError, MessageNotFoundError, + FileTooLargeError, + CurrentPasswordError, }; /* -class FileTooLongError(ValidationError): - @property - def message(self): - return 'The uploaded file is too long.' - class FileTypeNotAllowedError(ValidationError): @property def message(self): @@ -168,11 +183,6 @@ class FileTypeNotAllowedError(ValidationError): class InvalidTokenDigestError(ForbiddenError): pass -class CurrentPasswordError(ForbiddenError): - @property - def message(self): - return "Current password doesn't match." - class DiscussionCategoryAlreadyExistError(ForbiddenError): @property def message(self): diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index d2b61b55b..2898e7a35 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -153,13 +153,12 @@ class ContestScoreboardRawHandler extends ContestHandler { class ContestScoreboardDownloadHandler extends ContestHandler { @param('tid', Types.ObjectID) - @param('ext', Types.String) + @param('ext', Types.Range(['csv', 'html'])) async get(domainId: string, tid: ObjectID, ext: string) { const getContent = { csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`, html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }), }; - if (!getContent[ext]) throw new ValidationError('ext'); const [tdoc, rows] = await this.getScoreboard(domainId, tid, true); this.binary(await getContent[ext](rows), `${tdoc.title}.${ext}`); } diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index 3e4e81c7f..f6dc9959a 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -4,6 +4,8 @@ import { RoleAlreadyExistError, ValidationError, DomainJoinForbiddenError, DomainJoinAlreadyMemberError, InvalidJoinInvitationCodeError, } from '../error'; +import { log2 } from '../utils'; +import type { DomainDoc } from '../interface'; import * as user from '../model/user'; import * as domain from '../model/domain'; import * as system from '../model/system'; @@ -14,7 +16,6 @@ import paginate from '../lib/paginate'; import { Route, Handler, Types, param, } from '../service/server'; -import { DomainDoc } from '../interface'; class DomainRankHandler extends Handler { @param('page', Types.PositiveInt, true) @@ -124,10 +125,6 @@ class DomainUserHandler extends ManageHandler { class DomainPermissionHandler extends ManageHandler { async get({ domainId }) { const roles = await domain.getRoles(domainId); - const log2 = (val: bigint) => { - // @ts-ignore - for (let i = 0n; ; i++) if (!(val >> i)) return +i.toString() - 1; - }; const path = [ ['Hydro', 'homepage'], ['domain', null], diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 49bee8d6b..4ad17f90f 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -521,7 +521,7 @@ interface GeoIP { lookup: (ip: string, locale?: string) => any, } -export interface Lib { +export interface Lib extends Record { download: typeof import('./lib/download'), buildContent: typeof import('./lib/content').buildContent, 'hash.hydro': typeof import('./lib/hash.hydro'), diff --git a/packages/hydrooj/src/utils.ts b/packages/hydrooj/src/utils.ts index 04f1ed51d..38f576d64 100644 --- a/packages/hydrooj/src/utils.ts +++ b/packages/hydrooj/src/utils.ts @@ -257,3 +257,12 @@ export namespace Time { return constructedObjectId; } } + +export const log2 = (val: bigint | number) => { + if (typeof val === 'bigint') { + // @ts-ignore + for (let i = 0n; ; i++) if (!(val >> i)) return +i.toString() - 1; + } else { + for (let i = 0; ; i++) if (!(val >> i)) return i - 1; + } +};