class EL_BASE_64_DECODER
Decodes base 64 encoded text as type SPECIAL [NATURAL_8]
note
description: "[
Decodes [https://en.wikipedia.org/wiki/Base64 base 64 encoded text] as type ${SPECIAL [NATURAL_8]}
]"
author: "Finnian Reilly"
copyright: "Copyright (c) 2001-2022 Finnian Reilly"
contact: "finnian at eiffel hyphen loop dot com"
license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
date: "2024-01-20 19:18:24 GMT (Saturday 20th January 2024)"
revision: "13"
class
EL_BASE_64_DECODER
inherit
EL_BASE_64_ENCODE_DECODE
create
make
feature -- Access
data (base_64: STRING): SPECIAL [NATURAL_8]
local
i, i_final, skip_count: INTEGER; area: SPECIAL [CHARACTER]
do
create Result.make_empty ((base_64.count // 4 + 1) * 3)
area := base_64.area; i_final := base_64.count
from i := 0 until i = i_final loop
if i + 4 >= i_final then
-- no '=' padding at the end
create area.make_filled ('=', 4)
area.copy_data (base_64.area, i, 0, i_final - i)
skip_count := append (area, 0, Result)
i_final := i + 4 + skip_count -- exit loop
else
skip_count := append (area, i, Result)
end
i := i + 4 + skip_count
end
end
feature {NONE} -- Implementation
append (area: SPECIAL [CHARACTER]; offset: INTEGER; output: SPECIAL [NATURAL_8]): INTEGER
local
i, code, skip_count, l_count: INTEGER; c: CHARACTER; invalid_padding, done: BOOLEAN
area_4: like quartet
do
area_4 := quartet; area_4.wipe_out
from i := 0 until area_4.count = 4 or else done loop
c := area [offset + i + skip_count]
if c = '=' then
-- Note: RFC 2045 says "Because it is used only for padding
-- at the end of the data, the occurrence of any '=' characters
-- may be taken as evidence that the end of the data has
-- been reached (without truncation in transit)."
inspect i
when 0, 1 then
-- Invalid base64 stream: =*** OR *=**
invalid_padding := True
else
end
done := True
else
code := decoded (c)
inspect code
when Bad_character, White_space then
skip_count := skip_count + 1
else
area_4.extend (code)
i := i + 1
end
end
end
if not invalid_padding and then attached to_triplet (area_4) as area_3 then
l_count := area_3.count
from i := 0 until i = l_count loop
output.extend (area_3 [i].to_natural_8)
i := i + 1
end
end
Result := skip_count
end
decoded (c: CHARACTER_32): INTEGER
-- Decoded character;
-- Returns -1 if `c' is an ignorable character.
-- Returns -2 if `c' is an invalid character.
do
inspect c
when ' ', '%T', '%R', '%N' then
Result := White_space
when '=' then
Result := 0 -- treat padding character as 0 bits
when 'A' .. 'Z' then
Result := c.code - {ASCII}.Upper_a
when 'a' .. 'z' then
Result := 26 + c.code - {ASCII}.Lower_a
when '0' .. '9' then
Result := 52 + c.code - {ASCII}.zero
when '+' then
Result := 62
when '/' then
Result := 63
else
Result := Bad_character
end
ensure
valid_decoded_character: Result >= Bad_character and Result < 64
end
to_triplet (area_4: like quartet): like triplet
local
bitmap, sextet_count, octet_count: INTEGER
do
Result := triplet; Result.wipe_out
sextet_count := area_4.count; octet_count := sextet_count - 1
-- fill triplet in reverse from bitmap
Result.fill_with (0, 0, octet_count - 1)
-- load sextets into bitmap and align as octets
bitmap := new_area_bitmap (area_4, 6) |>> alignment_shift (sextet_count, octet_count)
-- write `bitmap' octets into `Result'
fill_area (Result, octet_count, bitmap, 8, Octet_mask)
end
feature {NONE} -- Constants
White_space: INTEGER = -1
Bad_character: INTEGER = -2
Octet_mask: INTEGER = 0xFF
end