В четверг, 7 мая, около 16 часов (MSK) регистратор заморозил домен «cyclowiki.org» без уведомления владельцев. Сайт недоступен из большинства стран. Правление изучает возможности решения проблемы.

MediaWiki:Gadget-common-special-search.js

Материал из Циклопедии
Перейти к навигации Перейти к поиску

Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.

  • Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
  • Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
  • Internet Explorer / Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
  • Opera: Нажмите Ctrl+F5.
$(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:cyclowiki.org',
        'DuckDuckGo': 'https://duckduckgo.com/?q=%s+site:cyclowiki.org',
        'Google': 'https://google.com/search?q=%s+site:cyclowiki.org&hl=ru',
        'Yandex': 'https://yandex.ru/yandsearch?text=%s&site=cyclowiki.org'
    });
    
    // Функция безопасной валидации 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');
                        }
                    }
                });
        });
    });
});