lib: adds base64 module and according tests
authorAlexis Laferrière <alexis.laf@xymus.net>
Mon, 4 Feb 2013 19:31:20 +0000 (14:31 -0500)
committerAlexis Laferrière <alexis.laf@xymus.net>
Mon, 4 Feb 2013 19:31:20 +0000 (14:31 -0500)
Implements the base64 encode and decode algorithms in Nit.

Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

lib/base64.nit [new file with mode: 0644]
tests/sav/test_base64.sav [new file with mode: 0644]
tests/test_base64.nit [new file with mode: 0644]

diff --git a/lib/base64.nit b/lib/base64.nit
new file mode 100644 (file)
index 0000000..8a718eb
--- /dev/null
@@ -0,0 +1,136 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Offers the base 64 encoding and decoding algorithms
+module base64
+
+redef class String
+
+       # Alphabet used by the base64 algorithm
+       private fun base64_chars : String
+       do
+               return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+       end
+       private fun inverted_base64_chars : HashMap[Char,Int]
+       do
+               var inv_base64_chars = new HashMap[Char,Int]
+               for k in [0..base64_chars.length[ do
+                       inv_base64_chars[ base64_chars[k] ] = k
+               end
+               return inv_base64_chars
+       end
+
+       # Encodes the receiver string to base64.
+       # By default, uses "=" for padding.
+       fun encode_base64 : String do return encode_base64_custom_padding( '=' )
+       fun encode_base64_custom_padding( padding : Char ) : String
+       do
+               var base64_chars = once base64_chars
+               var length = length
+
+               var steps = length / 3
+               var chars_in_last_step = length % 3
+               var result_length = steps*4
+               if chars_in_last_step > 0 then result_length += 4
+               var result = (padding.to_s*result_length).to_cstring
+
+               var mask_6bit = 63
+
+               for s in [0..steps[ do
+                       var e = 0
+                       for ss in [0..3[ do
+                               e += self[s*3+ss].ascii.lshift((2-ss)*8)
+                       end
+                       for ss in [0..4[ do
+                               result[s*4+3-ss] = base64_chars[ e.rshift(ss*6).bin_and( mask_6bit ) ]
+                       end
+               end
+
+               if chars_in_last_step == 1 then
+                       var e = self[length-1].ascii.lshift(16)
+                       for ss in [0..2[ do
+                               result[steps*4+1-ss] = base64_chars[ e.rshift((ss+2)*6).bin_and( mask_6bit ) ]
+                       end
+               else if chars_in_last_step == 2 then
+                       var e = self[length-2].ascii.lshift(16) +
+                               self[length-1].ascii.lshift(8)
+                       for ss in [0..3[ do
+                               result[steps*4+2-ss] = base64_chars[ e.rshift((ss+1)*6).bin_and( mask_6bit ) ]
+                       end
+               end
+
+               return new String.from_cstring( result )
+       end
+
+       # Decodes the receiver string from base64.
+       # By default, uses "=" for padding.
+       fun decode_base64 : String do return decode_base64_custom_padding( '=' )
+       fun decode_base64_custom_padding( padding : Char ) : String
+       do
+               var inverted_base64_chars = once inverted_base64_chars
+               var length = length
+               assert length % 4 == 0 else print "base64::decode_base64 only supports strings of length multiple of 4"
+
+               var steps = length / 4
+               var result_length = steps*3
+
+               var padding_begin = padding.search_index_in( self, 0 )
+               var padding_count : Int
+               if padding_begin == -1 then
+                       padding_count = 0
+               else
+                       padding_count = length - padding_begin
+                       steps -= 1
+                       result_length -= padding_count
+               end
+
+               var result = ("#"*result_length).to_cstring
+
+               var mask_8bit = 255
+
+               for s in [0..steps[ do
+                       var e = 0
+                       for ss in [0..4[ do
+                               e += inverted_base64_chars[self[s*4+ss]].lshift((3-ss)*6)
+                       end
+
+                       for ss in [0..3[ do
+                               result[s*3+ss] = e.rshift((2-ss)*8).bin_and( mask_8bit ).ascii
+                       end
+               end
+
+               var s = steps
+               if padding_count == 1 then
+                       var e = 0
+                       for ss in [0..3[ do
+                               e += inverted_base64_chars[self[s*4+ss]].lshift((3-ss)*6)
+                       end
+
+                       for ss in [0..2[ do
+                               result[s*3+ss] = e.rshift((2-ss)*8).bin_and( mask_8bit ).ascii
+                       end
+               else if padding_count == 2 then
+                       var e = 0
+                       for ss in [0..2[ do
+                               e += inverted_base64_chars[self[s*4+ss]].lshift((3-ss)*6)
+                       end
+
+                       result[s*3] = e.rshift(2*8).bin_and( mask_8bit ).ascii
+               end
+
+               return new String.from_cstring( result )
+       end
+end
diff --git a/tests/sav/test_base64.sav b/tests/sav/test_base64.sav
new file mode 100644 (file)
index 0000000..01698e8
--- /dev/null
@@ -0,0 +1,14 @@
+:
+f:      Zg==
+fo:     Zm8=
+foo:    Zm9v
+foob:   Zm9vYg==
+fooba:  Zm9vYmE=
+foobar: Zm9vYmFy
+:
+Zg==:     f
+Zm8=:     fo
+Zm9v:     foo
+Zm9vYg==: foob
+Zm9vYmE=: fooba
+Zm9vYmFy: foobar
diff --git a/tests/test_base64.nit b/tests/test_base64.nit
new file mode 100644 (file)
index 0000000..af82e47
--- /dev/null
@@ -0,0 +1,33 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+
+print ":" + "".encode_base64
+print "f:      " + "f".encode_base64
+print "fo:     " + "fo".encode_base64
+print "foo:    " + "foo".encode_base64
+print "foob:   " + "foob".encode_base64
+print "fooba:  " + "fooba".encode_base64
+print "foobar: " + "foobar".encode_base64
+
+print ":" + "".decode_base64
+print "Zg==:     " + "Zg==".decode_base64
+print "Zm8=:     " + "Zm8=".decode_base64
+print "Zm9v:     " + "Zm9v".decode_base64
+print "Zm9vYg==: " + "Zm9vYg==".decode_base64
+print "Zm9vYmE=: " + "Zm9vYmE=".decode_base64
+print "Zm9vYmFy: " + "Zm9vYmFy".decode_base64