Gemenon.jl/src/types.jl

137 lines
4.1 KiB
Julia

struct Status
major::Char
minor::Char
meta::String
# FIXME: error codes
function Status(ma::Char, mi::Char, meta::String)
'1' <= ma <= '6' || throw("Invalid")
'0' <= mi <= '9' || throw("Invalid")
if length(meta) > 1024
throw("Invalid")
end
new(ma, mi, meta)
end
end
Status(code::String, meta::String) = Status(code[1], code[2], meta)
Status(code::String) = Status(code[1], code[2], "")
Status(code::Int, meta::String) = Status(string(code), meta)
Status(code::Int) = Status(string(code))
struct Response
status::Status
body::String
end
Response(s::Status, b::Vector{String}) = Response(s, join(b, "\n"))
struct Request
protocol::Union{String,Nothing}
host::Union{String,Nothing}
port::Union{Int32,Nothing}
path::Union{Array{String},Nothing}
query::Union{String,Nothing}
valid::Bool
raw::Union{String,Vector{UInt8}}
"Assemble a Request object based on raw string, according to gemini specifications
(v0.14.2, July 2nd 2020)
[protocol://]host[:port][/path][?query]
Protocol: optional, default gemini://
Port: optional, default 1965
Host: required
Path: optional
Query: optional
spaces encoded with: %20"
end
function Request(data::String)
reg = r"""
((?P<protocol>[^:/]+)://)? # optional protocol
(?P<host>[^:/]+) # required host
(:(?P<port>[0-9]{1,5}))? # optional port (will match impossible 65000+ ports)
(/(?P<path>[^\?]+))? # optional path
(\?(?P<query>[^\?]+))? # optional query
"""x
s = endswith(data, "\r\n") ? data[1:end-2] : data
m = match(reg, s)
if isnothing(m)
Request(nothing, nothing, nothing, nothing, nothing, false, data)
else
Request(something(m["protocol"], "gemini"),
m["host"], parse(Int, something(m["port"], "1965")),
# normpath prevents "./path" being different from "path", and
# resolves also "../". The only problem is that "./" is
# transformed to ".", so we filter it. the raw rapresentation
# is still there, if needed
isnothing(m["path"]) ? nothing : filter!(!=("."), split(normpath(m["path"]), "/")),
isnothing(m["query"]) ? nothing : unescape(m["query"]),
true, data)
end
end
"unescape(percent_encoded_uri)::String
Replace %xx escaped chars with their single-character equivalent.
Should never throw errors"
function unescape(str)::String
occursin("%", str) || return str
len = length(str)
# Maximum length is the input string length.
# Should be len - 3 (since we already know there's at least one percent sign)
# but what if the percent is at the end? We can save only one char
# as "string%" -> saves 1, "string%2" saves 2. But %-- occupies all three
unescaped = Vector{UInt8}(undef, len)
pos = 1
chars = 0
while pos <= len
let c = str[pos]
chars += 1
if c == '%' && pos+2 <= len &&
# Keep only alphanumeric chars
'0' <= str[pos+1] <= 'f' &&
'0' <= str[pos+2] <= 'f'
unescaped[chars] = parse(UInt8, str[pos+1:pos+2], base = 16)
pos += 3
else
unescaped[chars] = c
pos += 1
end
end
end
return String(unescaped[1:chars])
end
import Base.show
function show(io::IO, r::Request)
# Useful for debugging
r.valid || begin
println(r.raw)
print(io, "[!invalid] ", String(r.raw))
return
end
printstyled(io, r.protocol, color = :red)
print(io, "://")
printstyled(io, r.host, color = :black, bold = :true)
print(io, ":")
printstyled(io, r.port, color = :blue)
if !isnothing(r.path)
print(io, "/")
printstyled(io, join(r.path, "/"), color = :green)
end
if !isnothing(r.query)
print(io, "?")
printstyled(io, r.query, color = :yellow)
end
end
struct Connection
server::Sockets.TCPServer
client::SSLClient
end