Phương pháp được đề xuất để thoát HTML trong Java


262

Có cách nào đề nghị để thoát <, >, "&nhân vật khi xuất ra HTML trong mã Java đơn giản? (Khác với cách làm thủ công sau đây, nghĩa là).

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

2
Xin lưu ý rằng nếu bạn xuất ra một thuộc tính HTML không được trích dẫn, các ký tự khác như dấu cách, tab, khoảng lùi, v.v ... có thể cho phép kẻ tấn công giới thiệu các thuộc tính javascript mà không có bất kỳ ký tự nào được liệt kê. Xem Bảng cheat phòng chống OWASP XSS để biết thêm.
Jeff Williams

BTW, trong mã này, bạn nên thoát "&" trước "<" để điều này hoạt động chính xác ("& lt;" được thay thế bằng "& amp; lt;" nếu không, được hiển thị là "& lt;", chứ không phải "<" "):source.replace("&", "&amp;").replace("<", "&lt;");
Tey '

Câu trả lời:


261

StringEscapeUtils từ 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);

Đối với phiên bản 3 :

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

2
Mặc dù StringEscapeUtilstốt nhưng nó sẽ không thoát khỏi khoảng trắng đúng cách cho các thuộc tính nếu bạn muốn tránh chuẩn hóa khoảng trắng HTML / XML. Xem câu trả lời của tôi để biết thêm chi tiết.
Adam Gent

21
Ví dụ trên bị hỏng. Sử dụng phương thức escHtml4 () ngay bây giờ.
stackoverflowuser2010

3
Đối với người hâm mộ Guava, xem câu trả lời của okranz dưới đây.
George Hawkins

2
Nếu trang web có mã hóa UTF-8 thì tất cả những gì chúng ta cần là htmlEscaper của Guava chỉ thoát khỏi năm ký tự ASCII sau: '"& <>. EscapeHtml () của Apache cũng thay thế các ký tự không phải ASCII bao gồm các dấu có vẻ không cần thiết với web UTF-8 trang?
zdenekca

4
Bây giờ nó không được dùng trong commons-lang3. Nó đã được chuyển đến commons.apache.org/proper/commons-text
Danny

137

Một thay thế cho Apache Commons: Sử dụng phương thức của SpringHtmlUtils.htmlEscape(String input) .


9
Cảm ơn. Tôi đã sử dụng nó (thay vì StringEscapeUtils.escapeHtml()từ apache-commons2.6) vì nó để lại các ký tự tiếng Nga.
Slava Semushin

6
Đó là điều tốt để biết. TBH Tôi cho công cụ Apache một bến rộng rãi những ngày này.
Adamski

1
Tôi cũng đã sử dụng nó, nó cũng để lại các ký tự Trung Quốc.
smartwjw

Làm thế nào để nó so sánh với ổi thay thế được đề cập dưới đây?
vishvAs vAsuki

2
Và nó cũng mã hóa dấu nháy đơn, vì vậy nó thực sự hữu ích, không giống như chuỗi apache StringEscapeUtils
David Balažic

57

Phương pháp ngắn đẹp:

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

Dựa trên https://stackoverflow.com/a/8838023/1199155 (amp bị thiếu ở đó). Bốn ký tự được kiểm tra trong mệnh đề if là những ký tự duy nhất dưới 128, theo http://www.w3.org/TR/html4/sgml/entities.html


Đẹp. Nó không sử dụng "phiên bản html" của các bảng mã (ví dụ: "á" sẽ là "& aacute;" thay vì "& # 225;"), nhưng vì các số này hoạt động ngay cả trong IE7 tôi đoán tôi không phải lo lắng Cảm ơn.
nonzaprej

Tại sao bạn mã hóa tất cả các ký tự đó khi OP yêu cầu thoát 4 ký tự có liên quan? Bạn đang lãng phí CPU và bộ nhớ.
David Balažic

1
Bạn đã quên dấu nháy đơn. Vì vậy, mọi người có thể tiêm các thuộc tính không được trích dẫn ở mọi nơi mà mã này được sử dụng để thoát các giá trị thuộc tính.
David Balažic

45

Có một phiên bản mới hơn của thư viện Apache Commons Lang và nó sử dụng một tên gói khác (org.apache.commons.lang3). Hiện StringEscapeUtilstại có các phương thức tĩnh khác nhau để thoát các loại tài liệu khác nhau ( http://commons.apache.org/proper/commons-lang/javadocs/api-3.0/index.html ). Vì vậy, để thoát chuỗi HTML phiên bản 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
Thật không may, HTML 5 không tồn tại, cũng như các tài liệu Apache không chỉ định liệu có phù hợp để sử dụng escHtml4 cho HTML 5.
Paul Vincent Craven

43

Đối với những người sử dụng 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

Trên Android (API 16 trở lên), bạn có thể:

Html.escapeHtml(textToScape);

hoặc cho API thấp hơn:

TextUtils.htmlEncode(textToScape);

Có bất kỳ lý do để sử dụng escapeHtmlthay vì htmlEncode?
Muz

2
Xem thêm câu hỏi của tôi về sự khác biệt giữa hai. (@Muz)
JonasCz - Tái lập lại

37

Hãy cẩn thận với điều này. Có một số 'bối cảnh' khác nhau trong một tài liệu HTML: Bên trong một phần tử, giá trị thuộc tính được trích dẫn, giá trị thuộc tính không được trích dẫn, thuộc tính URL, javascript, CSS, v.v ... Bạn sẽ cần sử dụng một phương thức mã hóa khác nhau cho mỗi những điều này để ngăn chặn kịch bản chéo trang web (XSS). Kiểm tra Bảng Cheat Ngăn chặn OWASP XSS để biết chi tiết về từng bối cảnh này. Bạn có thể tìm thấy các phương thức thoát cho từng bối cảnh trong thư viện OWASP ESAPI - https://github.com/ESAPI/esapi-java-legacy .


6
CẢM ƠN BẠN đã chỉ ra rằng bối cảnh mà bạn muốn mã hóa đầu ra rất nhiều vấn đề. Thuật ngữ "mã hóa" cũng là một động từ thích hợp hơn nhiều so với "thoát". Escape ngụ ý một số loại hack đặc biệt, trái ngược với "làm cách nào để mã hóa chuỗi này cho: thuộc tính XHTML / tham số truy vấn SQL / chuỗi in PostScript / trường đầu ra CSV?
Roboprog

5
'Mã hóa' và 'thoát' đều được sử dụng rộng rãi để mô tả điều này. Thuật ngữ "thoát" thường được sử dụng khi quá trình thêm "ký tự thoát" trước một ký tự có liên quan đến cú pháp, chẳng hạn như thoát một ký tự trích dẫn bằng dấu gạch chéo ngược \ "Thuật ngữ" mã hóa "thường được sử dụng hơn khi bạn dịch ký tự thành một dạng khác, chẳng hạn như URL mã hóa ký tự trích dẫn% 22 hoặc mã hóa thực thể HTML dưới dạng & # x22 hoặc @quot.
Jeff Williams


1
Để giúp bạn tiết kiệm thời gian, hãy tìm lớp Encoder static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/
Jakub Bochenski

14

Đối với một số mục đích, HtmlUtils :

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

1
Từ các bình luận HtmlUtils mùa xuân: * <p> Để có một bộ tiện ích thoát Chuỗi toàn diện, * hãy xem xét Apache Commons Lang và lớp StringEscapeUtils của nó. * Chúng tôi không sử dụng lớp đó ở đây để tránh phụ thuộc thời gian chạy * trên Commons Lang chỉ để thoát HTML. Hơn nữa, thoát * HTML của Spring linh hoạt hơn và tuân thủ 100% HTML 4.0. Nếu bạn đã sử dụng Apache commons trong dự án của mình, có lẽ bạn nên sử dụng StringEscapeUtils từ apache
andreyro

10

Mặc dù câu trả lời @dfa org.apache.commons.lang.StringEscapeUtils.escapeHtmllà hay và tôi đã sử dụng nó trong quá khứ, nó không nên được sử dụng để thoát các thuộc tính HTML (hoặc XML) nếu không khoảng trắng sẽ được chuẩn hóa (có nghĩa là tất cả các ký tự khoảng trắng liền kề trở thành một khoảng trắng).

Tôi biết điều này bởi vì tôi đã có lỗi gửi đến thư viện của tôi (JATL) cho các thuộc tính nơi khoảng trắng không được bảo tồn. Vì vậy, tôi có một lớp (sao chép n 'paste) (trong đó tôi đã đánh cắp một số từ JDOM) để phân biệt việc thoát các thuộc tính và nội dung phần tử .

Mặc dù điều này có thể không còn quan trọng trong quá khứ (thoát thuộc tính phù hợp), nhưng nó ngày càng trở nên được quan tâm nhiều hơn khi sử dụng sử dụng data-thuộc tính của HTML5 .


9

org.apache.commons.lang3.StringEscapeUtils hiện không được chấp nhận. Bây giờ bạn phải sử dụng org.apache.commons.text.StringEscapeUtils bởi

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

1

Hầu hết các thư viện cung cấp thoát mọi thứ họ có thể, bao gồm hàng trăm ký hiệu và hàng ngàn ký tự không phải ASCII không phải là thứ bạn muốn trong thế giới UTF-8.

Ngoài ra, như Jeff Williams đã lưu ý, không có tùy chọn nào thoát khỏi HTML HTML, có một số bối cảnh.

Giả sử bạn không bao giờ sử dụng các thuộc tính không được trích dẫn và hãy nhớ rằng các bối cảnh khác nhau tồn tại, nó đã viết phiên bản của riêng tôi:

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

Xem xét dán sao chép từ Gist mà không giới hạn độ dài dòng .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.