Skip to content
Permalink
Browse files

Fix Base64Parsing for blocks and extra characters

  • Loading branch information...
rossabaker committed May 24, 2019
1 parent 8f28411 commit 1a7ea2de28b00ba38066ad09b92d014b311fdbc0
@@ -27,28 +27,32 @@ trait Base64Parsing { this: Parser =>
/**
* 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.
*/
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.
*/
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
* 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
* 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]] = {
val start = cursor
rule {
@@ -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 {
@@ -35,8 +35,12 @@ object Base64ParsingSpec extends TestSuite {
"Base64Parsing" - {
"enable parsing of RFC2045 Strings" - test("rfc2045String", 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 Blocks" - test("base64CustomBlock", Base64.rfc2045())
"enable parsing of custom-Base64 Strings" - test("base64CustomString", Base64.custom())
"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())
}

}
@@ -48,18 +52,28 @@ object Base64ParsingSpec extends TestSuite {
"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 =
(1 to 100).foreach { x =>
val string = randomChars.take(x).mkString("")
val encoded = base64.encodeToString(string getBytes UTF8, lineSep = false)
val parser = 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 = sys.error("unexpected parse error")
def failure(error: Throwable): Result = sys.error("unexpected parser exception")
}
val parser = testParser(encoded)
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.
You can’t perform that action at this time.