fix: require rendered evidence for library access restrictions
This commit is contained in:
1 parent
71bf5fd926
commit
6d5b920287
4 files changed
+15
-4
No files matched your search
@@ -25,7 +25,7 @@ pnpm dev
|
||||
|
||||
Правила учитывают сообщения читалок Лани, Юрайта и iBooks, изъятие публикации и предложение покупки онлайн-доступа Znanium, покупку именно eBook Springer и гостевое ограничение BOOK.ru. Цена печатной книги и общий запрет скачивания PDF не доказывают закрытое чтение. Результат содержит короткое доказательство, URL без токенов, время, версию правил и профиль `anonymous`.
|
||||
|
||||
Статического HTML достаточно для явных ограничений. Неопределённые страницы известных ЭБС открываются в Chromium/Playwright; проверяется видимый текст, при необходимости адрес читалки, а у BOOK.ru — подсказка к гостевому просмотру. Для остальных сайтов неопределённость сохраняется. Проверка не выполняет вход, покупку и обход CAPTCHA.
|
||||
Страницы известных ЭБС открываются в Chromium/Playwright для подтверждения видимых ограничений: скрытые CSS-шаблоны не считаются доказательством; проверяется видимый текст, при необходимости адрес читалки, а у BOOK.ru — подсказка к гостевому просмотру. Для остальных сайтов неопределённость сохраняется. Проверка не выполняет вход, покупку и обход CAPTCHA.
|
||||
|
||||
Docker включает Chromium с включённой песочницей, UID1000, read-only rootfs, no-new-privileges и cap-drop ALL. Профиль seccomp разрешает необходимые для песочницы user namespaces, остальные ограничения остаются; источник и лицензия указаны в deploy/README.md. Браузер получает минимальное окружение без DATABASE_URL. Все HTTP-запросы перехватываются, DNS проверяется и закрепляется в Node-сокете. Прямой выход блокируется нерабочим proxy, WebSocket/SW отключены, непроходящий через proxy WebRTC UDP запрещён. Методы записи, внешние домены, изображения/медиа/шрифты блокируются. Лимиты:100 запросов,32MB на задачу,8MB на ресурс,10 одновременных запросов; browser deadline30s и общий link deadline60s. Ошибка/лимит оставляет доступ неподтверждённым.
|
||||
|
||||
|
||||
+5
-1
@@ -59,7 +59,11 @@ export async function checkBrowser(url:string,parentSignal?:AbortSignal):Promise
|
||||
while((node=walker.nextNode())&&size<100000){const el=node.parentElement;if(!el||el.closest('header,footer,nav,script,style,[role="navigation"],[role="banner"],[role="contentinfo"]'))continue;const style=getComputedStyle(el);if(style.visibility==='hidden'||style.display==='none'||!el.getClientRects().length)continue;const text=node.textContent??'';parts.push(text);size+=text.length;}
|
||||
return parts.join(' ');
|
||||
});
|
||||
const offers=await page.locator('.book-offers__item,.app-article-access').evaluateAll(nodes=>nodes.filter(node=>{const style=getComputedStyle(node);return style.display!=='none'&&style.visibility!=='hidden'&&node.getClientRects().length>0;}).map(node=>node.outerHTML).join(''));
|
||||
const offers=await page.locator('.book-offers__item,.app-article-access').evaluateAll(nodes=>nodes.filter(node=>{const style=getComputedStyle(node);return style.display!=='none'&&style.visibility!=='hidden'&&node.getClientRects().length>0;}).map(node=>{
|
||||
const clone=node.cloneNode(true) as Element,original=[...node.querySelectorAll('*')],copied=[...clone.querySelectorAll('*')];
|
||||
original.forEach((el,i)=>{const style=getComputedStyle(el);if(style.display==='none'||style.visibility==='hidden'||!el.getClientRects().length)copied[i].remove();});
|
||||
return clone.outerHTML;
|
||||
}).join(''));
|
||||
return {text,offers,url:page.url(),title:await page.title()};
|
||||
};
|
||||
const settle=async()=>{
|
||||
|
||||
+5
-2
@@ -29,9 +29,12 @@ export {safeUrl,publicIp,resolvePublicHost,retrieve} from './http.ts';
|
||||
export {classifyResponse as classify} from './access.ts';
|
||||
export async function checkLink(url:string):Promise<Check>{
|
||||
try{
|
||||
const signal=AbortSignal.timeout(60000),page=await retrieve(url,false,signal),check=classifyResponse(url,page);
|
||||
const signal=AbortSignal.timeout(60000);
|
||||
// Known libraries contain hidden auth/paywall templates; only rendered evidence can decide their access.
|
||||
if(provider(url))return await (await import('./browser.ts')).checkBrowser(url,signal);
|
||||
const page=await retrieve(url,false,signal),check=classifyResponse(url,page);
|
||||
if(page.code===403&&check.status==='blocked')await limitHost(new URL(page.url).hostname,403);
|
||||
if(check.status==='unknown'&&provider(url))return await (await import('./browser.ts')).checkBrowser(url,signal);
|
||||
if(check.evidence&&['auth','restricted'].includes(check.status))return {...check,status:'unknown',reason:'В HTML есть признаки ограничения, но доступ к полному тексту не подтверждён отрисованной страницей.',evidence:undefined};
|
||||
return check;
|
||||
}catch(e){const error=e as Error;return result(url,error instanceof HostCooldown?'blocked':error.name==='AbortError'?'timeout':'network',error instanceof HostCooldown?error.message:'Не удалось загрузить материал. Доступ к полному тексту не подтверждён.');}
|
||||
}
|
||||
@@ -38,3 +38,7 @@ test('sandboxed Chromium blocks private HTTP, redirects, frames, popups, workers
|
||||
assert.ok(proxied.every(url=>url.startsWith('https://e.lanbook.com/')));assert.ok(proxied.includes('https://e.lanbook.com/redirect'));
|
||||
}finally{await new Promise<void>(resolve=>server.close(()=>resolve()));udp.close();}
|
||||
});
|
||||
test('a CSS-hidden login template does not make a known library book unavailable',async()=>{
|
||||
fixture='<html><head><style>.reader-login-template {display:none}</style></head><body><h1>Book</h1><div class="reader-login-template">Для доступа к чтению книги необходимо авторизоваться.</div><p>Only the catalogue card has loaded.</p></body></html>';
|
||||
const check=await checkBrowser('https://e.lanbook.com/book/2');assert.equal(check.status,'unknown',check.reason);
|
||||
});
|
||||
Reference in new issue
Block a user