Skip to content

Commit

Permalink
Fix Base64Parsing for blocks and extra characters
Browse files Browse the repository at this point in the history
  • Loading branch information
rossabaker committed May 24, 2019
1 parent 8f28411 commit 1a7ea2d
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 14 deletions.
24 changes: 20 additions & 4 deletions parboiled/src/main/scala/org/parboiled2/Base64Parsing.scala
Expand Up @@ -27,28 +27,32 @@ trait Base64Parsing { this: Parser =>
/** /**
* Parses an RFC4045-encoded string and decodes it onto the value stack. * Parses an RFC4045-encoded string and decodes it onto the value stack.
*/ */
def rfc2045String: Rule1[Array[Byte]] = base64StringOrBlock(rfc2045Alphabet, rfc2045StringDecoder) def rfc2045String: Rule1[Array[Byte]] = base64(rfc2045Alphabet, Base64.rfc2045().fillChar, rfc2045StringDecoder)


/** /**
* Parses an RFC4045-encoded string potentially containing newlines and decodes it onto the value stack. * Parses an RFC4045-encoded string potentially containing newlines and decodes it onto the value stack.
*/ */
def rfc2045Block: Rule1[Array[Byte]] = base64StringOrBlock(rfc2045Alphabet, rfc2045BlockDecoder) def rfc2045Block: Rule1[Array[Byte]] = base64(rfc2045Alphabet, Base64.rfc2045().fillChar, rfc2045BlockDecoder)


/** /**
* Parses a org.parboiled2.util.Base64.custom()-encoded string and decodes it onto the value stack. * Parses a org.parboiled2.util.Base64.custom()-encoded string and decodes it onto the value stack.
*/ */
def base64CustomString: Rule1[Array[Byte]] = base64StringOrBlock(customAlphabet, customStringDecoder) def base64CustomString: Rule1[Array[Byte]] = base64(customAlphabet, Base64.custom().fillChar, customStringDecoder)


/** /**
* Parses a org.parboiled2.util.Base64.custom()-encoded string potentially containing newlines * Parses a org.parboiled2.util.Base64.custom()-encoded string potentially containing newlines
* and decodes it onto the value stack. * and decodes it onto the value stack.
*/ */
def base64CustomBlock: Rule1[Array[Byte]] = base64StringOrBlock(customAlphabet, customBlockDecoder) def base64CustomBlock: Rule1[Array[Byte]] = base64(customAlphabet, Base64.custom().fillChar, customBlockDecoder)


/** /**
* Parses a BASE64-encoded string with the given alphabet and decodes it onto the value * Parses a BASE64-encoded string with the given alphabet and decodes it onto the value
* stack using the given codec. * stack using the given codec.
*/ */
@deprecated(
"Does not work on padded blocks. Does not work on strings with trailing garbage. Use rfc2045String, rfc2045Block, base64CustomString, or base64CustomBlock instead.",
"2.1.7"
)
def base64StringOrBlock(alphabet: CharPredicate, decoder: Decoder): Rule1[Array[Byte]] = { def base64StringOrBlock(alphabet: CharPredicate, decoder: Decoder): Rule1[Array[Byte]] = {
val start = cursor val start = cursor
rule { rule {
Expand All @@ -60,6 +64,18 @@ trait Base64Parsing { this: Parser =>
} }
} }
} }

private def base64(alphabet: CharPredicate, fillChar: Char, decoder: Decoder): Rule1[Array[Byte]] = {
val start = cursor
rule {
oneOrMore(alphabet) ~ zeroOrMore(ch(fillChar)) ~ run {
decoder(input.sliceCharArray(start, cursor)) match {
case null => MISMATCH
case bytes => push(bytes)
}
} ~ EOI
}
}
} }


object Base64Parsing { object Base64Parsing {
Expand Down
34 changes: 24 additions & 10 deletions parboiled/src/test/scala/org/parboiled2/Base64ParsingSpec.scala
Expand Up @@ -35,8 +35,12 @@ object Base64ParsingSpec extends TestSuite {
"Base64Parsing" - { "Base64Parsing" - {
"enable parsing of RFC2045 Strings" - test("rfc2045String", Base64.rfc2045()) "enable parsing of RFC2045 Strings" - test("rfc2045String", Base64.rfc2045())
"enable parsing of RFC2045 Blocks" - test("rfc2045Block", Base64.rfc2045()) "enable parsing of RFC2045 Blocks" - test("rfc2045Block", Base64.rfc2045())
"enable parsing of custom-Base64 Strings" - test("base64CustomString", Base64.rfc2045()) "enable parsing of custom-Base64 Strings" - test("base64CustomString", Base64.custom())
"enable parsing of custom-Base64 Blocks" - test("base64CustomBlock", Base64.rfc2045()) "enable parsing of custom-Base64 Blocks" - test("base64CustomBlock", Base64.custom())
"reject RFC2045 Strings with trailing garbage" - testTrailingGarbage("rfc2045String", Base64.rfc2045())
"reject RFC2045 Blocks with trailing garbage" - testTrailingGarbage("rfc2045Block", Base64.rfc2045())
"reject custom-Base64 Strings with trailing garbage" - testTrailingGarbage("base64CustomString", Base64.custom())
"reject custom-Base64 Blocks with trailing garbage" - testTrailingGarbage("base64CustomBlock", Base64.custom())
} }


} }
Expand All @@ -48,18 +52,28 @@ object Base64ParsingSpec extends TestSuite {
"base64CustomBlock" "base64CustomBlock"
) )


def testParser(encoded: String) = new TestParser(encoded) with DynamicRuleHandler[TestParser, Array[Byte] :: HNil] {
type Result = String
def parser: TestParser = this
def ruleNotFound(ruleName: String): Result = "n/a"
def success(result: Array[Byte] :: HNil): Result = new String(result.head, UTF8)
def parseError(error: ParseError): Result = throw error
def failure(error: Throwable): Result = throw error
}

def test(ruleName: String, base64: Base64): Unit = def test(ruleName: String, base64: Base64): Unit =
(1 to 100).foreach { x => (1 to 100).foreach { x =>
val string = randomChars.take(x).mkString("") val string = randomChars.take(x).mkString("")
val encoded = base64.encodeToString(string getBytes UTF8, lineSep = false) val encoded = base64.encodeToString(string getBytes UTF8, lineSep = false)
val parser = new TestParser(encoded) with DynamicRuleHandler[TestParser, Array[Byte] :: HNil] { val parser = testParser(encoded)
type Result = String
def parser: TestParser = this
def ruleNotFound(ruleName: String): Result = "n/a"
def success(result: Array[Byte] :: HNil): Result = new String(result.head, UTF8)
def parseError(error: ParseError): Result = sys.error("unexpected parse error")
def failure(error: Throwable): Result = sys.error("unexpected parser exception")
}
dispatch(parser, ruleName) ==> string dispatch(parser, ruleName) ==> string
} }

def testTrailingGarbage(ruleName: String, base64: Base64): Unit =
(1 to 100).foreach { x =>
val string = randomChars.take(x).mkString("")
val encoded = base64.encodeToString(string getBytes UTF8, lineSep = false) + "!"
val parser = testParser(encoded)
intercept[ParseError] { dispatch(parser, ruleName) }
}
} }

0 comments on commit 1a7ea2d

Please sign in to comment.