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;"); // ...

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

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.


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


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

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

40

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

Html.escapeHtml(textToScape);

atau untuk API yang lebih rendah:

TextUtils.htmlEncode(textToScape);

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 .


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



14

Untuk beberapa tujuan, HtmlUtils :

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

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 .


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>

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 .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.