Javascript: REGEX для изменения всех относительных URL-адресов на абсолютные - PullRequest
10 голосов
/ 25 сентября 2011

В настоящее время я создаю веб-прокси-сервер Node.js / прокси, но у меня возникают проблемы с анализом относительных URL-адресов, найденных в части исходного кода, написанной сценарием. Я подумал, что REGEX поможет. Хотя неизвестно, как бы я этого достиг.

Могу ли я пойти по этому поводу?

Кроме того, я открыт для более простого способа сделать это, так как я весьма озадачен тем, как другие прокси анализируют сайты. Я полагал, что большинство из них - просто прославленные скребки сайта, которые могут читать исходники сайта и передавать все ссылки / формы обратно на прокси.

Ответы [ 5 ]

41 голосов
/ 25 сентября 2011

Расширенные функции замены строк HTML

Примечание для OP, поскольку он запросил такую ​​функцию: измените base_url на основной URL вашего прокси-сервера, чтобы достичь желаемых результатов.

Ниже будут показаны две функции (руководство по использованию содержится в коде).Убедитесь, что вы не пропустите ни одной части объяснения этого ответа, чтобы полностью понять поведение функции.

  • rel_to_abs(urL) - эта функция возвращает абсолютные URL-адреса.Когда передается абсолютный URL с общепринятым протоколом, он немедленно возвращает этот URL.Иначе, абсолютный URL генерируется из base_url и аргумента функции.Относительно правильно обрабатываются относительные URL (../; ./; .; //).
  • replace_all_rel_by_abs - эта функция будет анализировать все вхождения URL, которые имеютЗначительное значение в HTML, например, CSS url(), ссылки и внешние ресурсы.Смотрите код для полного списка проанализированных экземпляров.См. этот ответ для скорректированной реализации для очистки строк HTML из внешнего источника (для встраивания в документ).
  • Контрольный пример (внизу ответа): чтобы проверить эффективность функции, просто вставьте букмарклет в панель местоположения.

rel_to_abs - Парсинг относительных URL

function rel_to_abs(url){
    /* Only accept commonly trusted protocols:
     * Only data-image URLs are accepted, Exotic flavours (escaped slash,
     * html-entitied characters) are not supported to keep the function fast */
  if(/^(https?|file|ftps?|mailto|javascript|data:image\/[^;]{2,9};):/i.test(url))
         return url; //Url is already absolute

    var base_url = location.href.match(/^(.+)\/?(?:#.+)?$/)[0]+"/";
    if(url.substring(0,2) == "//")
        return location.protocol + url;
    else if(url.charAt(0) == "/")
        return location.protocol + "//" + location.host + url;
    else if(url.substring(0,2) == "./")
        url = "." + url;
    else if(/^\s*$/.test(url))
        return ""; //Empty = Return nothing
    else url = "../" + url;

    url = base_url + url;
    var i=0
    while(/\/\.\.\//.test(url = url.replace(/[^\/]+\/+\.\.\//g,"")));

    /* Escape certain characters to prevent XSS */
    url = url.replace(/\.$/,"").replace(/\/\./g,"").replace(/"/g,"%22")
            .replace(/'/g,"%27").replace(/</g,"%3C").replace(/>/g,"%3E");
    return url;
}

Случаи / примеры:

  • http://foo.bar.Уже абсолютный URL, поэтому сразу возвращается.
  • /doo Относительно корня: возвращает текущий корень + предоставленный относительный URL.
  • ./meh Относительно текущего каталога.
  • ../booh Относительно родительского каталога.

Функция преобразует относительные пути в ../ и выполняет поиск и замену (http://domain/sub/anything-but-a-slash/../me в http://domain/sub/me).

replace_all_rel_by_abs - Преобразование всех соответствующих вхождений URL URL-адреса внутри экземпляров сценария (<script>, обработчики событий не заменены, потому что практически невозможно создать быстрый и безопасный фильтр для анализа JavaScript.

Этот сценарий обслуживается снекоторые комментарии внутри. Регулярные выражения создаются динамически, потому что отдельный RE может иметь размер 3000 символов. <meta http-equiv=refresh content=.. > может быть запутан различными способами, следовательно, размер RE.

function replace_all_rel_by_abs(html){
    /*HTML/XML Attribute may not be prefixed by these characters (common 
       attribute chars.  This list is not complete, but will be sufficient
       for this function (see http://www.w3.org/TR/REC-xml/#NT-NameChar). */
    var att = "[^-a-z0-9:._]";

    var entityEnd = "(?:;|(?!\\d))";
    var ents = {" ":"(?:\\s|&nbsp;?|&#0*32"+entityEnd+"|&#x0*20"+entityEnd+")",
                "(":"(?:\\(|&#0*40"+entityEnd+"|&#x0*28"+entityEnd+")",
                ")":"(?:\\)|&#0*41"+entityEnd+"|&#x0*29"+entityEnd+")",
                ".":"(?:\\.|&#0*46"+entityEnd+"|&#x0*2e"+entityEnd+")"};
                /* Placeholders to filter obfuscations */
    var charMap = {};
    var s = ents[" "]+"*"; //Short-hand for common use
    var any = "(?:[^>\"']*(?:\"[^\"]*\"|'[^']*'))*?[^>]*";
    /* ^ Important: Must be pre- and postfixed by < and >.
     *   This RE should match anything within a tag!  */

    /*
      @name ae
      @description  Converts a given string in a sequence of the original
                      input and the HTML entity
      @param String string  String to convert
      */
    function ae(string){
        var all_chars_lowercase = string.toLowerCase();
        if(ents[string]) return ents[string];
        var all_chars_uppercase = string.toUpperCase();
        var RE_res = "";
        for(var i=0; i<string.length; i++){
            var char_lowercase = all_chars_lowercase.charAt(i);
            if(charMap[char_lowercase]){
                RE_res += charMap[char_lowercase];
                continue;
            }
            var char_uppercase = all_chars_uppercase.charAt(i);
            var RE_sub = [char_lowercase];
            RE_sub.push("&#0*" + char_lowercase.charCodeAt(0) + entityEnd);
            RE_sub.push("&#x0*" + char_lowercase.charCodeAt(0).toString(16) + entityEnd);
            if(char_lowercase != char_uppercase){
                /* Note: RE ignorecase flag has already been activated */
                RE_sub.push("&#0*" + char_uppercase.charCodeAt(0) + entityEnd);   
                RE_sub.push("&#x0*" + char_uppercase.charCodeAt(0).toString(16) + entityEnd);
            }
            RE_sub = "(?:" + RE_sub.join("|") + ")";
            RE_res += (charMap[char_lowercase] = RE_sub);
        }
        return(ents[string] = RE_res);
    }

    /*
      @name by
      @description  2nd argument for replace().
      */
    function by(match, group1, group2, group3){
        /* Note that this function can also be used to remove links:
         * return group1 + "javascript://" + group3; */
        return group1 + rel_to_abs(group2) + group3;
    }
    /*
      @name by2
      @description  2nd argument for replace(). Parses relevant HTML entities
      */
    var slashRE = new RegExp(ae("/"), 'g');
    var dotRE = new RegExp(ae("."), 'g');
    function by2(match, group1, group2, group3){
        /*Note that this function can also be used to remove links:
         * return group1 + "javascript://" + group3; */
        group2 = group2.replace(slashRE, "/").replace(dotRE, ".");
        return group1 + rel_to_abs(group2) + group3;
    }
    /*
      @name cr
      @description            Selects a HTML element and performs a
                                search-and-replace on attributes
      @param String selector  HTML substring to match
      @param String attribute RegExp-escaped; HTML element attribute to match
      @param String marker    Optional RegExp-escaped; marks the prefix
      @param String delimiter Optional RegExp escaped; non-quote delimiters
      @param String end       Optional RegExp-escaped; forces the match to end
                              before an occurence of <end>
     */
    function cr(selector, attribute, marker, delimiter, end){
        if(typeof selector == "string") selector = new RegExp(selector, "gi");
        attribute = att + attribute;
        marker = typeof marker == "string" ? marker : "\\s*=\\s*";
        delimiter = typeof delimiter == "string" ? delimiter : "";
        end = typeof end == "string" ? "?)("+end : ")(";
        var re1 = new RegExp('('+attribute+marker+'")([^"'+delimiter+']+'+end+')', 'gi');
        var re2 = new RegExp("("+attribute+marker+"')([^'"+delimiter+"]+"+end+")", 'gi');
        var re3 = new RegExp('('+attribute+marker+')([^"\'][^\\s>'+delimiter+']*'+end+')', 'gi');
        html = html.replace(selector, function(match){
            return match.replace(re1, by).replace(re2, by).replace(re3, by);
        });
    }
    /* 
      @name cri
      @description            Selects an attribute of a HTML element, and
                                performs a search-and-replace on certain values
      @param String selector  HTML element to match
      @param String attribute RegExp-escaped; HTML element attribute to match
      @param String front     RegExp-escaped; attribute value, prefix to match
      @param String flags     Optional RegExp flags, default "gi"
      @param String delimiter Optional RegExp-escaped; non-quote delimiters
      @param String end       Optional RegExp-escaped; forces the match to end
                                before an occurence of <end>
     */
    function cri(selector, attribute, front, flags, delimiter, end){
        if(typeof selector == "string") selector = new RegExp(selector, "gi");
        attribute = att + attribute;
        flags = typeof flags == "string" ? flags : "gi";
        var re1 = new RegExp('('+attribute+'\\s*=\\s*")([^"]*)', 'gi');
        var re2 = new RegExp("("+attribute+"\\s*=\\s*')([^']+)", 'gi');
        var at1 = new RegExp('('+front+')([^"]+)(")', flags);
        var at2 = new RegExp("("+front+")([^']+)(')", flags);
        if(typeof delimiter == "string"){
            end = typeof end == "string" ? end : "";
            var at3 = new RegExp("("+front+")([^\"'][^"+delimiter+"]*" + (end?"?)("+end+")":")()"), flags);
            var handleAttr = function(match, g1, g2){return g1+g2.replace(at1, by2).replace(at2, by2).replace(at3, by2)};
        } else {
            var handleAttr = function(match, g1, g2){return g1+g2.replace(at1, by2).replace(at2, by2)};
    }
        html = html.replace(selector, function(match){
             return match.replace(re1, handleAttr).replace(re2, handleAttr);
        });
    }

    /* <meta http-equiv=refresh content="  ; url= " > */
    cri("<meta"+any+att+"http-equiv\\s*=\\s*(?:\""+ae("refresh")+"\""+any+">|'"+ae("refresh")+"'"+any+">|"+ae("refresh")+"(?:"+ae(" ")+any+">|>))", "content", ae("url")+s+ae("=")+s, "i");

    cr("<"+any+att+"href\\s*="+any+">", "href"); /* Linked elements */
    cr("<"+any+att+"src\\s*="+any+">", "src"); /* Embedded elements */

    cr("<object"+any+att+"data\\s*="+any+">", "data"); /* <object data= > */
    cr("<applet"+any+att+"codebase\\s*="+any+">", "codebase"); /* <applet codebase= > */

    /* <param name=movie value= >*/
    cr("<param"+any+att+"name\\s*=\\s*(?:\""+ae("movie")+"\""+any+">|'"+ae("movie")+"'"+any+">|"+ae("movie")+"(?:"+ae(" ")+any+">|>))", "value");

    cr(/<style[^>]*>(?:[^"']*(?:"[^"]*"|'[^']*'))*?[^'"]*(?:<\/style|$)/gi, "url", "\\s*\\(\\s*", "", "\\s*\\)"); /* <style> */
    cri("<"+any+att+"style\\s*="+any+">", "style", ae("url")+s+ae("(")+s, 0, s+ae(")"), ae(")")); /*< style=" url(...) " > */
    return html;
}

Краткий обзор частных функций:

  • rel_to_abs(url) - Преобразует относительные / неизвестные URL-адреса в абсолютные URL-адреса
  • replace_all_rel_by_abs(html) - Заменяет все соответствующие вхождения URL-адресов встрока HTML по абсолютным URL.
    1. ae - A ny E ntity - возвращает RE-шаблон для работы с объектами HTML.
    2. by - заменить на - Эта короткая функция запрашивает фактическую замену URL (rel_to_abs). Эту функцию можно вызывать сотни, если не тысячу раз. Будьте осторожны, чтобы не добавлять медленный алгоритм кэта функцияion).
    3. cr - C reate R eplace - Создает и выполняет поиск и замену.Пример: href="..." (внутри любого тега HTML).
    4. cri - C reate R eplace I nline - Создает и выполняетпоиск и замена.Пример: url(..) в атрибуте all style в тегах HTML.

Контрольный пример

Откройте любую страницу и вставьте следующий букмарклет встрока адреса:

javascript:void(function(){var s=document.createElement("script");s.src="http://rob.lekensteyn.nl/rel_to_abs.js";document.body.appendChild(s)})();

Введенный код содержит две функции, как определено выше, плюс контрольный пример, показанный ниже. Примечание : контрольный пример не изменяет HTML-код страницы, но показывает проанализированные результаты в текстовой области (необязательно).

var t=(new Date).getTime();
  var result = replace_all_rel_by_abs(document.documentElement.innerHTML);
  if(confirm((new Date).getTime()-t+" milliseconds to execute\n\nPut results in new textarea?")){
    var txt = document.createElement("textarea");
    txt.style.cssText = "position:fixed;top:0;left:0;width:100%;height:99%"
    txt.ondblclick = function(){this.parentNode.removeChild(this)}
    txt.value = result;
    document.body.appendChild(txt);
}

См. Также:

2 голосов
/ 12 июля 2012

Надежный способ преобразования URL-адресов из относительных в абсолютные - это использование встроенного url модуля .

Пример:

var url = require('url');
url.resolve("http://www.example.org/foo/bar/", "../baz/qux.html");

>> gives 'http://www.example.org/foo/baz/qux.html' 
1 голос
/ 05 ноября 2014

Это Ответ Роба В. «Расширенные функции замены строк HTML» в текущем потоке плюс некоторый ре-факторинг моего кода, чтобы сделать JSLint счастливым.

Я должен опубликовать его как комментарий к ответу, но у меня недостаточно очков репутации.

/*jslint browser: true */
/*jslint regexp: true */
/*jslint unparam: true*/
/*jshint strict: false */

/**
 * convertRelToAbsUrl
 *
 * https://stackoverflow.com/a/7544757/1983903
 * 
 * @param  {String} url
 * @return {String} updated url
 */
function convertRelToAbsUrl(url) {
    var baseUrl = null;

    if (/^(https?|file|ftps?|mailto|javascript|data:image\/[^;]{2,9};):/i.test(url)) {
        return url; // url is already absolute
    }

    baseUrl = location.href.match(/^(.+)\/?(?:#.+)?$/)[0] + '/';

    if (url.substring(0, 2) === '//') {
        return location.protocol + url;
    }
    if (url.charAt(0) === '/') {
        return location.protocol + '//' + location.host + url;
    }
    if (url.substring(0, 2) === './') {
        url = '.' + url;
    } else if (/^\s*$/.test(url)) {
        return ''; // empty = return nothing
    }

    url = baseUrl + '../' + url;

    while (/\/\.\.\//.test(url)) {
        url = url.replace(/[^\/]+\/+\.\.\//g, '');
    }

    url = url.replace(/\.$/, '').replace(/\/\./g, '').replace(/"/g, '%22')
            .replace(/'/g, '%27').replace(/</g, '%3C').replace(/>/g, '%3E');

    return url;
}

/**
 * convertAllRelativeToAbsoluteUrls
 *
 * https://stackoverflow.com/a/7544757/1983903
 * 
 * @param  {String} html
 * @return {String} updated html
 */
function convertAllRelativeToAbsoluteUrls(html) {
    var me = this,
        att = '[^-a-z0-9:._]',
        entityEnd = '(?:;|(?!\\d))',
        ents = {
            ' ' : '(?:\\s|&nbsp;?|&#0*32' + entityEnd + '|&#x0*20' + entityEnd + ')',
            '(' : '(?:\\(|&#0*40' + entityEnd + '|&#x0*28' + entityEnd + ')',
            ')' : '(?:\\)|&#0*41' + entityEnd + '|&#x0*29' + entityEnd + ')',
            '.' : '(?:\\.|&#0*46' + entityEnd + '|&#x0*2e' + entityEnd + ')'
        },
        charMap = {},
        s = ents[' '] + '*', // short-hand for common use
        any = '(?:[^>\"\']*(?:\"[^\"]*\"|\'[^\']*\'))*?[^>]*',
        slashRE = null,
        dotRE = null;

    function ae(string) {
        var allCharsLowerCase = string.toLowerCase(),
            allCharsUpperCase = string.toUpperCase(),
            reRes = '',
            charLowerCase = null,
            charUpperCase = null,
            reSub = null,
            i = null;

        if (ents[string]) {
            return ents[string];
        }

        for (i = 0; i < string.length; i++) {
            charLowerCase = allCharsLowerCase.charAt(i);
            if (charMap[charLowerCase]) {
                reRes += charMap[charLowerCase];
                continue;
            }
            charUpperCase = allCharsUpperCase.charAt(i);
            reSub = [charLowerCase];
            reSub.push('&#0*' + charLowerCase.charCodeAt(0) + entityEnd);
            reSub.push('&#x0*' + charLowerCase.charCodeAt(0).toString(16) + entityEnd);

            if (charLowerCase !== charUpperCase) {
                reSub.push('&#0*' + charUpperCase.charCodeAt(0) + entityEnd);
                reSub.push('&#x0*' + charUpperCase.charCodeAt(0).toString(16) + entityEnd);
            }
            reSub = '(?:' + reSub.join('|') + ')';
            reRes += (charMap[charLowerCase] = reSub);
        }
        return (ents[string] = reRes);
    }

    function by(match, group1, group2, group3) {
        return group1 + me.convertRelToAbsUrl(group2) + group3;
    }

    slashRE = new RegExp(ae('/'), 'g');
    dotRE = new RegExp(ae('.'), 'g');

    function by2(match, group1, group2, group3) {
        group2 = group2.replace(slashRE, '/').replace(dotRE, '.');
        return group1 + me.convertRelToAbsUrl(group2) + group3;
    }

    function cr(selector, attribute, marker, delimiter, end) {
        var re1 = null,
            re2 = null,
            re3 = null;

        if (typeof selector === 'string') {
            selector = new RegExp(selector, 'gi');
        }

        attribute = att + attribute;
        marker = typeof marker === 'string' ? marker : '\\s*=\\s*';
        delimiter = typeof delimiter === 'string' ? delimiter : '';
        end = typeof end === 'string' ? '?)(' + end : ')(';

        re1 = new RegExp('(' + attribute + marker + '")([^"' + delimiter + ']+' + end + ')', 'gi');
        re2 = new RegExp('(' + attribute + marker + '\')([^\'' + delimiter + ']+' + end + ')', 'gi');
        re3 = new RegExp('(' + attribute + marker + ')([^"\'][^\\s>' + delimiter + ']*' + end + ')', 'gi');

        html = html.replace(selector, function (match) {
            return match.replace(re1, by).replace(re2, by).replace(re3, by);
        });
    }

    function cri(selector, attribute, front, flags, delimiter, end) {
        var re1 = null,
            re2 = null,
            at1 = null,
            at2 = null,
            at3 = null,
            handleAttr = null;

        if (typeof selector === 'string') {
            selector = new RegExp(selector, 'gi');
        }

        attribute = att + attribute;
        flags = typeof flags === 'string' ? flags : 'gi';
        re1 = new RegExp('(' + attribute + '\\s*=\\s*")([^"]*)', 'gi');
        re2 = new RegExp("(" + attribute + "\\s*=\\s*')([^']+)", 'gi');
        at1 = new RegExp('(' + front + ')([^"]+)(")', flags);
        at2 = new RegExp("(" + front + ")([^']+)(')", flags);

        if (typeof delimiter === 'string') {
            end = typeof end === 'string' ? end : '';
            at3 = new RegExp('(' + front + ')([^\"\'][^' + delimiter + ']*' + (end ? '?)(' + end + ')' : ')()'), flags);
            handleAttr = function (match, g1, g2) {
                return g1 + g2.replace(at1, by2).replace(at2, by2).replace(at3, by2);
            };
        } else {
            handleAttr = function (match, g1, g2) {
                return g1 + g2.replace(at1, by2).replace(at2, by2);
            };
        }
        html = html.replace(selector, function (match) {
            return match.replace(re1, handleAttr).replace(re2, handleAttr);
        });
    }

    cri('<meta' + any + att + 'http-equiv\\s*=\\s*(?:\"' + ae('refresh')
        + '\"' + any + '>|\'' + ae('refresh') + '\'' + any + '>|' + ae('refresh')
        + '(?:' + ae(' ') + any + '>|>))', 'content', ae('url') + s + ae('=') + s, 'i');

    cr('<' + any + att + 'href\\s*=' + any + '>', 'href'); /* Linked elements */
    cr('<' + any + att + 'src\\s*=' + any + '>', 'src'); /* Embedded elements */

    cr('<object' + any + att + 'data\\s*=' + any + '>', 'data'); /* <object data= > */
    cr('<applet' + any + att + 'codebase\\s*=' + any + '>', 'codebase'); /* <applet codebase= > */

    /* <param name=movie value= >*/
    cr('<param' + any + att + 'name\\s*=\\s*(?:\"' + ae('movie') + '\"' + any + '>|\''
        + ae('movie') + '\'' + any + '>|' + ae('movie') + '(?:' + ae(' ') + any + '>|>))', 'value');

    cr(/<style[^>]*>(?:[^"']*(?:"[^"]*"|'[^']*'))*?[^'"]*(?:<\/style|$)/gi,
        'url', '\\s*\\(\\s*', '', '\\s*\\)'); /* <style> */
    cri('<' + any + att + 'style\\s*=' + any + '>', 'style',
        ae('url') + s + ae('(') + s, 0, s + ae(')'), ae(')')); /*< style=" url(...) " > */

    return html;
}
0 голосов
/ 09 июня 2017

Из комментария Роба В. выше о базовом теге я написал функцию инъекции:

function injectBase(html, base) {
  // Remove any <base> elements inside <head>     
  html = html.replace(/(<[^>/]*head[^>]*>)[\s\S]*?(<[^>/]*base[^>]*>)[\s\S]*?(<[^>]*head[^>]*>)/img, "$1 $3");

  // Add <base> just before </head>  
  html = html.replace(/(<[^>/]*head[^>]*>[\s\S]*?)(<[^>]*head[^>]*>)/img, "$1 " + base + " $2");  
  return(html);
}
0 голосов
/ 25 сентября 2011

Если вы используете регулярное выражение для поиска всех неабсолютных URL-адресов, вы можете просто добавить в них префикс к текущему URL-адресу, и это должно быть именно так.

URL-адреса, которые нужно исправить, будут теми, которые неt начать с / или http(s):// (или других маркеров протокола, если вы заботитесь о них)

В качестве примера, скажем, вы очищаете http://www.example.com/.Если вы встретите относительный URL, скажем, foo/bar, вы просто добавите префикс URL, который будет добавлен к нему, следующим образом: http://www.example.com/foo/bar

Для регулярных выражений, чтобы очистить URL-адреса со страницы, вероятно, есть многоиз хороших доступных, если вы немного погуглите, поэтому я не собираюсь начинать изобретать плохой здесь:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...