import 'dart:convert'; import 'school_navigation_policy.dart'; /// Only serialize recognized table fragments, never a login document. const schoolPageProbeScript = r'''(() => { const trusted = value => { try { const u = new URL(value); return u.hostname === 'yjsjy.uestc.edu.cn' && !u.username && !u.password && ((u.protocol === 'https:' && (!u.port || u.port === '443')) || (u.protocol === 'http:' && (!u.port || u.port === '80'))) && /^\/pyxx(?:\/|$)/.test(u.pathname); } catch (_) { return false; } }; const topUrl = location.href; const result = {topUrl, authenticated: false, pages: []}; if (!trusted(topUrl) || document.querySelector('input[type="password"]')) { return JSON.stringify(result); } const topOrigin = new URL(topUrl).origin; const home = /^\/pyxx\/home\/(?:index)?\/?$/.test(location.pathname); const chrome = document.querySelector('#navbar') && document.querySelector('#main-container'); result.authenticated = typeof window.logout === 'function' && Boolean(chrome || home); const seen = new Set(); const visit = (doc, depth) => { if (!doc || depth > 4 || seen.size >= 16 || seen.has(doc)) return; seen.add(doc); let url; try { url = doc.location.href; } catch (_) { return; } if (!trusted(url) || new URL(url).origin !== topOrigin || doc.querySelector('input[type="password"]')) return; for (const [kind, tableId, termId] of [ ['timetable', '#tbl', '#drpxq'], ['exams', '#sample-table-1', '#xqbh'] ]) { const table = doc.querySelector(tableId), term = doc.querySelector(termId); if (!table || !term || !term.options || term.selectedIndex < 0) continue; const selected = term.cloneNode(true); Array.from(selected.options).forEach((option, index) => { if (index === term.selectedIndex) option.setAttribute('selected', 'selected'); else option.removeAttribute('selected'); }); const html = selected.outerHTML + table.outerHTML; if (html.length <= 2 * 1024 * 1024) result.pages.push({kind, url, html}); } for (const frame of doc.querySelectorAll('iframe,frame')) { try { visit(frame.contentDocument, depth + 1); } catch (_) {} } }; visit(document, 0); return JSON.stringify(result); })()'''; class SchoolTableFragment { const SchoolTableFragment(this.kind, this.url, this.html); final String kind; final Uri url; final String html; } class SchoolPageEvidence { const SchoolPageEvidence(this.authenticated, this.pages); final bool authenticated; final List pages; static SchoolPageEvidence decode(Object raw, Uri current) { dynamic value = raw; for (var i = 0; i < 2 && value is String; i++) { value = jsonDecode(value); } if (value is! Map || value['topUrl'] != current.toString() || !SchoolNavigationPolicy.isGraduatePage(current)) { throw const FormatException('Untrusted school page evidence'); } final pages = []; final candidates = value['pages']; if (candidates is List && candidates.length <= 32) { for (final page in candidates) { if (page is! Map || page['url'] is! String || page['html'] is! String) { continue; } final uri = Uri.tryParse(page['url'] as String); final content = page['html'] as String; if (uri == null || !SchoolNavigationPolicy.isGraduatePage(uri) || uri.origin != current.origin || utf8.encode(content).length > 2 * 1024 * 1024 || !const ['timetable', 'exams'].contains(page['kind'])) { continue; } pages.add(SchoolTableFragment(page['kind'] as String, uri, content)); } } return SchoolPageEvidence(value['authenticated'] == true, pages); } }