Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not accept invalid sequence as mapping value #194

Merged
merged 3 commits into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions core/shared/src/main/scala/org/virtuslab/yaml/Range.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ final case class Range(
end: Option[Position] = None
) {
def errorMsg: String = {
val msg = input(start.line)
val spaces = start.column
val circumflexes = msg.length - spaces
val msg = input(start.line)
val spaces = start.column
s"""|$msg
|${" " * spaces}^""".stripMargin
}
Expand Down
7 changes: 7 additions & 0 deletions core/shared/src/main/scala/org/virtuslab/yaml/YamlError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,11 @@ object ScannerError {
|$obtained but expected got ${got.kind}
|${got.range.errorMsg}""".stripMargin
)

def from(range: Range, msg: String): ScannerError = ScannerError(
s"""|Error at line ${range.start.line}, column ${range.start.column}:
|${range.errorMsg}
|$msg
|""".stripMargin
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ case class ReaderCtx(reader: Reader) {
def isInFlowSequence: Boolean = flowSequenceLevel > 0
def isInFlowCollection: Boolean = isInFlowMapping || isInFlowSequence

def isInBlockCollection = !isInFlowCollection

def parseDocumentStart(indent: Int): List[Token] =
checkIndents(-1) ++ List(Token(DocumentStart, reader.range))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
if (!ctx.isInFlowCollection && ctx.indent < in.column) {
ctx.addIndent(in.column)
List(Token(SequenceStart, in.range))
} else if (ctx.isInBlockCollection && !ctx.isPlainKeyAllowed) {
throw ScannerError.from(in.range, "cannot start sequence")
} else {
in.skipCharacter()
ctx.popPotentialKeys() ++ List(Token(SequenceValue, in.range))
Expand All @@ -147,7 +149,9 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
val range = in.range
in.skipCharacter() // skip %

def parseYamlDirective() = { throw ScannerError("YAML directives are not supported yet.") }
def parseYamlDirective() = {
throw ScannerError.from(in.range, "YAML directives are not supported yet.")
}

def parseTagDirective() = {
def parseTagHandle() = {
Expand Down Expand Up @@ -178,7 +182,7 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
val sb = new StringBuilder
while (in.peek().exists(c => !c.isWhitespace)) sb.append(in.read())
TagPrefix.Global(sb.result())
case _ => throw ScannerError("Invalid tag prefix in TAG directive")
case _ => throw ScannerError.from(in.range, "Invalid tag prefix in TAG directive")
}
}

Expand All @@ -188,7 +192,8 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
val handle = parseTagHandle()
val prefix = parseTagPrefix()
List(Token(TokenKind.TagDirective(handle, prefix), range))
case _ => throw ScannerError("Tag handle in TAG directive should start with '!'")
case _ =>
throw ScannerError.from(in.range, "Tag handle in TAG directive should start with '!'")
}
}

Expand All @@ -199,7 +204,7 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
case Some('T') if in.peekN(3) == "TAG" =>
in.skipN(3)
parseTagDirective()
case _ => throw ScannerError("Unknown directive, expected YAML or TAG")
case _ => throw ScannerError.from(in.range, "Unknown directive, expected YAML or TAG")
}
}

Expand All @@ -215,14 +220,15 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
case Some('>') =>
sb.append(in.read())
sb.result()
case _ => throw ScannerError("Lacks '>' which closes verbatim tag attribute")
case _ => throw ScannerError.from(in.range, "Lacks '>' which closes verbatim tag attribute")
}
}

def parseTagSuffix(): String = {
val sb = new StringBuilder
while (in.peek().exists(c => !invalidChars(c) && !c.isWhitespace)) sb.append(in.read())
if (in.peek().exists(c => invalidChars(c))) throw ScannerError("Invalid character in tag")
if (in.peek().exists(c => invalidChars(c)))
throw ScannerError.from(in.range, "Invalid character in tag")
UrlDecoder.decode(sb.result())
}

Expand All @@ -237,15 +243,15 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
while (in.peek().exists(c => !invalidChars(c) && !c.isWhitespace && c != '!'))
sb.append(in.read())
if (in.peek().exists(c => invalidChars(c)))
throw ScannerError("Invalid character in tag")
throw ScannerError.from(in.range, "Invalid character in tag")
in.peek() match {
case Some('!') =>
sb.insert(0, '!') // prepend already skipped exclamation mark
sb.append(in.read()) // append ending exclamation mark
TagValue.Shorthand(TagHandle.Named(sb.result()), parseTagSuffix())
case Some(' ') =>
TagValue.Shorthand(TagHandle.Primary, sb.result())
case _ => throw ScannerError("Invalid tag handle")
case _ => throw ScannerError.from(in.range, "Invalid tag handle")
}
}

Expand All @@ -260,7 +266,7 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
case Some(char) =>
val tagValue = parseShorthandTag(char)
Tag(tagValue)
case None => throw ScannerError("Input stream ended unexpectedly")
case None => throw ScannerError.from(in.range, "Input stream ended unexpectedly")
}

if (ctx.isPlainKeyAllowed) {
Expand Down Expand Up @@ -565,12 +571,12 @@ private[yaml] class Scanner(str: String) extends Tokenizer {
ctx.isPlainKeyAllowed = false

if (
!ctx.isInFlowCollection &&
ctx.isInBlockCollection &&
firstSimpleKey.range.end.exists(
_.line > firstSimpleKey.range.start.line
)
)
throw ScannerError.from("Not alowed here a mapping value", mappingValueToken)
throw ScannerError.from("Mapping value is not allowed", mappingValueToken)
else
maybeMappingStart ++ List(Token(MappingKey, in.range)) ++ potentialKeys :+ mappingValueToken
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,10 @@ class MappingSuite extends BaseYamlSuite {

assertEquals(yaml.events, Right(expectedEvents))
}

test("invalid sequence as mapping value") {
val yaml = """foo: - bar"""

assert(yaml.events.isLeft)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,6 @@ class TagSuite extends BaseYamlSuite {
|!invalid{}tag scalar
|""".stripMargin

assertEquals(yaml.events, Left(ScannerError("Invalid character in tag")))
assert(yaml.events.isLeft)
}
}