vjudge: codeforces: add getCsrfTokenOnDocument (#855)

Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
pandaandundefined authored and GitHub committed 2024-08-09 02:12:25 +08:00
1 parent ef4862d785
commit 5eea7489c9
1 file changed
+49 -40
+49 -40
View File
@@ -82,6 +82,16 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr
return _tta;
}
getCsrfTokenOnDocument(document: Document) {
const meta = document.querySelector("meta[name='X-Csrf-Token']")?.getAttribute('content');
if (meta?.length === 32) return meta;
const span = document.querySelector('span.csrf-token')?.getAttribute('data-csrf');
if (span?.length === 32) return span;
const input = document.querySelector('input[name="csrf_token"]')?.getAttribute('value');
if (input?.length === 32) return input;
return '';
}
async getCsrfToken(url: string) {
const { document, html, headers } = await this.html(url);
if (document.body.children.length < 2 && html.length < 512) {
@@ -89,13 +99,7 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr
}
const ftaa = this.getCookie('70a7c28f3de') || 'n/a';
const bfaa = this.getCookie('raa') || this.getCookie('bfaa') || 'n/a';
return [
(
document.querySelector('meta[name="X-Csrf-Token"]')
|| document.querySelector('input[name="csrf_token"]')
)?.getAttribute('content'),
ftaa, bfaa, headers,
];
return [this.getCsrfTokenOnDocument(document), ftaa, bfaa, headers];
}
get loggedIn() {
@@ -270,11 +274,15 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr
});
}
async readLatestSubmission(contestId = '') {
const { document } = await this.html(contestId ? `/gym/${contestId}/my` : '/problemset/status?my=on');
this.csrf = document.querySelector('meta[name="X-Csrf-Token"]').getAttribute('content');
const submission = document.querySelector('[data-submission-id]').getAttribute('data-submission-id');
return submission;
async readLatestSubmission(contestId = '', allowEmpty = false) {
for (let i = 1; i <= 3; i++) {
await sleep(1000);
const { document } = await this.html(contestId ? `/gym/${contestId}/my` : '/problemset/status?my=on');
this.csrf = this.getCsrfTokenOnDocument(document);
const id = document.querySelector('[data-submission-id]')?.getAttribute('data-submission-id');
if (id || allowEmpty) return id;
}
return null;
}
async submitProblem(id: string, lang: string, code: string, info, next, end) {
@@ -283,37 +291,38 @@ export default class CodeforcesProvider extends BasicFetcher implements IBasicPr
const endpoint = type === 'GYM'
? `/gym/${contestId}/submit`
: `/problemset/submit/${contestId}/${problemId}`;
const latestSubmission = await this.readLatestSubmission(type === 'GYM' ? contestId : '');
const [csrf, ftaa, bfaa] = await this.getCsrfToken(endpoint);
// TODO check submit time to ensure submission
const { text: submit, redirects } = await this.post(`${endpoint}?csrf_token=${csrf}`).send({
csrf_token: csrf,
action: 'submitSolutionFormSubmitted',
programTypeId,
source: code,
tabSize: 4,
sourceFile: '',
ftaa,
bfaa,
_tta: this.tta(this.getCookie('39ce7')),
contestId,
submittedProblemIndex: problemId,
});
const { window: { document: statusDocument } } = new JSDOM(submit);
const message = Array.from(statusDocument.querySelectorAll('.error'))
.map((i) => i.textContent).join('').replace(/&nbsp;/g, ' ').trim();
if (message) {
end({ status: STATUS.STATUS_SYSTEM_ERROR, message });
return null;
}
const submission = await this.readLatestSubmission(type === 'GYM' ? contestId : '');
if (submission === latestSubmission || redirects.length === 0 || !redirects.toString().includes('my')) {
// Submit failed so the request is not redirected
try {
const latestSubmission = await this.readLatestSubmission(type === 'GYM' ? contestId : '', true);
const [csrf, ftaa, bfaa] = await this.getCsrfToken(endpoint);
// TODO check submit time to ensure submission
const { text: submit, redirects } = await this.post(`${endpoint}?csrf_token=${csrf}`).send({
csrf_token: csrf,
action: 'submitSolutionFormSubmitted',
programTypeId,
source: code,
tabSize: 4,
sourceFile: '',
ftaa,
bfaa,
_tta: this.tta(this.getCookie('39ce7')),
contestId,
submittedProblemIndex: problemId,
});
const { window: { document: statusDocument } } = new JSDOM(submit);
const message = Array.from(statusDocument.querySelectorAll('.error'))
.map((i) => i.textContent).join('').replace(/&nbsp;/g, ' ').trim();
if (message) throw new Error(message);
const submission = await this.readLatestSubmission(type === 'GYM' ? contestId : '');
if (!submission) throw new Error('Failed to get submission id.');
if (submission === latestSubmission) throw new Error('Submission page is not updated.');
if (redirects.length === 0 || !redirects.toString().includes('my')) throw new Error('No redirect to submission page.');
return type !== 'GYM' ? submission : `${contestId}#${submission}`;
} catch (e) {
next({ message: e.message });
// eslint-disable-next-line max-len
end({ status: STATUS.STATUS_SYSTEM_ERROR, message: 'Submit failed. Check service status or use better network to avoid rejection by server protection.' });
end({ status: STATUS.STATUS_SYSTEM_ERROR, message: 'Submit to remote failed. Check service status or use better network to avoid rejection by server protection.' });
return null;
}
return type !== 'GYM' ? submission : `${contestId}#${submission}`;
}
async waitForSubmission(id: string, next, end) {