Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new Base64 encoder and decoder #24285

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions doc/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ if Sys.iswindows()
cp_q("../stdlib/Test/docs/src/index.md", "src/stdlib/test.md")
cp_q("../stdlib/Mmap/docs/src/index.md", "src/stdlib/mmap.md")
cp_q("../stdlib/SharedArrays/docs/src/index.md", "src/stdlib/sharedarrays.md")
cp_q("../stdlib/Base64/docs/src/index.md", "src/stdlib/base64.md")
else
symlink_q("../../../stdlib/DelimitedFiles/docs/src/index.md", "src/stdlib/delimitedfiles.md")
symlink_q("../../../stdlib/Test/docs/src/index.md", "src/stdlib/test.md")
symlink_q("../../../stdlib/Mmap/docs/src/index.md", "src/stdlib/mmap.md")
symlink_q("../../../stdlib/SharedArrays/docs/src/index.md", "src/stdlib/sharedarrays.md")
symlink_q("../../../stdlib/Base64/docs/src/index.md", "src/stdlib/base64.md")
end

const PAGES = [
Expand Down Expand Up @@ -99,6 +101,7 @@ const PAGES = [
"stdlib/profile.md",
"stdlib/stacktraces.md",
"stdlib/simd-types.md",
"stdlib/base64.md",
],
"Developer Documentation" => [
"devdocs/reflection.md",
Expand Down Expand Up @@ -133,11 +136,11 @@ const PAGES = [
],
]

using DelimitedFiles, Test, Mmap, SharedArrays
using DelimitedFiles, Test, Mmap, SharedArrays, Base64

makedocs(
build = joinpath(pwd(), "_build/html/en"),
modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays],
modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays, Base64],
clean = false,
doctest = "doctest" in ARGS,
linkcheck = "linkcheck" in ARGS,
Expand Down
8 changes: 8 additions & 0 deletions stdlib/Base64/docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Base64

```@docs
Base64.Base64EncodePipe
Base64.base64encode
Base64.Base64DecodePipe
Base64.base64decode
```
26 changes: 26 additions & 0 deletions stdlib/Base64/src/Base64.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

module Base64

#=
export
Base64EncodePipe,
base64encode,
Base64DecodePipe,
base64decode
=#

# Base64EncodePipe is a pipe-like IO object, which converts into base64 data
# sent to a stream. (You must close the pipe to complete the encode, separate
# from closing the target stream). We also have a function base64encode(f,
# args...) which works like sprint except that it produces base64-encoded data,
# along with base64encode(args...) which is equivalent to base64encode(write,
# args...), to return base64 strings. A Base64DecodePipe object can be used to
# decode base64-encoded data read from a stream , while function base64decode is
# useful for decoding strings

include("buffer.jl")
include("encode.jl")
include("decode.jl")

end
38 changes: 38 additions & 0 deletions stdlib/Base64/src/buffer.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# Data buffer for pipes.
mutable struct Buffer
data::Vector{UInt8}
ptr::Ptr{UInt8}
size::Int

function Buffer(bufsize)
data = Vector{UInt8}(bufsize)
return new(data, pointer(data), 0)
end
end

Base.empty!(buffer::Buffer) = buffer.size = 0
Base.getindex(buffer::Buffer, i::Integer) = unsafe_load(buffer.ptr, i)
Base.setindex!(buffer::Buffer, v::UInt8, i::Integer) = unsafe_store!(buffer.ptr, v, i)
Base.endof(buffer::Buffer) = buffer.size
Base.pointer(buffer::Buffer) = buffer.ptr
capacity(buffer::Buffer) = Int(pointer(buffer.data, endof(buffer.data) + 1) - buffer.ptr)

function consumed!(buffer::Buffer, n::Integer)
@assert n ≤ buffer.size
buffer.ptr += n
buffer.size -= n
end

function read_to_buffer(io::IO, buffer::Buffer)
offset = buffer.ptr - pointer(buffer.data)
copy!(buffer.data, 1, buffer.data, offset, buffer.size)
buffer.ptr = pointer(buffer.data) + buffer.size
if !eof(io)
n = min(nb_available(io), capacity(buffer) - buffer.size)
unsafe_read(io, buffer.ptr + buffer.size, n)
buffer.size += n
end
return
end
217 changes: 217 additions & 0 deletions stdlib/Base64/src/decode.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# Generate decode table.
const BASE64_CODE_END = 0x40
const BASE64_CODE_PAD = 0x41
const BASE64_CODE_IGN = 0x42
const BASE64_DECODE = fill(BASE64_CODE_IGN, 256)
for (i, c) in enumerate(BASE64_ENCODE)
BASE64_DECODE[Int(c)+1] = UInt8(i - 1)
end
BASE64_DECODE[Int(encodepadding())+1] = BASE64_CODE_PAD
decode(x::UInt8) = @inbounds return BASE64_DECODE[x + 1]

"""
Base64DecodePipe(istream)

Return a new read-only I/O stream, which decodes base64-encoded data read from
`istream`.

# Examples
```jldoctest
julia> io = IOBuffer();

julia> iob64_decode = Base64DecodePipe(io);

julia> write(io, "SGVsbG8h")
8

julia> seekstart(io);

julia> String(read(iob64_decode))
"Hello!"
```
"""
struct Base64DecodePipe <: IO
io::IO
buffer::Buffer
rest::Vector{UInt8}

function Base64DecodePipe(io::IO)
buffer = Buffer(512)
return new(io, buffer, UInt8[])
end
end

function Base.unsafe_read(pipe::Base64DecodePipe, ptr::Ptr{UInt8}, n::UInt)
p = read_until_end(pipe, ptr, n)
if p < ptr + n
throw(EOFError())
end
return nothing
end

# Read and decode as much data as possible.
function read_until_end(pipe::Base64DecodePipe, ptr::Ptr{UInt8}, n::UInt)
p = ptr
p_end = ptr + n
while !isempty(pipe.rest) && p < p_end
unsafe_store!(p, shift!(pipe.rest))
p += 1
end

buffer = pipe.buffer
i = 0
b1 = b2 = b3 = b4 = BASE64_CODE_IGN
while true
if b1 < 0x40 && b2 < 0x40 && b3 < 0x40 && b4 < 0x40 && p + 2 < p_end
# fast path to decode
unsafe_store!(p , b1 << 2 | b2 >> 4)
unsafe_store!(p + 1, b2 << 4 | b3 >> 2)
unsafe_store!(p + 2, b3 << 6 | b4 )
p += 3
else
i, p, ended = decode_slow(b1, b2, b3, b4, buffer, i, pipe.io, p, p_end - p, pipe.rest)
if ended
break
end
end
if p < p_end
if i + 4 ≤ endof(buffer)
b1 = decode(buffer[i+1])
b2 = decode(buffer[i+2])
b3 = decode(buffer[i+3])
b4 = decode(buffer[i+4])
i += 4
else
consumed!(buffer, i)
read_to_buffer(pipe.io, buffer)
i = 0
b1 = b2 = b3 = b4 = BASE64_CODE_IGN
end
else
break
end
end
consumed!(buffer, i)

return p
end

function Base.read(pipe::Base64DecodePipe, ::Type{UInt8})
if isempty(pipe.rest)
unsafe_read(pipe, convert(Ptr{UInt8}, C_NULL), 0)
if isempty(pipe.rest)
throw(EOFError())
end
end
return shift!(pipe.rest)
end

function Base.readbytes!(pipe::Base64DecodePipe, data::AbstractVector{UInt8}, nb::Integer=length(data))
filled::Int = 0
while filled < nb && !eof(pipe)
if length(data) == filled
resize!(data, min(length(data) * 2, nb))
end
p = pointer(data, filled + 1)
p_end = read_until_end(pipe, p, UInt(min(length(data), nb) - filled))
filled += p_end - p
end
resize!(data, filled)
return filled
end

Base.eof(pipe::Base64DecodePipe) = isempty(pipe.rest) && eof(pipe.io)
Base.close(pipe::Base64DecodePipe) = nothing

# Decode data from (b1, b2, b3, b5, buffer, input) into (ptr, rest).
function decode_slow(b1, b2, b3, b4, buffer, i, input, ptr, n, rest)
# Skip ignore code.
while true
if b1 == BASE64_CODE_IGN
b1, b2, b3 = b2, b3, b4
elseif b2 == BASE64_CODE_IGN
b2, b3 = b3, b4
elseif b3 == BASE64_CODE_IGN
b3 = b4
elseif b4 == BASE64_CODE_IGN
# pass
else
break
end
if i + 1 ≤ endof(buffer)
b4 = decode(buffer[i+=1])
elseif !eof(input)
b4 = decode(read(input, UInt8))
else
b4 = BASE64_CODE_END
break
end
end

# Check the decoded quadruplet.
k = 0
if b1 < 0x40 && b2 < 0x40 && b3 < 0x40 && b4 < 0x40
k = 3
elseif b1 < 0x40 && b2 < 0x40 && b3 < 0x40 && b4 == BASE64_CODE_PAD
b4 = 0x00
k = 2
elseif b1 < 0x40 && b2 < 0x40 && b3 == b4 == BASE64_CODE_PAD
b3 = b4 = 0x00
k = 1
elseif b1 == b2 == b3 == BASE64_CODE_IGN && b4 == BASE64_CODE_END
b1 = b2 = b3 = b4 = 0x00
else
throw(ArgumentError("malformed base64 sequence"))
end

# Write output.
p::Ptr{UInt8} = ptr
p_end = ptr + n
function output(b)
if p < p_end
unsafe_store!(p, b)
p += 1
else
push!(rest, b)
end
end
k ≥ 1 && output(b1 << 2 | b2 >> 4)
k ≥ 2 && output(b2 << 4 | b3 >> 2)
k ≥ 3 && output(b3 << 6 | b4 )

return i, p, k == 0
end

"""
base64decode(string)

Decode the base64-encoded `string` and returns a `Vector{UInt8}` of the decoded
bytes.

See also [`base64encode`](@ref).

# Examples
```jldoctest
julia> b = base64decode("SGVsbG8h")
6-element Array{UInt8,1}:
0x48
0x65
0x6c
0x6c
0x6f
0x21

julia> String(b)
"Hello!"
```
"""
function base64decode(s)
b = IOBuffer(s)
try
return read(Base64DecodePipe(b))
finally
close(b)
end
end
Loading