Skip to content

Commit ff34308

Browse files
committed
Scan node tags correctly
* Prefer scanning a verbatim tag in its own scope. This ensures we never mix/taint its heuristics with that of a normal tag. * Always return whether a handle is named tag handle in `_scanTagHandle`. Named tags. * Ensure named tags never have empty suffixes. * Indicate whether a `TagToken` is verbatim.
1 parent 9e80c8b commit ff34308

File tree

2 files changed

+109
-38
lines changed

2 files changed

+109
-38
lines changed

pkgs/yaml/lib/src/scanner.dart

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ class Scanner {
927927
Token _scanTagDirectiveValue(LineScannerState start) {
928928
_skipBlanks();
929929

930-
final handle = _scanTagHandle(directive: true);
930+
final handle = _scanTagHandle(directive: true).tagHandle;
931931
if (!_isBlank) {
932932
throw YamlException('Expected whitespace.', _scanner.emptySpan);
933933
}
@@ -938,7 +938,7 @@ class Scanner {
938938
///
939939
/// See: https://yaml.org/spec/1.2.2/#6822-tag-prefixes
940940
var prefix = _scanner.peekChar() == EXCLAMATION
941-
? _scanTagHandle(directive: true, isPrefix: true)
941+
? _scanTagHandle(directive: true, isGlobalTagPrefix: true).tagHandle
942942
: '';
943943

944944
prefix += _scanTagUri(); // Readability's sake
@@ -994,56 +994,121 @@ class Scanner {
994994

995995
/// Scans a [TokenType.tag] token.
996996
Token _scanTag() {
997-
String? handle;
998-
String suffix;
999-
var start = _scanner.state;
997+
final start = _scanner.state;
1000998

1001999
// Check if the tag is in the canonical form.
1002-
if (_scanner.peekChar(1) == LEFT_ANGLE) {
1003-
// Eat '!<'.
1004-
_scanner.readChar();
1005-
_scanner.readChar();
1000+
if (_scanner.peekChar(1) == LEFT_ANGLE) return _scanVerbatimTag(start);
10061001

1007-
handle = '';
1008-
suffix = _scanTagUri();
1002+
// The tag has either the '!suffix' or the '!handle!suffix' form.
10091003

1010-
_scanner.expect('>');
1011-
} else {
1012-
// The tag has either the '!suffix' or the '!handle!suffix' form.
1004+
// First, try to scan a handle.
1005+
final (:tagHandle, :isNamed) = _scanTagHandle();
10131006

1014-
// First, try to scan a handle.
1015-
handle = _scanTagHandle();
1007+
String? handle = tagHandle;
1008+
var suffix = '';
10161009

1017-
if (handle.length > 1 && handle.startsWith('!') && handle.endsWith('!')) {
1018-
suffix = _scanTagUri(flowSeparators: false);
1019-
} else {
1020-
suffix = _scanTagUri(head: handle, flowSeparators: false);
1010+
if (isNamed) {
1011+
suffix = _scanTagUri(flowSeparators: false);
10211012

1022-
// There was no explicit handle.
1023-
if (suffix.isEmpty) {
1024-
// This is the special '!' tag.
1025-
handle = null;
1026-
suffix = '!';
1027-
} else {
1028-
handle = '!';
1029-
}
1013+
/// Named tag handles cannot have an empty tag suffix.
1014+
///
1015+
/// c-ns-shorthand-tag ::=
1016+
/// c-tag-handle
1017+
/// ns-tag-char+
1018+
///
1019+
/// See: https://yaml.org/spec/1.2.2/#691-node-tags
1020+
if (suffix.isEmpty) {
1021+
throw YamlException(
1022+
'Expected a non-empty shorthand suffix',
1023+
_scanner.spanFrom(start),
1024+
);
1025+
}
1026+
} else {
1027+
suffix = _scanTagUri(head: handle, flowSeparators: false);
1028+
1029+
// There was no explicit handle.
1030+
if (suffix.isEmpty) {
1031+
// This is the special '!' tag.
1032+
handle = null;
1033+
suffix = '!';
1034+
} else {
1035+
handle = '!'; // Not named.
10301036
}
10311037
}
10321038

10331039
// libyaml insists on whitespace after a tag, but example 7.2 indicates
10341040
// that it's not required: http://yaml.org/spec/1.2/spec.html#id2786720.
1041+
return TagToken(
1042+
_scanner.spanFrom(start),
1043+
handle,
1044+
suffix,
1045+
isVerbatim: false,
1046+
);
1047+
}
1048+
1049+
/// Scans a canonical [TokenType.tag] token whose [start] position is
1050+
/// provided by [_scanTag].
1051+
TagToken _scanVerbatimTag(LineScannerState start) {
1052+
// Eat '!<'.
1053+
final buffer = StringBuffer()
1054+
..writeCharCode(_scanner.readChar())
1055+
..writeCharCode(_scanner.readChar());
1056+
1057+
var tagUri = '';
10351058

1036-
return TagToken(_scanner.spanFrom(start), handle, suffix);
1059+
if (_scanner.peekChar() == EXCLAMATION) {
1060+
tagUri = _scanTagHandle(isVerbatimTag: true).tagHandle; // !<!foo>
1061+
1062+
if (tagUri == '!') {
1063+
throw YamlException(
1064+
'A non-specific tag cannot be declared as a verbatim tag',
1065+
_scanner.spanFrom(start),
1066+
);
1067+
}
1068+
} else {
1069+
tagUri = _scanTagUri(); // !<foo:uri>
1070+
1071+
// Expect !<foo:uri> to be !<tag:yaml.org,2002:*>
1072+
switch (tagUri.replaceFirst('tag:yaml.org,2002:', '')) {
1073+
case 'map' || 'seq' || 'str' || 'null' || 'bool' || 'int' || 'float':
1074+
break;
1075+
1076+
default:
1077+
throw YamlException(
1078+
'Invalid tag uri used as a verbatim tag',
1079+
_scanner.spanFrom(start),
1080+
);
1081+
}
1082+
}
1083+
1084+
_scanner.expect('>');
1085+
buffer.write('$tagUri>');
1086+
1087+
return TagToken(
1088+
_scanner.spanFrom(start),
1089+
'',
1090+
buffer.toString(),
1091+
isVerbatim: true,
1092+
);
10371093
}
10381094

1039-
/// Scans a tag handle.
1095+
/// Scans a tag handle and explicitly indicates if the handle was a named
1096+
/// tag handle.
10401097
///
1041-
/// If [isPrefix] is `true`, the handle can never be a secondary or named
1042-
/// tag handle. Such handles cannot be used in a global tag's local tag
1098+
/// If [isGlobalTagPrefix] is `true`, the handle can never be a secondary or
1099+
/// named tag handle. Such handles cannot be used in a global tag's local tag
10431100
/// prefix.
10441101
///
1102+
/// If [isVerbatimTag] is `true`, `isNamed` will always be `false`. Verbatim
1103+
/// tags expect the next non-uri char to be `>`.
1104+
///
10451105
/// See: https://yaml.org/spec/1.2/spec.html#id2783273
1046-
String _scanTagHandle({bool directive = false, bool isPrefix = false}) {
1106+
({bool isNamed, String tagHandle}) _scanTagHandle({
1107+
bool directive = false,
1108+
bool isGlobalTagPrefix = false,
1109+
bool isVerbatimTag = false,
1110+
}) {
1111+
var named = false;
10471112
final start = _scanner.state;
10481113
_scanner.expect('!');
10491114

@@ -1053,7 +1118,7 @@ class Scanner {
10531118
// TODO: Do we really need this highlighted?
10541119
final char = _scanner.readCodePoint();
10551120

1056-
if (isPrefix) {
1121+
if (isGlobalTagPrefix) {
10571122
throw YamlException(
10581123
'A local tag used as a global tag prefix cannot have a secondary tag'
10591124
' handle',
@@ -1069,14 +1134,16 @@ class Scanner {
10691134
/// For directives, expect the "!" for a named tag. No other handle can
10701135
/// have a tag uri here. For a tag shorthand anywhere else, this needs to
10711136
/// be a separation space (tab included) or line break or nothing.
1072-
if (buffer.length > 1 && !_isBlankOrEnd) {
1137+
///
1138+
/// Verbatim tags expect '>'.
1139+
if (!isVerbatimTag && buffer.length > 1 && !_isBlankOrEnd) {
10731140
_scanner.expect('!');
10741141

10751142
/// A tag directive doesn't allow a local tag with a named handle as a
10761143
/// local tag prefix.
10771144
///
10781145
/// See: https://yaml.org/spec/1.2/spec.html#id2783273
1079-
if (directive && isPrefix) {
1146+
if (directive && isGlobalTagPrefix) {
10801147
throw YamlException(
10811148
'A local tag used as a global tag prefix cannot have a named tag'
10821149
' handle',
@@ -1085,10 +1152,11 @@ class Scanner {
10851152
}
10861153

10871154
buffer.write('!');
1155+
named = true;
10881156
}
10891157
}
10901158

1091-
return buffer.toString();
1159+
return (isNamed: named, tagHandle: buffer.toString());
10921160
}
10931161

10941162
/// Scans a tag URI.

pkgs/yaml/lib/src/token.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ class TagToken implements Token {
102102
/// The tag suffix.
103103
final String suffix;
104104

105-
TagToken(this.span, this.handle, this.suffix);
105+
/// Whether this tag is declared in its canonical form
106+
final bool isVerbatim;
107+
108+
TagToken(this.span, this.handle, this.suffix, {required this.isVerbatim});
106109

107110
@override
108111
String toString() => 'TAG $handle $suffix';

0 commit comments

Comments
 (0)