1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Offers the base 64 encoding and decoding algorithms
20 redef class NativeString
21 # Alphabet used by the base64 algorithm
22 private fun base64_chars
: SequenceRead[Byte]
24 return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".bytes
27 # Reversed alphabet for base64
28 private fun inverted_base64_chars
: HashMap[Byte, Byte]
30 var inv_base64_chars
= new HashMap[Byte, Byte]
31 var l
= base64_chars
.length
33 inv_base64_chars
[base64_chars
[k
]] = k
.to_b
35 return inv_base64_chars
38 # Encodes `self` to base64.
40 # By default, uses "=" for padding.
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
)
53 for s
in [0 .. steps
[ do
54 var ind
= ((self[in_off
] & 0b1111_1100u
8) >> 2).to_i
55 result
.add base64_bytes
[ind
]
56 ind
= ((self[in_off
] & 0b0000_0011u
8) << 4).to_i
| ((self[in_off
+ 1] & 0b1111_0000u
8) >> 4).to_i
57 result
.add base64_bytes
[ind
]
58 ind
= ((self[in_off
+ 1] & 0b0000_1111u
8) << 2).to_i
| ((self[in_off
+ 2] & 0b1100_0000u
8) >> 6).to_i
59 result
.add base64_bytes
[ind
]
60 ind
= (self[in_off
+ 2] & 0b0011_1111u
8).to_i
61 result
.add base64_bytes
[ind
]
64 if bytes_in_last_step
== 1 then
65 result
.add base64_bytes
[((self[in_off
] & 0b1111_1100u
8) >> 2).to_i
]
66 result
.add base64_bytes
[((self[in_off
] & 0b0000_0011u
8) << 4).to_i
]
67 else if bytes_in_last_step
== 2 then
68 result
.add base64_bytes
[((self[in_off
] & 0b1111_1100u
8) >> 2).to_i
]
69 result
.add base64_bytes
[(((self[in_off
] & 0b0000_0011u
8) << 4) | ((self[in_off
+ 1] & 0b1111_0000u
8) >> 4)).to_i
]
70 result
.add base64_bytes
[((self[in_off
+ 1] & 0b0000_1111u
8) << 2).to_i
]
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
78 # Decodes `self` from base64
80 # assert "c3RyaW5n".decode_base64 == "string"
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"
90 var steps
= length
/ 4
91 var result_length
= steps
* 3
95 while epos
>= 0 and bytes
[epos
] == padding
do
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
104 var result
= new Bytes.with_capacity
(result_length
+ 1)
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_1111u
8) << 2) | ((c1
& 0b0011_0000u
8) >> 4))
112 result
.add
(((c1
& 0b0000_1111u
8) << 4) | ((c2
& 0b0011_1100u
8) >> 2))
113 result
.add
(((c2
& 0b0000_0011u
8) << 6) | (c3
& 0b0011_1111u
8))
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_1111u
8) << 2) | ((c1
& 0b0011_0000u
8) >> 4))
122 result
.add
(((c1
& 0b0000_1111u
8) << 4) | ((c2
& 0b0011_1100u
8) >> 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_1111u
8) << 2) | ((c1
& 0b0011_0000u
8) >> 4))
135 # Encodes the receiver string to base64 using a custom padding character.
137 # If using the default padding character `=`, see `encode_base64`.
138 fun encode_base64
(padding
: nullable Byte): Bytes
140 return items
.encode_base64
(length
, padding
)
143 # Decodes the receiver string to base64 using a custom padding character.
145 # Default padding character `=`
146 fun decode_base64
(padding
: nullable Byte) : Bytes
148 return items
.decode_base64
(length
, padding
)
154 # Encodes the receiver string to base64 using a custom padding character.
156 # If using the default padding character `=`, see `encode_base64`.
157 fun encode_base64
(padding
: nullable Byte): String
159 return to_cstring
.encode_base64
(bytelen
, padding
).to_s
162 # Decodes the receiver string to base64 using a custom padding character.
164 # Default padding character `=`
165 fun decode_base64
(padding
: nullable Byte) : String
167 return to_cstring
.decode_base64
(bytelen
, padding
).to_s