Skip to content

Commit

Permalink
add Base64 module
Browse files Browse the repository at this point in the history
  • Loading branch information
bicycle1885 authored and JeffBezanson committed Oct 27, 2017
1 parent d4b80e3 commit 4a606f1
Show file tree
Hide file tree
Showing 7 changed files with 576 additions and 2 deletions.
7 changes: 5 additions & 2 deletions doc/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ if Sys.iswindows()
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/Profile/docs/src/index.md", "src/stdlib/profile.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/Profile/docs/src/index.md", "src/stdlib/profile.md")
symlink_q("../../../stdlib/Base64/docs/src/index.md", "src/stdlib/base64.md")
end

const PAGES = [
Expand Down Expand Up @@ -101,6 +103,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 @@ -135,11 +138,11 @@ const PAGES = [
],
]

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

makedocs(
build = joinpath(pwd(), "_build/html/en"),
modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays, Profile],
modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays, Profile, 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

0 comments on commit 4a606f1

Please sign in to comment.