MediaWiki:Gadget-common-special-search.js: различия между версиями
Перейти к навигации
Перейти к поиску
м 1 версия импортирована: поисковики в интерфейс поиска при отсутствии статьи в Абсе |
повышение безопасности |
||
| (не показана 1 промежуточная версия этого же участника) | |||
| Строка 1: | Строка 1: | ||
$(function() { | |||
$( function () { | if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') return; | ||
var searchInput = document.querySelector('#searchText input, #searchInput'); | |||
if (!searchInput) return; | |||
// Безопасное получение значения поиска с ограничением длины | |||
var searchQuery = (searchInput.value || '').trim(); | |||
if (searchQuery.length > 500) { | |||
searchQuery = searchQuery.slice(0, 500); | |||
} | |||
// Валидация и санитизация через encodeURIComponent | |||
var safeQuery = encodeURIComponent(searchQuery); | |||
// Замораживаем объект с движками для защиты от перезаписи | |||
var engines = Object.freeze({ | |||
'Bing': 'https://www.bing.com/search?q=%s+site:absurdopedia.wiki', | |||
'DuckDuckGo': 'https://duckduckgo.com/?q=%s+site:absurdopedia.wiki', | |||
'Google': 'https://google.com/search?q=%s+site:absurdopedia.wiki&hl=ru', | |||
'Yandex': 'https://yandex.ru/yandsearch?text=%s&site=absurdopedia.wiki' | |||
}); | |||
// Функция безопасной валидации URL | |||
function isValidUrl(url) { | |||
try { | |||
var parsedUrl = new URL(url); | |||
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'; | |||
} catch(e) { | |||
return false; | |||
} | |||
} | |||
var $enginesContainer = $('<p>').attr('id', 'searchEngines'); | |||
var $textNode = $('<span>').text('Искать в ('); | |||
$enginesContainer.append($textNode); | |||
// Безопасное получение ключей с проверкой собственных свойств | |||
var engineNames = Object.keys(engines).filter(function(name) { | |||
return engines.hasOwnProperty(name) && typeof engines[name] === 'string'; | |||
}); | |||
engineNames.forEach(function(name, index) { | |||
var urlTemplate = engines[name]; | |||
// Проверяем, что URL начинается с безопасного протокола | |||
if (!/^https?:\/\//i.test(urlTemplate)) { | |||
return; | |||
} | |||
var url = urlTemplate.replace('%s', safeQuery); | |||
// Дополнительная проверка финального URL | |||
if (!isValidUrl(url)) { | |||
return; | |||
} | |||
var $link = $('<a>') | |||
.attr('href', url) | |||
.attr('target', '_blank') | |||
.attr('rel', 'noopener noreferrer') | |||
.text(name); | |||
$enginesContainer.append($link); | |||
if (index < engineNames.length - 1) { | |||
$enginesContainer.append(' | '); | |||
} | |||
}); | |||
$enginesContainer.append(')'); | |||
$('.searchresults > .mw-search-visualclear').last().after($enginesContainer); | |||
var urlParams = new URLSearchParams(location.search); | |||
var prefix = urlParams.get('prefix'); | |||
if (prefix && typeof prefix === 'string' && prefix.includes('/')) { | |||
var basePage = prefix.split('/')[0]; | |||
// Строгая валидация basePage: только безопасные символы | |||
if (basePage && /^[a-zA-Z0-9\s\u0400-\u04FF\-_]+$/.test(basePage)) { | |||
var $searchAllLink = $('#mw-content-subtitle a'); | |||
if ($searchAllLink.length) { | |||
var $searchPrefix = $searchAllLink.clone(); | |||
// Используем text() вместо HTML-конкатенации | |||
var linkText = 'Искать на подстраницах «' + basePage + '»'; | |||
$searchPrefix | |||
.text(linkText) | |||
.attr('href', $searchAllLink.attr('href') + '&prefix=' + encodeURIComponent(basePage)); | |||
$searchAllLink.after( | |||
$('<span>').text(' | '), | |||
$searchPrefix | |||
); | |||
} | |||
} | |||
} | |||
mw.loader.using(['mediawiki.util', 'oojs-ui-core', 'oojs-ui-widgets'], function() { | |||
var $keywordsWrapper = $('#keywords-popup-pseudolink-wrapper'); | |||
if (!$keywordsWrapper.length) return; | |||
$('#mw-indicator-mw-helplink a').text(function(i, text) { | |||
return text.replace('Справка', 'Полная справка'); | |||
}); | |||
mw.util.addCSS('.mw-indicators { display: flex; align-items: center; }'); | |||
var keywordsButton = new OO.ui.PopupButtonWidget({ | |||
label: 'Ключевые слова', | |||
} ); | indicator: 'down', | ||
flags: ['progressive'], | |||
icon: 'keywords', | |||
framed: false, | |||
popup: { | |||
$content: $('<div>').append($('#keywords-popup').children().clone()), | |||
padded: true, | |||
align: 'down', | |||
width: 420 | |||
} | |||
}); | |||
keywordsButton.$element.appendTo('#mw-indicator-0-keywords-popup .mw-parser-output'); | |||
var $searchBox = $('#searchText input'); | |||
$('.keywords-popup-keyword').each(function() { | |||
var $keyword = $(this); | |||
var rawKeyword = $keyword.data('keyword'); | |||
// Проверяем, что данные существуют и являются строкой | |||
if (typeof rawKeyword !== 'string') return; | |||
// Усиленная санитизация ключевого слова | |||
var keywordText = rawKeyword | |||
.replace(/[<>]/g, '') // Удаляем угловые скобки | |||
.replace(/['"]/g, '') // Удаляем кавычки | |||
.replace(/javascript:/gi, '') // Защита от псевдо-протокола | |||
.replace(/data:/gi, '') // Защита от data: URI | |||
.replace(/vbscript:/gi, '') // Защита от VBScript | |||
.replace(/on\w+=/gi, '') // Удаляем обработчики событий | |||
.slice(0, 500); // Ограничиваем длину | |||
// Дополнительная проверка: не должно быть опасных паттернов | |||
var hasDangerousPattern = /[<>'"]|javascript:|data:|vbscript:|on\w+=/i.test(keywordText); | |||
if (hasDangerousPattern) return; | |||
$keyword | |||
.attr('role', 'button') | |||
.attr('tabindex', '0') | |||
.attr('title', 'Вставить ключевое слово в поле поиска') | |||
.css('cursor', 'pointer') | |||
.on('click keydown', function(e) { | |||
// Исправлено: добавлена поддержка Spacebar для старых браузеров | |||
if (e.type === 'click' || e.key === ' ' || e.key === 'Spacebar' || e.key === 'Enter') { | |||
e.preventDefault(); | |||
var currentValue = $searchBox.val() || ''; | |||
var newValue = currentValue + keywordText; | |||
// Проверяем длину результата | |||
if (newValue.length < 10000) { | |||
$searchBox.val(newValue).trigger('focus'); | |||
} | |||
} | |||
}); | |||
}); | |||
}); | |||
}); | |||
Текущая версия от 06:36, 30 марта 2026
$(function() {
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') return;
var searchInput = document.querySelector('#searchText input, #searchInput');
if (!searchInput) return;
// Безопасное получение значения поиска с ограничением длины
var searchQuery = (searchInput.value || '').trim();
if (searchQuery.length > 500) {
searchQuery = searchQuery.slice(0, 500);
}
// Валидация и санитизация через encodeURIComponent
var safeQuery = encodeURIComponent(searchQuery);
// Замораживаем объект с движками для защиты от перезаписи
var engines = Object.freeze({
'Bing': 'https://www.bing.com/search?q=%s+site:absurdopedia.wiki',
'DuckDuckGo': 'https://duckduckgo.com/?q=%s+site:absurdopedia.wiki',
'Google': 'https://google.com/search?q=%s+site:absurdopedia.wiki&hl=ru',
'Yandex': 'https://yandex.ru/yandsearch?text=%s&site=absurdopedia.wiki'
});
// Функция безопасной валидации URL
function isValidUrl(url) {
try {
var parsedUrl = new URL(url);
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
} catch(e) {
return false;
}
}
var $enginesContainer = $('<p>').attr('id', 'searchEngines');
var $textNode = $('<span>').text('Искать в (');
$enginesContainer.append($textNode);
// Безопасное получение ключей с проверкой собственных свойств
var engineNames = Object.keys(engines).filter(function(name) {
return engines.hasOwnProperty(name) && typeof engines[name] === 'string';
});
engineNames.forEach(function(name, index) {
var urlTemplate = engines[name];
// Проверяем, что URL начинается с безопасного протокола
if (!/^https?:\/\//i.test(urlTemplate)) {
return;
}
var url = urlTemplate.replace('%s', safeQuery);
// Дополнительная проверка финального URL
if (!isValidUrl(url)) {
return;
}
var $link = $('<a>')
.attr('href', url)
.attr('target', '_blank')
.attr('rel', 'noopener noreferrer')
.text(name);
$enginesContainer.append($link);
if (index < engineNames.length - 1) {
$enginesContainer.append(' | ');
}
});
$enginesContainer.append(')');
$('.searchresults > .mw-search-visualclear').last().after($enginesContainer);
var urlParams = new URLSearchParams(location.search);
var prefix = urlParams.get('prefix');
if (prefix && typeof prefix === 'string' && prefix.includes('/')) {
var basePage = prefix.split('/')[0];
// Строгая валидация basePage: только безопасные символы
if (basePage && /^[a-zA-Z0-9\s\u0400-\u04FF\-_]+$/.test(basePage)) {
var $searchAllLink = $('#mw-content-subtitle a');
if ($searchAllLink.length) {
var $searchPrefix = $searchAllLink.clone();
// Используем text() вместо HTML-конкатенации
var linkText = 'Искать на подстраницах «' + basePage + '»';
$searchPrefix
.text(linkText)
.attr('href', $searchAllLink.attr('href') + '&prefix=' + encodeURIComponent(basePage));
$searchAllLink.after(
$('<span>').text(' | '),
$searchPrefix
);
}
}
}
mw.loader.using(['mediawiki.util', 'oojs-ui-core', 'oojs-ui-widgets'], function() {
var $keywordsWrapper = $('#keywords-popup-pseudolink-wrapper');
if (!$keywordsWrapper.length) return;
$('#mw-indicator-mw-helplink a').text(function(i, text) {
return text.replace('Справка', 'Полная справка');
});
mw.util.addCSS('.mw-indicators { display: flex; align-items: center; }');
var keywordsButton = new OO.ui.PopupButtonWidget({
label: 'Ключевые слова',
indicator: 'down',
flags: ['progressive'],
icon: 'keywords',
framed: false,
popup: {
$content: $('<div>').append($('#keywords-popup').children().clone()),
padded: true,
align: 'down',
width: 420
}
});
keywordsButton.$element.appendTo('#mw-indicator-0-keywords-popup .mw-parser-output');
var $searchBox = $('#searchText input');
$('.keywords-popup-keyword').each(function() {
var $keyword = $(this);
var rawKeyword = $keyword.data('keyword');
// Проверяем, что данные существуют и являются строкой
if (typeof rawKeyword !== 'string') return;
// Усиленная санитизация ключевого слова
var keywordText = rawKeyword
.replace(/[<>]/g, '') // Удаляем угловые скобки
.replace(/['"]/g, '') // Удаляем кавычки
.replace(/javascript:/gi, '') // Защита от псевдо-протокола
.replace(/data:/gi, '') // Защита от data: URI
.replace(/vbscript:/gi, '') // Защита от VBScript
.replace(/on\w+=/gi, '') // Удаляем обработчики событий
.slice(0, 500); // Ограничиваем длину
// Дополнительная проверка: не должно быть опасных паттернов
var hasDangerousPattern = /[<>'"]|javascript:|data:|vbscript:|on\w+=/i.test(keywordText);
if (hasDangerousPattern) return;
$keyword
.attr('role', 'button')
.attr('tabindex', '0')
.attr('title', 'Вставить ключевое слово в поле поиска')
.css('cursor', 'pointer')
.on('click keydown', function(e) {
// Исправлено: добавлена поддержка Spacebar для старых браузеров
if (e.type === 'click' || e.key === ' ' || e.key === 'Spacebar' || e.key === 'Enter') {
e.preventDefault();
var currentValue = $searchBox.val() || '';
var newValue = currentValue + keywordText;
// Проверяем длину результата
if (newValue.length < 10000) {
$searchBox.val(newValue).trigger('focus');
}
}
});
});
});
});