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"
81 # assert "c3Rya\nW5n".decode_base64 == "string"
83 # REQUIRE: `length % 4 == 0`
84 private fun decode_base64
(length
: Int, padding
: nullable Byte): Bytes do
85 if padding
== null then padding
= '='.ascii
86 var inv
= once inverted_base64_chars
87 if length
== 0 then return new Bytes.empty
89 # Remove non-base64 chars
90 var bytes
= new Bytes.with_capacity
(length
)
91 for k
in [0 .. length
[ do
93 if inv
.has_key
(byte
) or byte
== padding
then bytes
.add
(byte
)
97 var steps
= length
/ 4
98 var result_length
= steps
* 3
100 var epos
= length
- 1
102 while epos
>= 0 and bytes
[epos
] == padding
do
107 if padding_len
!= 0 then steps
-= 1
108 if padding_len
== 1 then result_length
-= 1
109 if padding_len
== 2 then result_length
-= 2
111 var result
= new Bytes.with_capacity
(result_length
+ 1)
113 for s
in [0 .. steps
[ do
114 var c0
= inv
[bytes
[s
* 4]]
115 var c1
= inv
[bytes
[s
* 4 + 1]]
116 var c2
= inv
[bytes
[s
* 4 + 2]]
117 var c3
= inv
[bytes
[s
* 4 + 3]]
118 result
.add
(((c0
& 0b0011_1111u
8) << 2) | ((c1
& 0b0011_0000u
8) >> 4))
119 result
.add
(((c1
& 0b0000_1111u
8) << 4) | ((c2
& 0b0011_1100u
8) >> 2))
120 result
.add
(((c2
& 0b0000_0011u
8) << 6) | (c3
& 0b0011_1111u
8))
123 var last_start
= steps
* 4
124 if padding_len
== 1 then
125 var c0
= inv
[bytes
[last_start
]]
126 var c1
= inv
[bytes
[last_start
+ 1]]
127 var c2
= inv
[bytes
[last_start
+ 2]]
128 result
.add
(((c0
& 0b0011_1111u
8) << 2) | ((c1
& 0b0011_0000u
8) >> 4))
129 result
.add
(((c1
& 0b0000_1111u
8) << 4) | ((c2
& 0b0011_1100u
8) >> 2))
130 else if padding_len
== 2 then
131 var c0
= inv
[bytes
[last_start
]]
132 var c1
= inv
[bytes
[last_start
+ 1]]
133 result
.add
(((c0
& 0b0011_1111u
8) << 2) | ((c1
& 0b0011_0000u
8) >> 4))
142 # Encodes the receiver string to base64 using a custom padding character.
144 # If using the default padding character `=`, see `encode_base64`.
145 fun encode_base64
(padding
: nullable Byte): Bytes
147 return items
.encode_base64
(length
, padding
)
150 # Decodes the receiver string to base64 using a custom padding character.
152 # Default padding character `=`
153 fun decode_base64
(padding
: nullable Byte) : Bytes
155 return items
.decode_base64
(length
, padding
)
161 # Encodes the receiver string to base64 using a custom padding character.
163 # If using the default padding character `=`, see `encode_base64`.
164 fun encode_base64
(padding
: nullable Byte): String
166 return to_cstring
.encode_base64
(bytelen
, padding
).to_s
169 # Decodes the receiver string to base64 using a custom padding character.
171 # Default padding character `=`
172 fun decode_base64
(padding
: nullable Byte) : String
174 return to_cstring
.decode_base64
(bytelen
, padding
).to_s