README: document nit_env.sh
[nit.git] / lib / base64.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Offers the base 64 encoding and decoding algorithms
18 module base64
19
20 redef class NativeString
21 # Alphabet used by the base64 algorithm
22 private fun base64_chars : SequenceRead[Byte]
23 do
24 return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".bytes
25 end
26
27 # Reversed alphabet for base64
28 private fun inverted_base64_chars : HashMap[Byte, Byte]
29 do
30 var inv_base64_chars = new HashMap[Byte, Byte]
31 var l = base64_chars.length
32 for k in [0 .. l[ do
33 inv_base64_chars[base64_chars[k]] = k.to_b
34 end
35 return inv_base64_chars
36 end
37
38 # Encodes `self` to base64.
39 #
40 # By default, uses "=" for padding.
41 #
42 # assert "string".encode_base64 == "c3RyaW5n"
43 private fun encode_base64(length: Int, padding: nullable Byte): Bytes do
44 var base64_bytes = once base64_chars
45 if padding == null then padding = '='.ascii
46 var steps = length / 3
47 var bytes_in_last_step = length % 3
48 var result_length = steps * 4
49 if bytes_in_last_step > 0 then result_length += 4
50 var result = new Bytes.with_capacity(result_length)
51
52 var in_off = 0
53 for s in [0 .. steps[ do
54 var ind = ((self[in_off] & 0b1111_1100u8) >> 2).to_i
55 result.add base64_bytes[ind]
56 ind = ((self[in_off] & 0b0000_0011u8) << 4).to_i | ((self[in_off + 1] & 0b1111_0000u8) >> 4).to_i
57 result.add base64_bytes[ind]
58 ind = ((self[in_off + 1] & 0b0000_1111u8) << 2).to_i | ((self[in_off + 2] & 0b1100_0000u8) >> 6).to_i
59 result.add base64_bytes[ind]
60 ind = (self[in_off + 2] & 0b0011_1111u8).to_i
61 result.add base64_bytes[ind]
62 in_off += 3
63 end
64 if bytes_in_last_step == 1 then
65 result.add base64_bytes[((self[in_off] & 0b1111_1100u8) >> 2).to_i]
66 result.add base64_bytes[((self[in_off] & 0b0000_0011u8) << 4).to_i]
67 else if bytes_in_last_step == 2 then
68 result.add base64_bytes[((self[in_off] & 0b1111_1100u8) >> 2).to_i]
69 result.add base64_bytes[(((self[in_off] & 0b0000_0011u8) << 4) | ((self[in_off + 1] & 0b1111_0000u8) >> 4)).to_i]
70 result.add base64_bytes[((self[in_off + 1] & 0b0000_1111u8) << 2).to_i]
71 end
72 var rempad = if bytes_in_last_step > 0 then 3 - bytes_in_last_step else 0
73 for i in [0 .. rempad[ do result.add padding
74
75 return result
76 end
77
78 # Decodes `self` from base64
79 #
80 # assert "c3RyaW5n".decode_base64 == "string"
81 #
82 # REQUIRE: `length % 4 == 0`
83 private fun decode_base64(length: Int, padding: nullable Byte): Bytes do
84 if padding == null then padding = '='.ascii
85 var inv = once inverted_base64_chars
86 if length == 0 then return new Bytes.empty
87 assert length % 4 == 0 else print "base64::decode_base64 only supports strings of length multiple of 4"
88
89 var bytes = self
90 var steps = length / 4
91 var result_length = steps * 3
92
93 var epos = length - 1
94 var padding_len = 0
95 while epos >= 0 and bytes[epos] == padding do
96 epos -= 1
97 padding_len += 1
98 end
99
100 if padding_len != 0 then steps -= 1
101 if padding_len == 1 then result_length -= 1
102 if padding_len == 2 then result_length -= 2
103
104 var result = new Bytes.with_capacity(result_length + 1)
105
106 for s in [0 .. steps[ do
107 var c0 = inv[bytes[s * 4]]
108 var c1 = inv[bytes[s * 4 + 1]]
109 var c2 = inv[bytes[s * 4 + 2]]
110 var c3 = inv[bytes[s * 4 + 3]]
111 result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4))
112 result.add (((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2))
113 result.add (((c2 & 0b0000_0011u8) << 6) | (c3 & 0b0011_1111u8))
114 end
115
116 var last_start = steps * 4
117 if padding_len == 1 then
118 var c0 = inv[bytes[last_start]]
119 var c1 = inv[bytes[last_start + 1]]
120 var c2 = inv[bytes[last_start + 2]]
121 result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4))
122 result.add (((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2))
123 else if padding_len == 2 then
124 var c0 = inv[bytes[last_start]]
125 var c1 = inv[bytes[last_start + 1]]
126 result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4))
127 end
128
129 return result
130 end
131 end
132
133 redef class Bytes
134
135 # Encodes the receiver string to base64 using a custom padding character.
136 #
137 # If using the default padding character `=`, see `encode_base64`.
138 fun encode_base64(padding: nullable Byte): Bytes
139 do
140 return items.encode_base64(length, padding)
141 end
142
143 # Decodes the receiver string to base64 using a custom padding character.
144 #
145 # Default padding character `=`
146 fun decode_base64(padding : nullable Byte) : Bytes
147 do
148 return items.decode_base64(length, padding)
149 end
150 end
151
152 redef class String
153
154 # Encodes the receiver string to base64 using a custom padding character.
155 #
156 # If using the default padding character `=`, see `encode_base64`.
157 fun encode_base64(padding: nullable Byte): String
158 do
159 return to_cstring.encode_base64(bytelen, padding).to_s
160 end
161
162 # Decodes the receiver string to base64 using a custom padding character.
163 #
164 # Default padding character `=`
165 fun decode_base64(padding : nullable Byte) : String
166 do
167 return to_cstring.decode_base64(bytelen, padding).to_s
168 end
169 end