mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 05:26:57 +00:00
1b4ae5a7e1
Cleanup for https://github.com/dart-lang/dartdoc/issues/3531 This makes these comment references more idiomatic, I think more readable, and supports dropping the leading `new ` syntax in doc comments. Change-Id: Id832ad14d9ea08fe03fe3125065755f49b1b93ed CoreLibraryReviewExempt: doc comment only change Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/330683 Reviewed-by: Devon Carew <devoncarew@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
280 lines
9.6 KiB
Dart
280 lines
9.6 KiB
Dart
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of dart.convert;
|
|
|
|
/// A `String` converter that converts characters to HTML entities.
|
|
///
|
|
/// This is intended to sanitize text before inserting the text into an HTML
|
|
/// document. Characters that are meaningful in HTML are converted to
|
|
/// HTML entities (like `&` for `&`).
|
|
///
|
|
/// The general converter escapes all characters that are meaningful in HTML
|
|
/// attributes or normal element context. Elements with special content types
|
|
/// (like CSS or JavaScript) may need a more specialized escaping that
|
|
/// understands that content type.
|
|
///
|
|
/// If the context where the text will be inserted is known in more detail,
|
|
/// it's possible to omit escaping some characters (like quotes when not
|
|
/// inside an attribute value).
|
|
///
|
|
/// The escaped text should only be used inside quoted HTML attributes values
|
|
/// or as text content of a normal element. Using the escaped text inside a
|
|
/// tag, but not inside a quoted attribute value, is still dangerous.
|
|
const HtmlEscape htmlEscape = HtmlEscape();
|
|
|
|
/// HTML escape modes.
|
|
///
|
|
/// Allows specifying a mode for HTML escaping that depends on the context
|
|
/// where the escaped result is going to be used.
|
|
/// The relevant contexts are:
|
|
///
|
|
/// * as text content of an HTML element.
|
|
/// * as value of a (single- or double-) quoted attribute value.
|
|
///
|
|
/// All modes require escaping of `&` (ampersand) characters, and may
|
|
/// enable escaping of more characters.
|
|
///
|
|
/// Custom escape modes can be created using the [HtmlEscapeMode.new]
|
|
/// constructor.
|
|
///
|
|
/// Example:
|
|
/// ```dart
|
|
/// const htmlEscapeMode = HtmlEscapeMode(
|
|
/// name: 'custom',
|
|
/// escapeLtGt: true,
|
|
/// escapeQuot: false,
|
|
/// escapeApos: false,
|
|
/// escapeSlash: false,
|
|
/// );
|
|
///
|
|
/// const HtmlEscape htmlEscape = HtmlEscape(htmlEscapeMode);
|
|
/// String unescaped = 'Text & subject';
|
|
/// String escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Text & subject
|
|
///
|
|
/// unescaped = '10 > 1 and 1 < 10';
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // 10 > 1 and 1 < 10
|
|
///
|
|
/// unescaped = "Single-quoted: 'text'";
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Single-quoted: 'text'
|
|
///
|
|
/// unescaped = 'Double-quoted: "text"';
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Double-quoted: "text"
|
|
///
|
|
/// unescaped = 'Path: /system/';
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Path: /system/
|
|
/// ```
|
|
final class HtmlEscapeMode {
|
|
final String _name;
|
|
|
|
/// Whether to escape '<' and '>'.
|
|
final bool escapeLtGt;
|
|
|
|
/// Whether to escape '"' (quote).
|
|
final bool escapeQuot;
|
|
|
|
/// Whether to escape "'" (apostrophe).
|
|
final bool escapeApos;
|
|
|
|
/// Whether to escape "/" (forward slash, solidus).
|
|
///
|
|
/// Escaping a slash is recommended to avoid cross-site scripting attacks by
|
|
/// [the Open Web Application Security Project](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content)
|
|
final bool escapeSlash;
|
|
|
|
/// Default escaping mode, which escapes all characters.
|
|
///
|
|
/// The result of such an escaping is usable both in element content and
|
|
/// in any attribute value.
|
|
///
|
|
/// The escaping only works for elements with normal HTML content,
|
|
/// and not, for example, for script or style element content,
|
|
/// which require escapes matching their particular content syntax.
|
|
static const HtmlEscapeMode unknown =
|
|
HtmlEscapeMode._('unknown', true, true, true, true);
|
|
|
|
/// Escaping mode for text going into double-quoted HTML attribute values.
|
|
///
|
|
/// The result should not be used as the content of an unquoted
|
|
/// or single-quoted attribute value.
|
|
///
|
|
/// Escapes double quotes (`"`) but not single quotes (`'`),
|
|
/// and escapes `<` and `>` characters because they are not allowed
|
|
/// in strict XHTML attributes
|
|
static const HtmlEscapeMode attribute =
|
|
HtmlEscapeMode._('attribute', true, true, false, false);
|
|
|
|
/// Escaping mode for text going into single-quoted HTML attribute values.
|
|
///
|
|
/// The result should not be used as the content of an unquoted
|
|
/// or double-quoted attribute value.
|
|
///
|
|
/// Escapes single quotes (`'`) but not double quotes (`"`),
|
|
/// and escapes `<` and `>` characters because they are not allowed
|
|
/// in strict XHTML attributes.
|
|
static const HtmlEscapeMode sqAttribute =
|
|
HtmlEscapeMode._('attribute', true, false, true, false);
|
|
|
|
/// Escaping mode for text going into HTML element content.
|
|
///
|
|
/// The escaping only works for elements with normal HTML content,
|
|
/// and not, for example, for script or style element content,
|
|
/// which require escapes matching their particular content syntax.
|
|
///
|
|
/// Escapes `<` and `>` characters.
|
|
static const HtmlEscapeMode element =
|
|
HtmlEscapeMode._('element', true, false, false, false);
|
|
|
|
const HtmlEscapeMode._(this._name, this.escapeLtGt, this.escapeQuot,
|
|
this.escapeApos, this.escapeSlash);
|
|
|
|
/// Create a custom escaping mode.
|
|
///
|
|
/// All modes escape `&`.
|
|
/// The mode can further be set to escape `<` and `>` ([escapeLtGt]),
|
|
/// `"` ([escapeQuot]), `'` ([escapeApos]), and/or `/` ([escapeSlash]).
|
|
const HtmlEscapeMode(
|
|
{String name = "custom",
|
|
this.escapeLtGt = false,
|
|
this.escapeQuot = false,
|
|
this.escapeApos = false,
|
|
this.escapeSlash = false})
|
|
: _name = name;
|
|
|
|
String toString() => _name;
|
|
}
|
|
|
|
/// Converter which escapes characters with special meaning in HTML.
|
|
///
|
|
/// The converter finds characters that are significant in the HTML source and
|
|
/// replaces them with corresponding HTML entities.
|
|
///
|
|
/// The characters that need escaping in HTML are:
|
|
///
|
|
/// * `&` (ampersand) always needs to be escaped.
|
|
/// * `<` (less than) and `>` (greater than) when inside an element.
|
|
/// * `"` (quote) when inside a double-quoted attribute value.
|
|
/// * `'` (apostrophe) when inside a single-quoted attribute value.
|
|
/// Apostrophe is escaped as `'` instead of `'` since
|
|
/// not all browsers understand `'`.
|
|
/// * `/` (slash) is recommended to be escaped because it may be used
|
|
/// to terminate an element in some HTML dialects.
|
|
///
|
|
/// Escaping `>` (greater than) isn't necessary, but the result is often
|
|
/// found to be easier to read if greater-than is also escaped whenever
|
|
/// less-than is.
|
|
///
|
|
/// Example:
|
|
/// ```dart
|
|
/// const HtmlEscape htmlEscape = HtmlEscape();
|
|
/// String unescaped = 'Text & subject';
|
|
/// String escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Text & subject
|
|
///
|
|
/// unescaped = '10 > 1 and 1 < 10';
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // 10 > 1 and 1 < 10
|
|
///
|
|
/// unescaped = "Single-quoted: 'text'";
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Single-quoted: 'text'
|
|
///
|
|
/// unescaped = 'Double-quoted: "text"';
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Double-quoted: "text"
|
|
///
|
|
/// unescaped = 'Path: /system/';
|
|
/// escaped = htmlEscape.convert(unescaped);
|
|
/// print(escaped); // Path: /system/
|
|
/// ```
|
|
final class HtmlEscape extends Converter<String, String> {
|
|
/// The [HtmlEscapeMode] used by the converter.
|
|
final HtmlEscapeMode mode;
|
|
|
|
/// Create converter that escapes HTML characters.
|
|
///
|
|
/// If [mode] is provided as either [HtmlEscapeMode.attribute] or
|
|
/// [HtmlEscapeMode.element], only the corresponding subset of HTML
|
|
/// characters is escaped.
|
|
/// The default is to escape all HTML characters.
|
|
const HtmlEscape([this.mode = HtmlEscapeMode.unknown]);
|
|
|
|
String convert(String text) {
|
|
var val = _convert(text, 0, text.length);
|
|
return val == null ? text : val;
|
|
}
|
|
|
|
/// Converts the substring of text from start to end.
|
|
///
|
|
/// Returns `null` if no changes were necessary, otherwise returns
|
|
/// the converted string.
|
|
String? _convert(String text, int start, int end) {
|
|
StringBuffer? result;
|
|
for (var i = start; i < end; i++) {
|
|
var ch = text[i];
|
|
String? replacement;
|
|
switch (ch) {
|
|
case '&':
|
|
replacement = '&';
|
|
break;
|
|
case '"':
|
|
if (mode.escapeQuot) replacement = '"';
|
|
break;
|
|
case "'":
|
|
if (mode.escapeApos) replacement = ''';
|
|
break;
|
|
case '<':
|
|
if (mode.escapeLtGt) replacement = '<';
|
|
break;
|
|
case '>':
|
|
if (mode.escapeLtGt) replacement = '>';
|
|
break;
|
|
case '/':
|
|
if (mode.escapeSlash) replacement = '/';
|
|
break;
|
|
}
|
|
if (replacement != null) {
|
|
result ??= StringBuffer();
|
|
if (i > start) result.write(text.substring(start, i));
|
|
result.write(replacement);
|
|
start = i + 1;
|
|
}
|
|
}
|
|
if (result == null) return null;
|
|
if (end > start) result.write(text.substring(start, end));
|
|
return result.toString();
|
|
}
|
|
|
|
StringConversionSink startChunkedConversion(Sink<String> sink) {
|
|
return _HtmlEscapeSink(this,
|
|
sink is StringConversionSink ? sink : StringConversionSink.from(sink));
|
|
}
|
|
}
|
|
|
|
class _HtmlEscapeSink extends StringConversionSink {
|
|
final HtmlEscape _escape;
|
|
final StringConversionSink _sink;
|
|
|
|
_HtmlEscapeSink(this._escape, this._sink);
|
|
|
|
void addSlice(String chunk, int start, int end, bool isLast) {
|
|
var val = _escape._convert(chunk, start, end);
|
|
if (val == null) {
|
|
_sink.addSlice(chunk, start, end, isLast);
|
|
} else {
|
|
_sink.add(val);
|
|
if (isLast) _sink.close();
|
|
}
|
|
}
|
|
|
|
void close() {
|
|
_sink.close();
|
|
}
|
|
}
|