Рекомендований метод для втечі HTML на Java


262

Чи є спосіб рекомендується бігти <, >, "і &символи, якщо розпечатати HTML в звичайному коді Java? (Крім того, що вручну виконайте наступне, тобто).

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...

2
Майте на увазі, що якщо ви виводите атрибут HTML без котирування, інші символи, такі як пробіл, вкладка, зворотний простір тощо, можуть дозволити зловмисникам вводити атрибути javascript без жодного з перерахованих символів. Докладнішу інформацію див. У шпаргалках OWASP XSS Prevention.
Джефф Вільямс

BTW, у цьому коді вам слід уникнути "&" перед "<", щоб це працювало належним чином ("& lt;" заміняйте на "& amp; lt;"), інакше це відображається як "& lt;", а не "< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23

Відповіді:


261

StringEscapeUtils від Apache Commons Lang :

import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
// ...
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = escapeHtml(source);

Для версії 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);

2
Хоча StringEscapeUtilsце добре, він не уникне належного пробілу для атрибутів, якщо ви хочете уникнути нормалізації пробілів HTML / XML. Дивіться мою відповідь для більш детальної інформації.
Адам Гент

21
Наведений вище приклад порушено. Скористайтеся методом escapeHtml4 () зараз.
stackoverflowuser2010

3
Для шанувальників Guava дивіться відповідь okranz нижче.
Джордж Хокінс

2
Якщо веб-сторінка має кодування UTF-8, тоді все, що нам потрібно, - це htmlEscaper Guava, який уникає лише наступних п'яти символів ASCII: "" & <. сторінки?
zdenekca

4
Тепер це застаріло в commons-lang3. Він був переміщений на commons.apache.org/proper/commons-text
Danny

137

Альтернатива Apache Commons: Використання Spring «s HtmlUtils.htmlEscape(String input)метод.


9
Дякую. Я використовував його (замість StringEscapeUtils.escapeHtml()від apache-commons2.6) , оскільки вона залишає російські символи як є.
Слава Семушин

6
Це добре знати. TBH Я даю речі Apache в цей день широке причал.
Адамський

1
Я також його використав, він також залишає китайські символи, як є.
smartwjw

Як вона порівнюється з альтернативою, згаданою нижче, guava?
vishvAs vAsuki

2
І він також кодує апостроф, тому він насправді корисний, на відміну від apache StringEscapeUtils
Девід

57

Хороший короткий метод:

public static String escapeHTML(String s) {
    StringBuilder out = new StringBuilder(Math.max(16, s.length()));
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c > 127 || c == '"' || c == '\'' || c == '<' || c == '>' || c == '&') {
            out.append("&#");
            out.append((int) c);
            out.append(';');
        } else {
            out.append(c);
        }
    }
    return out.toString();
}

На основі https://stackoverflow.com/a/8838023/1199155 (підсилювач там відсутній). Чотири символи, перевірені в пункті if, є єдиними під 128, відповідно до http://www.w3.org/TR/html4/sgml/entities.html


Приємно. Він не використовує "html-версії" кодувань (приклад: "á" буде "& aacute;" замість "& # 225;"), але оскільки числові працюють навіть у IE7, я думаю, я не повинні турбуватися. Дякую.
nonzaprej

Чому ви кодуєте всіх цих символів, коли ОП попросили уникнути 4 відповідних символів? Ви витрачаєте процесор і пам'ять.
Девід Балажич

1
Ви забули апостроф. Таким чином, люди можуть вводити атрибути, які не котируються, всюди, де цей код використовується для уникнення значень атрибутів.
Девід Балажич

45

Існує новіша версія бібліотеки Apache Commons Lang, і вона використовує іншу назву пакета (org.apache.commons.lang3). StringEscapeUtilsТепер мають різні статичні методи для втечі різних типів документів ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Отже, щоб уникнути рядка HTML версії 4.0:

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;

String output = escapeHtml4("The less than sign (<) and ampersand (&) must be escaped before using them in HTML");

3
На жаль, нічого не існує для HTML 5, а також в документах Apache не вказується, чи правильно використовувати escapeHtml4 для HTML 5.
Пол Вінсент Крейвен

43

Для тих, хто використовує Google Guava:

import com.google.common.html.HtmlEscapers;
[...]
String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = HtmlEscapers.htmlEscaper().escape(source);

40

На android (API 16 або вище) ви можете:

Html.escapeHtml(textToScape);

або для нижчого API:

TextUtils.htmlEncode(textToScape);

Чи є причина використовувати escapeHtmlзамість цього htmlEncode?
Муз

2
Дивіться також моє запитання про різницю між цими двома. (@Muz)
JonasCz

37

Будьте обережні з цим. У документі HTML є декілька різних "контекстів": Всередині елемента, значення атрибута, що цитується, значення атрибута, яке не цитується, атрибут URL, javascript, CSS тощо ... Вам потрібно буде використовувати інший метод кодування для кожного з вони запобігають міжсайтовому сценарію (XSS). Перевірте чит-лист OWASP XSS Prevention, щоб отримати детальну інформацію про кожен із цих контекстів. Ви можете знайти методи виходу для кожного з цих контекстів у бібліотеці OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy .


6
ДЯКУЄМО, що вказали , що контекст, у якому ви хочете кодувати вихід, має велике значення. Термін "кодувати" також є набагато більш відповідним дієсловом, ніж "втеча". Escape передбачає якийсь спеціальний хак, на відміну від "як я кодую цю рядок: атрибут XHTML / параметр запиту SQL / рядок друку PostScript / поле виводу CSV?"
Roboprog

5
"Кодування" та "втеча" широко використовуються для опису цього. Термін "втеча" зазвичай використовується, коли процес полягає в додаванні "символу втечі" перед синтаксично значущим символом, наприклад, уникнення символу цитати із зворотною косою рисою \ "Термін" кодування "зазвичай використовується при перекладі символу в іншій формі, такі як URL-кодування котирування символу% 22 або кодування HTML-сутності як & # x22 або @quot.
Джефф Вільямс,

owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/index.html . посилання тепер розірвало
andrew pate

1
Щоб врятувати вас в Google, шукайте клас Encoder static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/…
Якуб

14

Для деяких цілей HtmlUtils :

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&"); //gives &#38;
HtmlUtils.htmlEscape("&"); //gives &amp;

1
З весни коментарі HtmlUtils: * <p> Для вичерпного набору утилітів String, що виходять, * розгляньте Apache Commons Lang та його клас StringEscapeUtils. * Ми не використовуємо цей клас тут, щоб уникнути залежності часу виконання * від Commons Lang лише для того, щоб уникнути HTML. Крім того, Spring * HTML-вихід є більш гнучким і 100% сумісним HTML 4.0. Якщо ви вже використовуєте спільноту Apache у своєму проекті, ймовірно, вам слід використовувати StringEscapeUtils від apache
andreyro

10

Хоча відповідь @dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmlприємна, і я раніше її використовував, вона не повинна використовуватися для атрибутів HTML (або XML), інакше пробіл буде нормалізований (тобто всі сусідні символи пробілу стають єдиним пробілом).

Я знаю це, тому що у мене були подані помилки проти моєї бібліотеки (JATL) за атрибутами, де пробіл не зберігався. Таким чином, у мене є падіння класу (copy n 'paste) (з якого я вкрав деякі з JDOM), що відрізняє уникнення атрибутів та вмісту елементів. .

Незважаючи на те, що це не мало значення в минулому (правильне уникнення атрибуту), все більше викликає більший інтерес з огляду на використання використання data-атрибутів HTML5 .


9

org.apache.commons.lang3.StringEscapeUtils тепер застарілий. Тепер ви повинні використовувати org.apache.commons.text.StringEscapeUtils від

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>${commons.text.version}</version>
    </dependency>

1

Більшість бібліотек пропонують уникнути всього, що можуть, в тому числі сотні символів та тисячі символів, що не належать до ASCII, а це не те, що потрібно у світі UTF-8.

Крім того, як зауважив Джефф Вільямс, не існує жодного варіанту «втечі HTML», є кілька контекстів.

Якщо припустити, що ви ніколи не використовуєте атрибути, які не котируються, і маючи на увазі, що існують різні контексти, я написав мою власну версію:

private static final long BODY_ESCAPE =
        1L << '&' | 1L << '<' | 1L << '>';
private static final long DOUBLE_QUOTED_ATTR_ESCAPE =
        1L << '"' | 1L << '&' | 1L << '<' | 1L << '>';
private static final long SINGLE_QUOTED_ATTR_ESCAPE =
        1L << '"' | 1L << '&' | 1L << '\'' | 1L << '<' | 1L << '>';

// 'quot' and 'apos' are 1 char longer than '#34' and '#39' which I've decided to use
private static final String REPLACEMENTS = "&#34;&amp;&#39;&lt;&gt;";
private static final int REPL_SLICES = /*  |0,   5,   10,  15, 19, 23*/
        5<<5 | 10<<10 | 15<<15 | 19<<20 | 23<<25;
// These 5-bit numbers packed into a single int
// are indices within REPLACEMENTS which is a 'flat' String[]

private static void appendEscaped(
        StringBuilder builder,
        CharSequence content,
        long escapes // pass BODY_ESCAPE or *_QUOTED_ATTR_ESCAPE here
) {
    int startIdx = 0, len = content.length();
    for (int i = 0; i < len; i++) {
        char c = content.charAt(i);
        long one;
        if (((c & 63) == c) && ((one = 1L << c) & escapes) != 0) {
        // -^^^^^^^^^^^^^^^   -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        // |                  | take only dangerous characters
        // | java shifts longs by 6 least significant bits,
        // | e. g. << 0b110111111 is same as >> 0b111111.
        // | Filter out bigger characters

            int index = Long.bitCount(SINGLE_QUOTED_ATTR_ESCAPE & (one - 1));
            builder.append(content, startIdx, i /* exclusive */)
                    .append(REPLACEMENTS,
                            REPL_SLICES >>> 5*index & 31,
                            REPL_SLICES >>> 5*(index+1) & 31);
            startIdx = i + 1;
        }
    }
    builder.append(content, startIdx, len);
}

Спробуйте скопіювати копіювання з Gist без обмеження довжини рядка .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.