MediaWiki:Gadget-common-special-search.js: различия между версиями

Нет описания правки
повышение безопасности
 
Строка 1: Строка 1:
$(function() {
$(function() {
     if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') return;
     if (mw.config.get('wgCanonicalSpecialPageName') !== 'Search') return;
 
   
     var searchInput = document.querySelector('#searchText input, #searchInput');
     var searchInput = document.querySelector('#searchText input, #searchInput');
     if (!searchInput) return;
     if (!searchInput) return;
 
   
     var engines = {
    // Безопасное получение значения поиска с ограничением длины
    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',
         'Bing': 'https://www.bing.com/search?q=%s+site:absurdopedia.wiki',
         'DuckDuckGo': 'https://duckduckgo.com/?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',
         'Google': 'https://google.com/search?q=%s+site:absurdopedia.wiki&hl=ru',
         'Yandex': 'https://yandex.ru/yandsearch?text=%s&site=absurdopedia.wiki'
         '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 $enginesContainer = $('<p>').attr('id', 'searchEngines');
     var $textNode = $('<span>').text('Искать в (');
     var $textNode = $('<span>').text('Искать в (');
     $enginesContainer.append($textNode);
     $enginesContainer.append($textNode);
      
      
     var engineNames = Object.keys(engines);
    // Безопасное получение ключей с проверкой собственных свойств
     var engineNames = Object.keys(engines).filter(function(name) {
        return engines.hasOwnProperty(name) && typeof engines[name] === 'string';
    });
   
     engineNames.forEach(function(name, index) {
     engineNames.forEach(function(name, index) {
         var url = engines[name].replace('%s', encodeURIComponent(searchInput.value));
         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>')
         var $link = $('<a>')
             .attr('href', url)
             .attr('href', url)
             .attr('target', '_blank')
             .attr('target', '_blank')
          .attr('rel', 'noopener noreferrer')
            .attr('rel', 'noopener noreferrer')
          .text(name);
            .text(name);
 
         $enginesContainer.append($link);
         $enginesContainer.append($link);
         if (index < engineNames.length - 1) {
         if (index < engineNames.length - 1) {
             $enginesContainer.append(' | ');
             $enginesContainer.append(' | ');
         }
         }
     });
     });
 
   
     $enginesContainer.append(')');
     $enginesContainer.append(')');
   
     $('.searchresults > .mw-search-visualclear').last().after($enginesContainer);
     $('.searchresults > .mw-search-visualclear').last().after($enginesContainer);
      
      
     var urlParams = new URLSearchParams(location.search);
     var urlParams = new URLSearchParams(location.search);
     var prefix = urlParams.get('prefix');
     var prefix = urlParams.get('prefix');
 
     if (prefix && typeof prefix === 'string' && prefix.includes('/')) {
     if (prefix && prefix.includes('/')) {
         var basePage = prefix.split('/')[0];
         var basePage = prefix.split('/')[0];
         var $searchAllLink = $('#mw-content-subtitle a');
         // Строгая валидация basePage: только безопасные символы
 
        if (basePage && /^[a-zA-Z0-9\s\u0400-\u04FF\-_]+$/.test(basePage)) {
        if ($searchAllLink.length) {
            var $searchAllLink = $('#mw-content-subtitle a');
            var $searchPrefix = $searchAllLink.clone();
            if ($searchAllLink.length) {
            $searchPrefix
                var $searchPrefix = $searchAllLink.clone();
                 .text('Искать на подстраницах «' + basePage + '»')
                 // Используем text() вместо HTML-конкатенации
                .attr('href', $searchAllLink.attr('href') + '&prefix=' + encodeURIComponent(basePage));
                var linkText = 'Искать на подстраницах «' + basePage + '»';
 
            $searchAllLink.after(
                $('<span>').text(' | '),
                 $searchPrefix
                 $searchPrefix
            );
                    .text(linkText)
                    .attr('href', $searchAllLink.attr('href') + '&prefix=' + encodeURIComponent(basePage));
                $searchAllLink.after(
                    $('<span>').text(' | '),
                    $searchPrefix
                );
            }
         }
         }
     }
     }
Строка 64: Строка 97:
             return text.replace('Справка', 'Полная справка');
             return text.replace('Справка', 'Полная справка');
         });
         });
 
       
         mw.util.addCSS('.mw-indicators { display: flex; align-items: center; }');
         mw.util.addCSS('.mw-indicators { display: flex; align-items: center; }');
 
       
         var keywordsButton = new OO.ui.PopupButtonWidget({
         var keywordsButton = new OO.ui.PopupButtonWidget({
             label: 'Ключевые слова',
             label: 'Ключевые слова',
Строка 80: Строка 113:
             }
             }
         });
         });
 
       
         keywordsButton.$element.appendTo('#mw-indicator-0-keywords-popup .mw-parser-output');
         keywordsButton.$element.appendTo('#mw-indicator-0-keywords-popup .mw-parser-output');
 
       
         var $searchBox = $('#searchText input');
         var $searchBox = $('#searchText input');
         $('.keywords-popup-keyword').each(function() {
         $('.keywords-popup-keyword').each(function() {
        var $keyword = $(this);
            var $keyword = $(this);
        var keywordText = $keyword.data('keyword').replace(/ /g, ' ');
            var rawKeyword = $keyword.data('keyword');
 
           
        $keyword
            // Проверяем, что данные существуют и являются строкой
        .attr('role', 'button')
            if (typeof rawKeyword !== 'string') return;
        .attr('tabindex', '0')
           
        .attr('title', 'Вставить ключевое слово в поле поиска')
            // Усиленная санитизация ключевого слова
        .css('cursor', 'pointer')
            var keywordText = rawKeyword
        .on('click keydown', function(e) {
                .replace(/[<>]/g, '') // Удаляем угловые скобки
          if (e.type === 'click' || e.key === ' ' || e.key === 'Enter') {
                .replace(/['"]/g, '') // Удаляем кавычки
            e.preventDefault();
                .replace(/javascript:/gi, '') // Защита от псевдо-протокола
        $searchBox.val($searchBox.val() + keywordText).trigger('focus');
                .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');
                        }
                    }
                });
        });
     });
     });
});
});