From 82c193b5ee94cf2b3826d410969151a2080c1ea4 Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 12 Jul 2020 18:59:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E4=BC=98=E5=8C=96=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20Date.prototype.format=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=A2=98=E7=9B=AE=E5=88=86=E7=B1=BB=E8=A7=A3=E6=9E=90=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98=20Interface=20Tdoc=20X-For?= =?UTF-8?q?warded-For=20=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hydro/error.ts | 7 +- hydro/handler/contest.ts | 8 +- hydro/handler/discussion.ts | 8 + hydro/handler/domain.ts | 2 + hydro/handler/home.ts | 4 +- hydro/handler/judge.ts | 2 + hydro/handler/manage.ts | 5 +- hydro/handler/misc.ts | 2 +- hydro/handler/problem.ts | 62 +++++--- hydro/handler/training.ts | 3 + hydro/interface.ts | 17 ++- hydro/lib/mail.ts | 9 +- hydro/model/builtin.ts | 2 +- hydro/model/contest.ts | 8 +- hydro/model/problem.ts | 8 +- hydro/model/setting.ts | 1 + hydro/service/server.ts | 295 ++++++++++++++++++++++-------------- hydro/utils.ts | 24 +-- package.json | 4 +- 19 files changed, 289 insertions(+), 182 deletions(-) diff --git a/hydro/error.ts b/hydro/error.ts index 55ec79433..a985b8eee 100644 --- a/hydro/error.ts +++ b/hydro/error.ts @@ -52,6 +52,7 @@ export const ForbiddenError = Err('ForbiddenError', UserFacingError, 'ForbiddenE export const NotFoundError = Err('NotFoundError', UserFacingError, 'NotFoundError', 404); export const MethodNotAllowedError = Err('MethodNotAllowedError', UserFacingError, 'MethodNotAllowedError', 405); export const RemoteOnlineJudgeError = Err('RemoteOnlineJudgeError', UserFacingError, 'RemoteOnlineJudgeError', 500); +export const SendMailError = Err('SendMailError', UserFacingError, 'Failed to send mail to {0}.', 500); export const AlreadyVotedError = Err('AlreadyVotedError', ForbiddenError, "You've already voted."); export const LoginError = Err('LoginError', ForbiddenError, 'Invalid password for user {0}.'); @@ -103,6 +104,7 @@ global.Hydro.error = { BlacklistedError, ForbiddenError, NotFoundError, + SendMailError, LoginError, CsrfTokenError, MethodNotAllowedError, @@ -188,9 +190,4 @@ class InvalidArgumentError(BadRequestError): @property def message(self): return 'Argument {0} is invalid.' - -class SendMailError(UserFacingError): - @property - def message(self): - return 'Failed to send mail to {0}.' */ diff --git a/hydro/handler/contest.ts b/hydro/handler/contest.ts index 8af07c695..c67179cdd 100644 --- a/hydro/handler/contest.ts +++ b/hydro/handler/contest.ts @@ -155,8 +155,8 @@ class ContestEditHandler extends ContestHandler { duration, path, pids: this.tdoc.pids.join(','), - date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`, - time_text: `${dt.getHours()}:${dt.getMinutes()}`, + date_text: dt.format('%Y-%m-%d'), + time_text: dt.format('%H:%M'), page_name: 'contest_edit', }; } @@ -337,8 +337,8 @@ class ContestCreateHandler extends ContestHandler { rules, path, page_name: 'contest_create', - date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`, - time_text: `${dt.getHours()}:${dt.getMinutes()}`, + date_text: dt.format('%Y-%m-%d'), + time_text: dt.format('%M:%S'), pids: '1000, 1001', }; } diff --git a/hydro/handler/discussion.ts b/hydro/handler/discussion.ts index e4c47d145..4205895c0 100644 --- a/hydro/handler/discussion.ts +++ b/hydro/handler/discussion.ts @@ -21,6 +21,14 @@ export const typeMapper = { }; class DiscussionHandler extends Handler { + ddoc?: any; + + drdoc?: any; + + drrdoc?: any; + + vnode?: any; + async _prepare({ domainId, type, name, did, drid, drrid, }) { diff --git a/hydro/handler/domain.ts b/hydro/handler/domain.ts index fdd9ceb01..8d5be6b8e 100644 --- a/hydro/handler/domain.ts +++ b/hydro/handler/domain.ts @@ -60,6 +60,8 @@ class DomainCreateHandler extends Handler { } class ManageHandler extends Handler { + domain: any; + async prepare({ domainId }) { this.checkPerm(PERM.PERM_EDIT_DOMAIN); this.domain = await domain.get(domainId); diff --git a/hydro/handler/home.ts b/hydro/handler/home.ts index a1d85e8a1..1dab44970 100644 --- a/hydro/handler/home.ts +++ b/hydro/handler/home.ts @@ -147,10 +147,10 @@ class HomeSecurityHandler extends Handler { const [code] = await token.add( token.TYPE_CHANGEMAIL, await system.get('changemail_token_expire_seconds'), - { uid: this.udoc._id, mail }, + { uid: this.user._id, mail }, ); await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', { - url: this.url('user_changemail_with_code', { code }), uname: this.udoc.uname, + url: this.url('user_changemail_with_code', { code }), uname: this.user.uname, }); this.response.template = 'user_changemail_mail_sent.html'; } diff --git a/hydro/handler/judge.ts b/hydro/handler/judge.ts index eea36bc97..cf012ec1f 100644 --- a/hydro/handler/judge.ts +++ b/hydro/handler/judge.ts @@ -155,6 +155,8 @@ class JudgeHandler extends Handler { } class JudgeConnectionHandler extends ConnectionHandler { + processing: any; + async message(msg) { if (msg.key === 'next') await next(msg); else if (msg.key === 'end') { diff --git a/hydro/handler/manage.ts b/hydro/handler/manage.ts index 1f2c319a8..d3bf56cd4 100644 --- a/hydro/handler/manage.ts +++ b/hydro/handler/manage.ts @@ -15,6 +15,7 @@ function set(key, value) { if (setting.SYSTEM_SETTINGS_BY_KEY[key]) { const s = setting.SYSTEM_SETTINGS_BY_KEY[key]; if (s.flag & setting.FLAG_DISABLED) return undefined; + if (s.flag & setting.FLAG_SECRET && !value) return undefined; if (s.type === 'boolean') { if (value === 'on') return true; return false; @@ -48,6 +49,8 @@ class SystemMainHandler extends SystemHandler { } class SystemCheckConnHandler extends ConnectionHandler { + id: any; + async prepare() { this.checkPriv(PRIV.PRIV_EDIT_SYSTEM); await this.check(); @@ -61,7 +64,7 @@ class SystemCheckConnHandler extends ConnectionHandler { } async cleanup() { - check.cancel(this.token); + check.cancel(this.id); } } diff --git a/hydro/handler/misc.ts b/hydro/handler/misc.ts index e6f0dd2de..86e00fbd1 100644 --- a/hydro/handler/misc.ts +++ b/hydro/handler/misc.ts @@ -17,7 +17,7 @@ class FileDownloadHandler extends Handler { @param('name', Types.String, true) async get(domainId: string, fileId: ObjectID, secret: string, name: string) { if (name) name = Buffer.from(name, 'base64').toString(); - this.response.attachment(name || fileId); + this.response.attachment(name || fileId.toHexString()); this.response.body = await file.getWithSecret(fileId, secret); } } diff --git a/hydro/handler/problem.ts b/hydro/handler/problem.ts index 551cfbca0..15c833ea4 100644 --- a/hydro/handler/problem.ts +++ b/hydro/handler/problem.ts @@ -19,8 +19,13 @@ import { NoProblemError, ProblemDataNotFoundError, BadRequestError, SolutionNotFoundError, } from '../error'; +import { Pdoc, Udoc, Rdoc } from '../interface'; -const parseCategory = (value: string) => flatten(value.split('+').map((e) => e.split(','))).map((e) => e.trim()); +const parseCategory = (value: string) => { + if (!value) return []; + console.log(value); + return flatten(value.split('+').map((e) => e.split(','))).map((e) => e.trim()); +}; const parsePid = (value: string) => (isSafeInteger(value) ? parseInt(value, 10) : value); class ProblemHandler extends Handler { @@ -99,7 +104,7 @@ class ProblemMainHandler extends ProblemHandler { class ProblemCategoryHandler extends ProblemHandler { @param('page', Types.UnsignedInt, true) - @param('category', Types.String, parseCategory) + @param('category', Types.String, null, parseCategory) async get(domainId: string, page = 1, category: string[]) { this.response.template = 'problem_main.html'; const q: any = { $and: [] }; @@ -136,7 +141,7 @@ class ProblemCategoryHandler extends ProblemHandler { class ProblemRandomHandler extends ProblemHandler { @param('category', Types.String, true, null, parseCategory) - async get(domainId: string, category: string[]) { + async get(domainId: string, category: string[] = []) { const q: any = category.length ? { $and: [] } : {}; for (const name of category) { if (name) { @@ -157,31 +162,32 @@ class ProblemRandomHandler extends ProblemHandler { } class ProblemDetailHandler extends ProblemHandler { + pdoc: Pdoc; + + udoc: Udoc; + @param('pid', Types.String, null, parsePid) async _prepare(domainId: string, pid: number | string) { this.response.template = 'problem_detail.html'; - if (pid) { - this.pdoc = await problem.get(domainId, pid, this.user._id); - if (this.pdoc.hidden && this.pdoc.owner !== this.user._id) { - this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); - } - if (this.pdoc) this.udoc = await user.getById(domainId, this.pdoc.owner); + this.pdoc = await problem.get(domainId, pid, this.user._id, true); + if (this.pdoc.hidden && this.pdoc.owner !== this.user._id) { + this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); } + this.udoc = await user.getById(domainId, this.pdoc.owner); this.response.body = { pdoc: this.pdoc, udoc: this.udoc, - title: (this.pdoc || {}).title || '', + title: this.pdoc.title, + path: [ + ['Hydro', 'homepage'], + ['problem_main', 'problem_main'], + [this.pdoc.title, null, true], + ], }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async get(...args: any) { - this.response.body.path = [ - ['Hydro', 'homepage'], - ['problem_main', 'problem_main'], - [this.pdoc.title, null, true], - ]; - } + // eslint-disable-next-line + async get(...args: any[]) { } } class ProblemSubmitHandler extends ProblemDetailHandler { @@ -234,6 +240,10 @@ class ProblemPretestHandler extends ProblemDetailHandler { } class ProblemPretestConnectionHandler extends ConnectionHandler { + pid: string; + + domainId: string; + @param('pid', Types.String) async prepare(domainId: string, pid: string) { this.pid = pid; @@ -242,7 +252,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler { } async onRecordChange(data) { - const rdoc = data.value; + const rdoc: Rdoc = data.value; if ( rdoc.uid !== this.user._id || rdoc.pid.toString() !== this.pid @@ -339,23 +349,25 @@ class ProblemDataUploadHandler extends ProblemManageHandler { } async get() { + let md5; if (this.pdoc.data && typeof this.pdoc.data === 'object') { const f = await file.getMeta(this.pdoc.data); - this.md5 = f.md5; + md5 = f.md5; } - this.response.body.md5 = this.md5; + this.response.body.md5 = md5; } async post({ domainId }) { + let md5; if (!this.request.files.file) throw new BadRequestError(); await problem.setTestdata( domainId, this.pdoc.docId, this.request.files.file.path, ); if (this.pdoc.data && typeof this.pdoc.data === 'object') { const f = await file.getMeta(this.pdoc.data); - this.md5 = f.md5; + md5 = f.md5; } - this.response.body.md5 = this.md5; + this.response.body.md5 = md5; } } @@ -368,7 +380,7 @@ class ProblemDataDownloadHandler extends ProblemDetailHandler { } if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid); else if (typeof this.pdoc.data === 'string') [, this.response.redirect] = this.pdoc.data.split('from:'); - this.response.redirect = await file.url(this.pdoc.data, `${this.pdoc.title}.zip`); + else this.response.redirect = await file.url(this.pdoc.data, `${this.pdoc.title}.zip`); } } @@ -416,7 +428,7 @@ class ProblemSolutionHandler extends ProblemDetailHandler { let psdoc = await solution.get(domainId, psid); if (psdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION); else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF); - psdoc = await solution.edit(domainId, this.psdoc.docId, content); + psdoc = await solution.edit(domainId, psdoc.docId, content); this.back({ psdoc }); } diff --git a/hydro/handler/training.ts b/hydro/handler/training.ts index 1ce84471d..d8a821f1e 100644 --- a/hydro/handler/training.ts +++ b/hydro/handler/training.ts @@ -12,6 +12,7 @@ import * as system from '../model/system'; import { Route, Handler, Types, param, } from '../service/server'; +import { Tdoc } from '../interface'; async function _parseDagJson(domainId, dag) { const parsed = []; @@ -199,6 +200,8 @@ class TrainingCreateHandler extends Handler { } class TrainingEditHandler extends Handler { + tdoc: Tdoc; + @param('tid', Types.ObjectID) async prepare(domainId: string, tid: ObjectID) { this.tdoc = await training.get(domainId, tid); diff --git a/hydro/interface.ts b/hydro/interface.ts index 45c60a0a2..2a23f7f8c 100644 --- a/hydro/interface.ts +++ b/hydro/interface.ts @@ -2,10 +2,12 @@ import { ObjectID, GridFSBucket } from 'mongodb'; import fs from 'fs'; +export type Dict = { [key: string]: T }; + export interface Setting { family: string, key: string, - range: Array<[string, string]> | { [key: string]: string }, + range: Array<[string, string]> | Dict, value: any, type: string, name: string, @@ -51,7 +53,8 @@ export interface Pdoc { nAccept: number, tag: string[], category: string[], - data?: ObjectID, + // ObjectID for built-in files, string for other source (RemoteJudge, etc.) + data?: ObjectID | string, hidden: boolean, config: string, } @@ -108,15 +111,21 @@ export interface Tdoc { domainId: string, docId: ObjectID, docType: number, + owner: number, beginAt: Date, endAt: Date, - penaltySince?: Date, - penaltyRules?: PenaltyRules, attend: number, title: string, content: string, rule: string, pids: number[], + + // For homework + penaltySince?: Date, + penaltyRules?: PenaltyRules, + + // For training + dag: any } interface ContestStat { diff --git a/hydro/lib/mail.ts b/hydro/lib/mail.ts index da3b25170..6862779c1 100644 --- a/hydro/lib/mail.ts +++ b/hydro/lib/mail.ts @@ -1,13 +1,6 @@ import nodemailer from 'nodemailer'; +import { SendMailError } from '../error'; -const { SendMailError } = require('../error'); - -/** - * @param {string} to - * @param {string} subject - * @param {string} text - * @param {string} html - */ // eslint-disable-next-line import/prefer-default-export export async function sendMail(to: string, subject: string, text: string, html: string) { let t: unknown; diff --git a/hydro/model/builtin.ts b/hydro/model/builtin.ts index d819611b7..cbb2b73c0 100644 --- a/hydro/model/builtin.ts +++ b/hydro/model/builtin.ts @@ -278,7 +278,7 @@ export const BUILTIN_USERS = [ regip: '127.0.0.1', loginat: new Date(0), loginip: '127.0.0.1', - priv: PRIV.PRIV_NONE, + priv: PRIV.PRIV_REGISTER_USER, }, { _id: 1, diff --git a/hydro/model/contest.ts b/hydro/model/contest.ts index 72e9b49aa..eb104a61b 100644 --- a/hydro/model/contest.ts +++ b/hydro/model/contest.ts @@ -390,8 +390,10 @@ export async function edit( return await document.set(domainId, type, tid, $set); } -export async function get(domainId: string, tid: ObjectID, type = document.TYPE_CONTEST) { - let tdoc; +export async function get( + domainId: string, tid: ObjectID, type = document.TYPE_CONTEST, +) { + let tdoc: Tdoc; if (type === -1) { tdoc = await document.get(domainId, document.TYPE_CONTEST, tid); if (!tdoc) tdoc = await document.get(domainId, document.TYPE_HOMEWORK, tid); @@ -535,7 +537,7 @@ export function getMulti(domainId: string, query = {}, type = document.TYPE_CONT export async function getAndListStatus( domainId: string, tid: ObjectID, docType = document.TYPE_CONTEST, -) { +): Promise<[Tdoc, any[]]> { // TODO(iceboy): projection, pagination. const tdoc = await get(domainId, tid, docType); const tsdocs = await document.getMultiStatus(domainId, docType, { docId: tid }) diff --git a/hydro/model/problem.ts b/hydro/model/problem.ts index 9f1027bea..cfe1295ca 100644 --- a/hydro/model/problem.ts +++ b/hydro/model/problem.ts @@ -1,4 +1,4 @@ -import { ObjectID, FilterQuery } from 'mongodb'; +import { ObjectID } from 'mongodb'; import { STATUS } from './builtin'; import * as file from './file'; import * as document from './document'; @@ -47,8 +47,6 @@ export async function get( ): Promise { if (typeof pid !== 'number') { if (!Number.isNaN(parseInt(pid, 10))) pid = parseInt(pid, 10); - else if (doThrow) throw new ProblemNotFoundError(domainId, pid); - else return null; } const pdoc = Number.isInteger(pid) ? await document.get(domainId, document.TYPE_PROBLEM, pid) @@ -87,11 +85,11 @@ export function inc(domainId: string, _id: number, field: string, n: number) { return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n); } -export function count(domainId: string, query: FilterQuery) { +export function count(domainId: string, query: any) { return document.count(domainId, document.TYPE_PROBLEM, query); } -export async function random(domainId: string, query: FilterQuery) { +export async function random(domainId: string, query: any) { const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query); const pcount = await cursor.count(); if (pcount) { diff --git a/hydro/model/setting.ts b/hydro/model/setting.ts index d9933b3d1..adb04180e 100644 --- a/hydro/model/setting.ts +++ b/hydro/model/setting.ts @@ -116,6 +116,7 @@ SystemSetting( Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'), Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'), Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'), + Setting('setting_server', 'server.xff', null, null, 'text', 'IP Header'), Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'), Setting('setting_ui', 'ui.header', null, null, 'text', 'Header Logo'), Setting('setting_ui', 'ui.headerBackground', null, null, 'text', 'Header Background'), diff --git a/hydro/service/server.ts b/hydro/service/server.ts index 74cf0bcb8..e0f245625 100644 --- a/hydro/service/server.ts +++ b/hydro/service/server.ts @@ -31,10 +31,11 @@ const app = new Koa(); let server; const router = new Router(); -interface IConstructor { - // @ts-ignore - new(value: any): any; +// TODO fix this ugly hack +interface H { + [key: string]: any, } + interface IHandler { // @ts-ignore new(ctx: Koa.Context): Handler; @@ -44,7 +45,7 @@ interface IConnectionHandler { new(conn: sockjs.Connection): ConnectionHandler; } type MethodDecorator = (target: any, name: string, obj: any) => any; -type Converter = IConstructor | ((value: any) => any); +type Converter = (value: any) => any; type Validator = (value: any) => boolean; interface ParamOption { name: string, @@ -64,7 +65,7 @@ const Tools: Array<[Converter, Validator, boolean?]> = [ const t = parseFloat(v); return t && !Number.isNaN(t) && !Number.isFinite(t); }], - [ObjectID, ObjectID.isValid], + [(v) => new ObjectID(v), ObjectID.isValid], [(v) => !!v, null, true], [ (v) => { @@ -105,37 +106,29 @@ export function param( ): MethodDecorator; export function param( name: string, ...args: Array -) -export function param(...args: any): MethodDecorator { +): MethodDecorator; +export function param(name: string, ...args: any): MethodDecorator { let cursor = 0; - let v: ParamOption = null; + const v: ParamOption = { name }; let isValidate = true; - const d: ParamOption[] = []; - while (cursor <= args.length) { - if (typeof args[cursor] === 'string') { - v = { name: args[cursor] }; - d.push(v); - isValidate = true; - } else if (typeof args[cursor] === 'number') { + while (cursor < args.length) { + if (typeof args[cursor] === 'number') { const type = args[cursor]; if (Tools[type]) { - if (Tools[type][0]) d[d.length - 1].convert = Tools[type][0]; - if (Tools[type][1]) d[d.length - 1].validate = Tools[type][1]; - if (Tools[type][2]) d[d.length - 1].isOptional = Tools[type][2]; + if (Tools[type][0]) v.convert = Tools[type][0]; + if (Tools[type][1]) v.validate = Tools[type][1]; + if (Tools[type][2]) v.isOptional = Tools[type][2]; } - } else if (typeof args[cursor] === 'boolean') d[d.length - 1].isOptional = args[cursor]; - else if (!isValidate) { - const I = args[cursor]; - if (typeof I.constructor === 'function') { - d[d.length - 1].convert = (val: any) => new I(val); - } else d[d.length - 1].convert = I; + } else if (typeof args[cursor] === 'boolean') v.isOptional = args[cursor]; + else if (isValidate) { + if (args[cursor] !== null) v.validate = args[cursor]; isValidate = false; - } else d[d.length - 1].validate = args[cursor]; + } else { + const I = args[cursor]; + v.convert = I; + } cursor++; } - for (let i = 0; i < d.length; i++) { - d[i].isOptional = d[i].isOptional || false; - } return function desc(target: any, funcName: string, obj: any) { if (!target.__param) target.__param = {}; if (!target.__param[target.constructor.name]) target.__param[target.constructor.name] = {}; @@ -153,7 +146,6 @@ export function param(...args: any): MethodDecorator { throw new ValidationError(item.name); } } - // @ts-ignore if (item.convert) c.push(item.convert(rawArgs[item.name])); else c.push(rawArgs[item.name]); } else c.push(undefined); @@ -161,7 +153,7 @@ export function param(...args: any): MethodDecorator { return originalMethod.call(this, ...c); }; } - target.__param[target.constructor.name][funcName].splice(1, 0, ...d); + target.__param[target.constructor.name][funcName].splice(1, 0, v); return obj; }; } @@ -180,11 +172,80 @@ export async function prepare() { })); } -const HandlerMixin = (Class: IConstructor) => class extends Class { +export class Handler { + UIContext: any; + + args: any; + + ctx: Koa.Context; + + request: { + host: string, + hostname: string, + ip: string, + headers: any, + cookies: any, + body: any, + files: any, + query: any, + path: string, + params: any, + referer: any, + json: boolean, + }; + + response: { + body: any, + type: string, + status: number, + template: string | undefined, + redirect: string | undefined, + disposition: string | undefined, + attachment: (name: string) => void, + }; + + session: any; + + csrfToken: string; + + user: any; + + constructor(ctx: Koa.Context) { + this.ctx = ctx; + this.request = { + host: ctx.request.host, + hostname: ctx.request.hostname, + ip: ctx.request.ip, + headers: ctx.request.headers, + cookies: ctx.cookies, + body: ctx.request.body, + files: ctx.request.files, + query: ctx.query, + path: ctx.path, + params: ctx.params, + referer: ctx.request.headers.referer || '/', + json: (ctx.request.headers.accept || '').includes('application/json'), + }; + this.response = { + body: {}, + type: '', + status: null, + template: null, + redirect: null, + attachment: (name) => ctx.attachment(name), + disposition: null, + }; + this.UIContext = { + cdn_prefix: '/', + url_prefix: '/', + }; + this.session = {}; + } + async renderHTML(name: string, context: any): Promise { if (enableLog) console.time(name); - this.hasPerm = (perm) => this.user.hasPerm(perm); // FIXME fix this ugly hack + // @ts-ignore this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; const res = await render(name, Object.assign(context, { handler: this, @@ -261,77 +322,6 @@ const HandlerMixin = (Class: IConstructor) => class extends Class { } return res; } -}; - -export const Handler = HandlerMixin(class { - UIContext: any; - - args: any; - - ctx: Koa.Context; - - request: { - host: string, - hostname: string, - ip: string, - headers: any, - cookies: any, - body: any, - files: any, - query: any, - path: string, - params: any, - referer: any, - json: boolean, - }; - - response: { - body: any, - type: string, - status: number, - template: string | undefined, - redirect: string | undefined, - disposition: string | undefined, - attachment: (name: string) => void, - }; - - session: any; - - csrfToken: string; - - user: any; - - constructor(ctx: Koa.Context) { - this.ctx = ctx; - this.request = { - host: ctx.request.host, - hostname: ctx.request.hostname, - ip: ctx.request.ip, - headers: ctx.request.headers, - cookies: ctx.cookies, - body: ctx.request.body, - files: ctx.request.files, - query: ctx.query, - path: ctx.path, - params: ctx.params, - referer: ctx.request.headers.referer || '/', - json: (ctx.request.headers.accept || '').includes('application/json'), - }; - this.response = { - body: {}, - type: '', - status: null, - template: null, - redirect: null, - attachment: (name) => ctx.attachment(name), - disposition: null, - }; - this.UIContext = { - cdn_prefix: '/', - url_prefix: '/', - }; - this.session = {}; - } async render(name: string, context: any) { // @ts-ignore @@ -339,7 +329,7 @@ export const Handler = HandlerMixin(class { this.response.type = 'text/html'; } - back(body: any) { + back(body?: any) { this.response.body = body || this.response.body || {}; this.response.redirect = this.request.headers.referer || '/'; } @@ -371,6 +361,8 @@ export const Handler = HandlerMixin(class { } async init({ domainId }) { + const xff = await system.get('server.xff'); + if (xff) this.request.ip = this.request.headers[xff.toLowerCase()]; await Promise.all([ this.getSession(), this.getBdoc(), @@ -481,7 +473,7 @@ export const Handler = HandlerMixin(class { }; await this.finish().catch(() => { }); } -}); +} async function handle(ctx, HandlerClass, checker) { global.Hydro.stat.reqCount++; @@ -553,13 +545,13 @@ const Checker = (permPrivChecker) => { }; }; -export function Route(name: string, route: string, RouteHandler: IHandler, ...permPrivChecker) { +export function Route(name: string, route: string, RouteHandler: any, ...permPrivChecker) { const checker = Checker(permPrivChecker); router.all(name, route, (ctx) => handle(ctx, RouteHandler, checker)); router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker)); } -export const ConnectionHandler = HandlerMixin(class { +export class ConnectionHandler { conn: sockjs.Connection; request: { @@ -587,7 +579,88 @@ export const ConnectionHandler = HandlerMixin(class { for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]); } - send(data: JSON) { + async renderHTML(name: string, context: any): Promise { + if (enableLog) console.time(name); + // FIXME fix this ugly hack + // @ts-ignore + this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; + const res = await render(name, Object.assign(context, { + handler: this, + // @ts-ignore + url: (...args) => this.url(...args), + _: (str: string) => (str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : ''), + user: this.user, + })); + if (enableLog) console.timeEnd(name); + return res; + } + + async limitRate(op, periodSecs, maxOperations) { + await opcount.inc(op, this.request.ip, periodSecs, maxOperations); + } + + translate(str) { + return str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : ''; + } + + renderTitle(str) { + return `${this.translate(str)} - Hydro`; + } + + checkPerm(...args) { + for (const i in args) { + if (args[i] instanceof Array) { + let p = false; + for (const j in args) { + if (this.user.hasPerm(args[i][j])) { + p = true; + break; + } + } + if (!p) throw new PermissionError([args[i]]); + } else if (!this.user.hasPerm(args[i])) { + throw new PermissionError([[args[i]]]); + } + } + } + + checkPriv(...args) { + for (const i in args) { + if (args[i] instanceof Array) { + let p = false; + for (const j in args) { + if (this.user.hasPriv(args[i][j])) { + p = true; + break; + } + } + if (!p) throw new PrivilegeError([args[i]]); + } else if (!this.user.hasPriv(args[i])) { + throw new PrivilegeError([[args[i]]]); + } + } + } + + url(name, kwargs = {}) { + let res = '#'; + const args: any = { ...kwargs }; + try { + if (this.args.domainId !== 'system') { + name += '_with_domainId'; + args.domainId = args.domainId || this.args.domainId; + } + const { anchor, query } = args; + if (query) res = router.url(name, args, { query }); + else res = router.url(name, args); + if (anchor) return `${res}#${anchor}`; + } catch (e) { + console.error(e.message); + console.log(name, args); + } + return res; + } + + send(data: any) { this.conn.write(JSON.stringify(data)); } @@ -611,17 +684,17 @@ export const ConnectionHandler = HandlerMixin(class { this.user = await user.getById(domainId, this.session.uid); if (!this.user) throw new UserNotFoundError(this.session.uid); } -}); +} export function Connection( name: string, prefix: string, - RouteConnHandler: IConnectionHandler, + RouteConnHandler: any, ...permPrivChecker: Array ) { const sock = sockjs.createServer({ prefix }); const checker = Checker(permPrivChecker); sock.on('connection', async (conn) => { - const h = new RouteConnHandler(conn); + const h: H = new RouteConnHandler(conn); try { const args = { domainId: 'system', ...h.request.params }; h.args = args; diff --git a/hydro/utils.ts b/hydro/utils.ts index f51c0a941..74c67b752 100644 --- a/hydro/utils.ts +++ b/hydro/utils.ts @@ -50,19 +50,23 @@ Array.isDiff = function isDiff(a, b) { }; Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') { - let h = this.getHours(); - if (h < 10) h = `0${h}`; - let m = this.getMinutes(); + let m = this.getMonth() + 1; if (m < 10) m = `0${m}`; - let s = this.getSeconds(); - if (s < 10) s = `0${s}`; + let d = this.getDate(); + if (d < 10) d = `0${d}`; + let H = this.getHours(); + if (H < 10) H = `0${H}`; + let M = this.getMinutes(); + if (M < 10) M = `0${M}`; + let S = this.getSeconds(); + if (S < 10) S = `0${S}`; return fmt .replace('%Y', this.getFullYear()) - .replace('%m', this.getMonth() + 1) - .replace('%d', this.getDate()) - .replace('%H', h) - .replace('%M', m) - .replace('%S', s); + .replace('%m', m) + .replace('%d', d) + .replace('%H', H) + .replace('%M', M) + .replace('%S', S); }; Math.sum = function sum(...args) { diff --git a/package.json b/package.json index 2d36f0e10..23b081625 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hydrooj", - "version": "2.7.0", - "main": "hydro/loader.js", + "version": "2.7.2", + "main": "hydro/loader.ts", "repository": "https://github.com/hydro-dev/Hydro.git", "author": "masnn", "license": "GPL-3.0-only",