Metode yang disarankan untuk keluar dari HTML di Jawa

262

Apakah ada cara yang direkomendasikan untuk melarikan diri <, >, "dan &karakter ketika keluaran HTML dalam kode Java polos? (Selain secara manual melakukan hal berikut, yaitu).

String source = "The less than sign (<) and ampersand (&) must be escaped before using them in HTML";
String escaped = source.replace("<", "&lt;").replace("&", "&amp;"); // ...
Ben Lings
sumber
2
Ketahuilah bahwa jika Anda menghasilkan atribut HTML yang tidak dikutip, bahwa karakter lain seperti spasi, tab, backspace, dll ... dapat memungkinkan penyerang untuk memperkenalkan atribut javascript tanpa ada karakter yang terdaftar. Lihat Lembar Curang Pencegahan OWASP XSS untuk lebih.
Jeff Williams
BTW, dalam kode ini, Anda harus melarikan diri "&" sebelum "<" agar ini berfungsi dengan baik ("& lt;" diganti dengan "& amp; lt;" jika tidak, yang diterjemahkan sebagai "& lt;" lalu, bukan "< "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '23

Jawaban:

261

StringEscapeUtils dari 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);

Untuk versi 3 :

import static org.apache.commons.lang3.StringEscapeUtils.escapeHtml4;
// ...
String escaped = escapeHtml4(source);
dfa
sumber
2
Meskipun StringEscapeUtilsbagus, itu tidak akan keluar dari spasi dengan benar untuk atribut jika Anda ingin menghindari normalisasi HTML / XML spasi. Lihat jawaban saya untuk detail lebih lanjut.
Adam Gent
21
Contoh di atas rusak. Gunakan metode escapeHtml4 () sekarang.
stackoverflowuser2010
3
Untuk penggemar Guava lihat jawaban okranz di bawah ini.
George Hawkins
2
Jika halaman web memiliki pengkodean UTF-8 maka yang kita butuhkan adalah htmlEscaper Guava yang lolos hanya lima karakter ASCII berikut: '"& <>. Apache's escapeHtml () juga menggantikan karakter non-ASCII termasuk aksen yang tampaknya tidak perlu dengan web UTF-8 halaman?
zdenekca
4
Sekarang sudah ditinggalkan di commons-lang3. Itu dipindahkan ke commons.apache.org/proper/commons-text
Danny
137

Sebuah alternatif untuk Apache Commons: Gunakan Musim Semi 's HtmlUtils.htmlEscape(String input)metode.

Adamski
sumber
9
Terima kasih. Saya sudah menggunakannya (bukan StringEscapeUtils.escapeHtml()dari apache-commons2.6) karena meninggalkan karakter Rusia apa adanya.
Slava Semushin
6
Senang mendengarnya. TBH Saya memberikan barang-barang Apache tempat tidur yang luas akhir-akhir ini.
Adamski
1
Saya sudah menggunakannya juga, itu meninggalkan karakter Cina seperti apa adanya.
smartwjw
Bagaimana perbandingannya dengan alternatif jambu biji yang disebutkan di bawah ini?
vishva vAsuki
2
Dan itu juga mengkode apostrof, jadi itu sebenarnya berguna, tidak seperti apache StringEscapeUtils
David Balažic
57

Metode pendek yang bagus:

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();
}

Berdasarkan https://stackoverflow.com/a/8838023/1199155 (amp hilang di sana). Keempat karakter yang diperiksa dalam klausa if adalah satu-satunya di bawah 128, menurut http://www.w3.org/TR/html4/sgml/entities.html

Bruno Eberhard
sumber
Bagus. Itu tidak menggunakan "versi html" dari penyandian (contoh: "a" akan menjadi "& aacute;" bukannya "& # 225;"), tetapi karena yang numerik bekerja bahkan di IE7 saya kira saya tidak harus khawatir. Terima kasih.
nonzaprej
Mengapa Anda menyandikan semua karakter itu ketika OP diminta untuk keluar dari 4 karakter yang relevan? Anda membuang-buang CPU dan memori.
David Balažic
1
Anda lupa apostrof. Jadi orang dapat menyuntikkan atribut yang tidak dikutip di mana saja di mana kode ini digunakan untuk keluar dari nilai atribut.
David Balažic
45

Ada versi yang lebih baru dari perpustakaan Lang Apache Commons dan menggunakan nama paket yang berbeda (org.apache.commons.lang3). The StringEscapeUtilssekarang memiliki metode statis yang berbeda untuk melarikan diri berbagai jenis dokumen ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Jadi untuk menghindari string HTML versi 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");
Martin Dimitrov
sumber
3
Sayangnya tidak ada yang ada untuk HTML 5, dokumen Apache juga tidak menentukan apakah layak menggunakan escapeHtml4 untuk HTML 5.
Paul Vincent Craven
43

Bagi mereka yang menggunakan 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);
okrasz
sumber
40

Di android (API 16 atau lebih tinggi) Anda dapat:

Html.escapeHtml(textToScape);

atau untuk API yang lebih rendah:

TextUtils.htmlEncode(textToScape);
OriolJ
sumber
Apakah ada alasan untuk menggunakannya escapeHtml bukan htmlEncode?
Muz
2
Lihat juga pertanyaanku tentang perbedaan antara keduanya. (@Muz)
JonasCz
37

Hati-hati dengan ini. Ada sejumlah 'konteks' yang berbeda dalam dokumen HTML: Di dalam elemen, nilai atribut yang dikutip, nilai atribut yang tidak dikutip, atribut URL, javascript, CSS, dll ... Anda harus menggunakan metode pengkodean yang berbeda untuk masing-masing ini untuk mencegah Cross-Site Scripting (XSS). Periksa Lembar Curang Pencegahan OWASP XSS untuk perincian tentang masing-masing konteks ini. Anda dapat menemukan metode melarikan diri untuk masing-masing konteks ini di perpustakaan OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy .

Jeff Williams
sumber
6
Terima kasih telah menunjukkan bahwa konteks di mana Anda ingin mengkodekan output sangat penting. Istilah "encode" juga kata kerja yang jauh lebih tepat daripada "escape", juga. Escape menyiratkan semacam peretasan khusus, yang bertentangan dengan "bagaimana cara menyandikan string ini untuk: atribut XHTML / parameter kueri SQL / string cetak PostScript / bidang keluaran CSV?
Roboprog
5
'Encode' dan 'escape' keduanya banyak digunakan untuk menggambarkan ini. Istilah "escape" umumnya digunakan ketika prosesnya adalah untuk menambahkan "escape character" sebelum karakter yang relevan secara sintaksis, seperti keluar dari karakter kutipan dengan garis miring terbalik \ "Istilah" encode "lebih biasanya digunakan ketika Anda menerjemahkan suatu karakter ke bentuk yang berbeda, seperti URL yang menyandikan karakter kutipan% 22 atau penyandian entitas HTML sebagai & # x22 atau @quot.
Jeff Williams
1
Untuk menghemat Google, cari kelas Encoder static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/…
Jakub Bochenski
14

Untuk beberapa tujuan, HtmlUtils :

import org.springframework.web.util.HtmlUtils;
[...]
HtmlUtils.htmlEscapeDecimal("&"); //gives &#38;
HtmlUtils.htmlEscape("&"); //gives &amp;
AUU
sumber
1
Dari komentar HtmlUtils musim semi: * <p> Untuk seperangkat utilitas pelolosan String yang komprehensif, * pertimbangkan Apache Commons Lang dan kelas StringEscapeUtils-nya. * Kami tidak menggunakan kelas itu di sini untuk menghindari ketergantungan runtime * pada Commons Lang hanya untuk pelolosan HTML. Lebih jauh, Spring's * HTML escaping lebih fleksibel dan 100% HTML 4.0 compliant. Jika Anda sudah menggunakan Apache commons dalam proyek Anda, mungkin Anda harus menggunakan StringEscapeUtils dari apache
andreyro
10

Meskipun jawaban @ dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmlbagus dan saya telah menggunakannya di masa lalu, seharusnya tidak digunakan untuk keluar dari atribut HTML (atau XML) kalau tidak spasi akan dinormalisasi (artinya semua karakter spasi yang berdekatan menjadi ruang tunggal).

Saya tahu ini karena saya memiliki bug yang diajukan terhadap perpustakaan saya (JATL) untuk atribut di mana spasi putih tidak dipertahankan. Jadi saya memiliki drop (copy n 'paste) kelas (yang saya mencuri beberapa dari JDOM) yang membedakan pelarian atribut dan konten elemen .

Meskipun ini mungkin tidak terlalu penting di masa lalu (atribut yang tepat melarikan diri) itu menjadi semakin menarik mengingat penggunaan penggunaan data-penggunaan atribut HTML5 .

Adam Gent
sumber
9

org.apache.commons.lang3.StringEscapeUtils sekarang sudah tidak digunakan lagi. Anda sekarang harus menggunakan org.apache.commons.text.StringEscapeUtils oleh

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

Sebagian besar perpustakaan menawarkan pelarian semua yang mereka bisa, termasuk ratusan simbol dan ribuan karakter non-ASCII yang bukan yang Anda inginkan di dunia UTF-8.

Juga, seperti yang dicatat Jeff Williams, tidak ada opsi "escape HTML", ada beberapa konteks.

Dengan asumsi Anda tidak pernah menggunakan atribut yang tidak dikutip, dan mengingat bahwa ada konteks yang berbeda, itu telah menulis versi saya sendiri:

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);
}

Pertimbangkan copy-paste dari Gist tanpa batas panjang garis .

Miha_x64
sumber