Skip to content

Commit

Permalink
Issue 4 add base64url encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark Lister committed Oct 7, 2014
1 parent 02b1658 commit 3e17910
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 11 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,25 @@ scala> res1.sameElements("ABCDEFG".getBytes)
res2: Boolean = true

```

####Base64 url

You can switch base64 encoding by exposing the encoding as an implicit parameter:

```scala> "+/+/+/+/".toByteArray
res5: Array[Byte] = Array(-5, -1, -65, -5, -1, -65)
scala> implicit val encoding = io.github.marklister.base64.Base64.base64Url
encoding: io.github.marklister.base64.Base64.B64Scheme = Vector(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -, _)
scala> res5.toBase64
res6: String = -_-_-_-_
```

And with the Base64 object already imported this simplifies to:

``` implicit val encoding = base64Url```

####Mix encodings

If you need to mix encodings then have a look at the tests for an example.
25 changes: 15 additions & 10 deletions src/main/scala/Base64.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,45 @@ import scala.annotation.tailrec
*
* The repo for this Base64 encoder lives at https://github.com/marklister/base64
* Please send your issues, suggestions and pull requests there.
*
*/

object Base64 {
private[this] val encodeTable: IndexedSeq[Char] = ('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ Seq('+', '/')
private[this] val decodeMap=collection.immutable.TreeMap(encodeTable.zipWithIndex : _*)
type B64Scheme= IndexedSeq[Char]
val base64: B64Scheme = ('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ Seq('+', '/')
val base64Url: B64Scheme = base64.dropRight(2) ++ Seq('-', '_')

private[this] val basic = Array(127, 127, 127).map(_.toByte) //to force only positive BigInts
private[this] val zero = Array(0, 0).map(_.toByte)

implicit class Encoder(b: Array[Byte]) {
def toBase64: String = {
implicit class Encoder(b: Array[Byte]) {
def toBase64 (implicit encTbl:B64Scheme=base64): String = {
@tailrec
def enc(z: BigInt,acc:Seq[Char]): Seq[Char] =
if (z == 0) acc
else enc(z / 64,encodeTable((z % 64).toInt)+:acc)
else enc(z / 64,encTbl((z % 64).toInt)+:acc)

val pad = (3 - b.length % 3) % 3
val bi = BigInt(basic ++ b ++ zero.take(pad))

(enc(bi,Seq.empty).drop(4).dropRight(pad) :+ "=" * pad).mkString
}
}

implicit class Decoder(s: String) {
lazy val cleanS = s.reverse.dropWhile(_ == '=').reverse
lazy val pad = s.length - cleanS.length

def toByteArray: Array[Byte] = {
def toByteArray(implicit encTbl:B64Scheme=base64): Array[Byte] = {
if (pad > 2 || s.length % 4 != 0) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s)
if (!cleanS.forall(encodeTable.contains(_))) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s)
if (!cleanS.forall(encTbl.contains(_))) throw new java.lang.IllegalArgumentException("Invalid Base64 String:" + s)
val decodeMap=collection.immutable.TreeMap(encTbl.zipWithIndex : _*)

(cleanS + "A" * pad)
.foldLeft(BigInt(127))((a, b) => a * 64 + decodeMap(b))
.toByteArray
.drop(1).dropRight(pad)
}
}
}
}

6 changes: 5 additions & 1 deletion src/test/scala/Base64Spec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,9 @@ import org.specs2.mutable.Specification
"aG9nZXBpeW9mb29iYXI=".toByteArray must beEqualTo("hogepiyofoobar".getBytes)
}
}

"+/+/+/+/ base64" should {
" be equivalent to -_-_-_-_ base64Url" in {
"+/+/+/+/".toByteArray.sameElements(("-_-_-_-_").toByteArray(base64Url)) must beTrue
}
}
}

0 comments on commit 3e17910

Please sign in to comment.