Handle TUNN CONN FAILED, sanitize optioins page url params, fix #50

This commit is contained in:
ilyaigpetrov 2019-05-11 06:20:53 -05:00
parent 1eb005f085
commit 719d6eac9c
8 changed files with 2323 additions and 993 deletions

View File

@ -17,12 +17,15 @@ export default function getApp(theState) {
constructor(props) {
super(props);
const hash = window.location.hash.substr(1);
const hashParams = new URLSearchParams(hash);
const sanitizedUrl = theState.flags.ifOpenedUnsafely ? { hash: '', search: '' } : window.location;
const hashParams = new URLSearchParams(sanitizedUrl.hash.substr(1));
const searchParams = new URLSearchParams(sanitizedUrl.search.substr(1));
this.state = {
status: 'Загрузка...',
ifInputsDisabled: false,
hashParams: hashParams,
hashParams,
searchParams,
};
this.setStatusTo = this.setStatusTo.bind(this);
@ -62,9 +65,9 @@ export default function getApp(theState) {
const uiComEtag = 'ui-last-comments-etag';
const uiLastNewsArr = 'ui-last-news-arr';
const statusFromHash = this.state.hashParams.get('status');
if (statusFromHash) {
return this.setStatusTo(statusFromHash);
const statusFromUrl = this.state.searchParams.get('status');
if (statusFromUrl) {
return this.setStatusTo(statusFromUrl);
}
const comDate = localStorage[uiComDate];

View File

@ -17,24 +17,22 @@ export default function getFooter() {
`;
return function (props) {
return (props) => (
<div class="horPadded">
<section class={scopedCss.statusRow}>
<div class={scopedCss.status} style="will-change: contents">
{typeof(props.status) === 'string' ? <div dangerouslySetInnerHTML={{ __html: props.status }}></div> : props.status}
</div>
</section>
return (
<div class="horPadded">
<section class={scopedCss.statusRow}>
<div clss={scopedCss.status} style="will-change: contents">{props.status}</div>
</section>
<footer class={scopedCss.controlRow + ' horFlex nowrap'}>
<input type="button" value={chrome.i18n.getMessage('Finish')} disabled={props.ifInputsDisabled} onClick={() => window.close()} />
<a href="https://rebrand.ly/ac-donate">{chrome.i18n.getMessage('Donate')}</a>
<a data-in-bg="false" href="../troubleshoot/index.html">
{chrome.i18n.getMessage('ProblemsQ')}
</a>
</footer>
</div>
);
};
<footer class={scopedCss.controlRow + ' horFlex nowrap'}>
<input type="button" value={chrome.i18n.getMessage('Finish')} disabled={props.ifInputsDisabled} onClick={() => window.close()} />
<a href="https://rebrand.ly/ac-donate">{chrome.i18n.getMessage('Donate')}</a>
<a data-in-bg="false" href="../troubleshoot/index.html">
{chrome.i18n.getMessage('ProblemsQ')}
</a>
</footer>
</div>
);
};

View File

@ -9,7 +9,13 @@ import getApp from './components/App';
chrome.runtime.getBackgroundPage( (bgWindow) =>
bgWindow.apis.errorHandlers.installListenersOn(
window, 'PUP', async() => {
/*
`Extension context invalidated` error is thrown if `window.closed` is true and call to
`window.chrome.i18n` or other `window.chrome` api happens. Use bgWindow.chrome instead.
Use winChrome for tab-related calls like winChrome.tabs.getCurrent.
*/
window.winChrome = window.chrome;
window.chrome = bgWindow.chrome;
let theState;
{
const apis = bgWindow.apis;
@ -29,15 +35,23 @@ chrome.runtime.getBackgroundPage( (bgWindow) =>
// IF INSIDE OPTIONS TAB
const currentTab = await new Promise(
(resolve) => chrome.tabs.query(
(resolve) => winChrome.tabs.query(
{active: true, currentWindow: true},
([tab]) => resolve(tab)
([tab]) => resolve(tab),
)
);
theState.flags.ifInsideOptionsPage = !currentTab || currentTab.url.startsWith('chrome://extensions/?options=') || currentTab.url.startsWith('about:addons');
theState.currentTab = currentTab;
// If opened not via popup and not via options modal.
// E.g., if opened via copy-pasting an URL into the address bar from somewhere.
// If browser is not Chrome (Opera, e.g.) then options page may be opened in a separate tab
// and then you will get a false positive.
theState.flags.ifOpenedUnsafely = Boolean(await new Promise(
(resolve) => winChrome.tabs.getCurrent(resolve),
));
// STATE DEFINED, COMPOSE.
appendGlobalCss(document, theState);

View File

@ -2,7 +2,88 @@
{
chrome.webNavigation.onErrorOccurred.addListener((details) => {
const proxySideErrors = [
'net::ERR_TUNNEL_CONNECTION_FAILED',
];
const urlToA = (url) => new URL(url).host.link(
encodeURIComponent(url),
);
const isProxyErrorHandledAsync = async (details) => {
if (!proxySideErrors.includes(details.error) || details.type === 'main_frame') {
// Main frame websocket errors are followed by webnavigation errors
// which chrome-internals code resets the state of the popup.
return;
}
let fromPageHref = '';
let fromPageHtml = '';
let youMayReportHtml = '';
const initiator = details.initiator !== 'null' && details.initiator;
try {
if (initiator) {
fromPageHref = new URL(initiator).href; // Sanitize: only urls, not other stuff.
fromPageHtml = ` со страницы ${urlToA(fromPageHref)}`;
}
youMayReportHtml = ` Вы можете <b>${'сообщить об ошибке'.link(
encodeURIComponent(
'/pages/report-proxy-error/index.html?' +
new URLSearchParams({
fromPageHref,
requestFailedTo: new URL(details.url).href,
}),
),
)}</b> администратору прокси.`;
} catch(e) {
/* Suppress for malformed urls. */
console.log('Error handling malformed URLs:', details);
}
const tabId = details.tabId;
const popupPrefix = chrome.runtime.getURL(`/pages/options/index.html?status=<span style="color: red">🔥 Прокси-сервер отказался обслуживать запрос к `);
const oldPopup = await new Promise((resolve) => chrome.browserAction.getPopup({ tabId }, resolve));
if (decodeURIComponent(oldPopup).startsWith(popupPrefix)) {
return true;
}
const popup = `${popupPrefix}${urlToA(details.url)}${fromPageHtml}</span>. Это могло быть намеренно или по ошибке.${youMayReportHtml}#tab=exceptions`;
chrome.browserAction.setPopup({
tabId,
popup,
});
chrome.browserAction.setBadgeBackgroundColor({
tabId,
color: 'red',
});
chrome.browserAction.setBadgeText({
tabId,
text: '❗',
});
let limit = 5;
let ifOnTurn = true;
let ifError = false;
const flip = () => {
if (!ifOnTurn && !--limit || ifError) {
clearInterval(timer);
return;
}
chrome.browserAction.setBadgeText({
tabId,
text: ifOnTurn ? '❗' : '',
}, () => {
ifError = chrome.runtime.lastError;
});
ifOnTurn = !ifOnTurn;
};
flip();
const timer = setInterval(flip, 500);
return true;
};
chrome.webNavigation.onErrorOccurred.addListener(async (details) => {
const tabId = details.tabId;
if ( !(details.frameId === 0 && tabId >= 0) ||
@ -12,13 +93,16 @@
].includes(details.error) ) {
return;
}
if (await isProxyErrorHandledAsync(details)) {
return;
}
chrome.browserAction.setPopup({
tabId,
popup: './pages/options/index.html#tab=exceptions&status=Правый клик по иконке — меню инструментов!',
popup: './pages/options/index.html?status=Правый клик по иконке — меню инструментов!#tab=exceptions',
});
window.chrome.browserAction.setBadgeBackgroundColor({
chrome.browserAction.setBadgeBackgroundColor({
tabId,
color: '#4285f4',
});
@ -29,4 +113,9 @@
});
chrome.webRequest.onErrorOccurred.addListener(
isProxyErrorHandledAsync,
{urls: ['<all_urls>']},
);
}

View File

@ -41,7 +41,7 @@
const setRedBadge = (opts) => {
window.chrome.browserAction.setBadgeBackgroundColor({
chrome.browserAction.setBadgeBackgroundColor({
color: '#db4b2f',
});
chrome.browserAction.setBadgeText(opts);

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Сообщить об ошибке прокси-сервера</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root {
font-size: 1.3em;
}
</style>
</head>
<body>
<h1>Сообщить об ошибке прокси-сервера</h1>
Перешлите администратору вашего прокси следующее:
<fieldset>
<pre id="errorInfo"></pre>
</fieldset>
Вот известные нам электронные адреса для популярных прокси-серверов (кликните по email для открытия шаблона письма):
<ol>
<li>
Только если вы используете <a
href="https://antizapret.prostovpn.org">Антизапрет</a>:
<a
href="mailto:antizapret@prostovpn.org">antizapret@prostovpn.org</a>.
</li>
</ol>
<script src="./index.js"></script>
<script src="../lib/keep-links-clickable.js"></script>
</body>
</html>

View File

@ -0,0 +1,39 @@
'use strict';
chrome.runtime.getBackgroundPage( (bgWindow) =>
bgWindow.apis.errorHandlers.installListenersOn(
window, 'PRERR', () => {
const params = new URLSearchParams(location.search.substr(1));
const requestFailedTo = params.get('requestFailedTo');
const fromPageHref = params.get('fromPageHref') || requestFailedTo;
const acr = bgWindow.apis.antiCensorRu;
const pacKey = acr.getCurrentPacProviderKey();
const pacModTime = acr.getLastModifiedForKey(pacKey);
const errorReport = `
Your proxy blocked the following request:
* Request was from page: ${fromPageHref}
* To address: ${requestFailedTo}
* Used PAC-script: ${pacKey}
* Its Last-Modified HTTP-header: ${pacModTime}
I think it's a mistake! Could you, please, take action to fix it.
Thank you!
Ваш прокси-сервер заблокировал следующий запрос:
* Запрос был со страницы: ${fromPageHref}
* Адрес запроса: ${requestFailedTo}
* Мой PAC-скрипт: ${pacKey}
* Его HTTP-заголовок Last-Modified: ${pacModTime}
Я думаю, это произошло по ошибке! Пожалуйста, примите действия для её исправления.
Спасибо!
`.trim();
errorInfo.innerText = errorReport;
document.querySelectorAll('a[href^="mailto:"]').forEach((a) => {
a.href = `${a.href}?subject=${encodeURIComponent(new URL(requestFailedTo).hostname)} TUNNEL_CONNECTION_FAILED&body=${encodeURIComponent(errorReport)}`;
});
},
),
);