util-soc-imp-url.lua / last modification: 2020-01-30 14:16
-- original file : url.lua
-- for more into : see util-soc.lua

local tonumber, tostring, type = tonumber, tostring, type

local gsub, sub, match, find, format, byte, char = string.gsub, string.sub, string.match, string.find, string.format, string.byte, string.char
local insert = table.insert

local socket = socket or require("socket")

local url = {
    _VERSION = "URL 1.0.3",
}

socket.url = url

function url.escape(s)
    return (gsub(s, "([^A-Za-z0-9_])", function(c)
        return format("%%%02x", byte(c))
    end))
end

local function make_set(t) -- table.tohash
    local s = { }
    for i=1,#t do
        s[t[i]] = true
    end
    return s
end

local segment_set = make_set {
    "-", "_", ".", "!", "~", "*", "'", "(",
    ")", ":", "@", "&", "=", "+", "$", ",",
}

local function protect_segment(s)
    return gsub(s, "([^A-Za-z0-9_])", function(c)
        if segment_set[c] then
            return c
        else
            return format("%%%02X", byte(c))
        end
    end)
end

function url.unescape(s)
    return (gsub(s, "%%(%x%x)", function(hex)
        return char(tonumber(hex,16))
    end))
end

local function absolute_path(base_path, relative_path)
    if find(relative_path,"^/") then
        return relative_path
    end
    local path = gsub(base_path, "[^/]*$", "")
    path = path .. relative_path
    path = gsub(path, "([^/]*%./)", function (s)
        if s ~= "./" then
            return s
        else
            return ""
        end
    end)
    path = gsub(path, "/%.$", "/")
    local reduced
    while reduced ~= path do
        reduced = path
        path = gsub(reduced, "([^/]*/%.%./)", function (s)
            if s ~= "../../" then
                return ""
            else
                return s
            end
        end)
    end
    path = gsub(reduced, "([^/]*/%.%.)$", function (s)
        if s ~= "../.." then
            return ""
        else
            return s
        end
    end)
    return path
end

function url.parse(url, default)
    local parsed = { }
    for k, v in next, default or parsed do
        parsed[k] = v
    end
    if not url or url == "" then
        return nil, "invalid url"
    end
    url = gsub(url, "#(.*)$", function(f)
        parsed.fragment = f
        return ""
    end)
    url = gsub(url, "^([%w][%w%+%-%.]*)%:", function(s)
        parsed.scheme = s
        return ""
    end)
    url = gsub(url, "^//([^/]*)", function(n)
        parsed.authority = n
        return ""
    end)
    url = gsub(url, "%?(.*)", function(q)
        parsed.query = q
        return ""
    end)
    url = gsub(url, "%;(.*)", function(p)
        parsed.params = p
        return ""
    end)
    if url ~= "" then
        parsed.path = url
    end
    local authority = parsed.authority
    if not authority then
        return parsed
    end
    authority = gsub(authority,"^([^@]*)@", function(u)
        parsed.userinfo = u
        return ""
    end)
    authority = gsub(authority, ":([^:%]]*)$", function(p)
        parsed.port = p
        return ""
    end)
    if authority ~= "" then
        parsed.host = match(authority, "^%[(.+)%]$") or authority
    end
    local userinfo = parsed.userinfo
    if not userinfo then
        return parsed
    end
    userinfo = gsub(userinfo, ":([^:]*)$", function(p)
        parsed.password = p
        return ""
    end)
    parsed.user = userinfo
    return parsed
end

function url.build(parsed)
    local url = parsed.path or ""
    if parsed.params then
        url = url .. ";" .. parsed.params
    end
    if parsed.query then
        url = url .. "?" .. parsed.query
    end
    local authority = parsed.authority
    if parsed.host then
        authority = parsed.host
        if find(authority, ":") then -- IPv6?
            authority = "[" .. authority .. "]"
        end
        if parsed.port then
            authority = authority .. ":" .. tostring(parsed.port)
        end
        local userinfo = parsed.userinfo
        if parsed.user then
            userinfo = parsed.user
            if parsed.password then
                userinfo = userinfo .. ":" .. parsed.password
            end
        end
        if userinfo then authority = userinfo .. "@" .. authority end
    end
    if authority then
        url = "//" .. authority .. url
    end
    if parsed.scheme then
        url = parsed.scheme .. ":" .. url
    end
    if parsed.fragment then
        url = url .. "#" .. parsed.fragment
    end
    return url
end

function url.absolute(base_url, relative_url)
    local base_parsed
    if type(base_url) == "table" then
        base_parsed = base_url
        base_url = url.build(base_parsed)
    else
        base_parsed = url.parse(base_url)
    end
    local relative_parsed = url.parse(relative_url)
    if not base_parsed then
        return relative_url
    elseif not relative_parsed then
        return base_url
    elseif relative_parsed.scheme then
        return relative_url
    else
        relative_parsed.scheme = base_parsed.scheme
        if not relative_parsed.authority then
            relative_parsed.authority = base_parsed.authority
            if not relative_parsed.path then
                relative_parsed.path = base_parsed.path
                if not relative_parsed.params then
                    relative_parsed.params = base_parsed.params
                    if not relative_parsed.query then
                        relative_parsed.query = base_parsed.query
                    end
                end
            else
                relative_parsed.path = absolute_path(base_parsed.path or "", relative_parsed.path)
            end
        end
        return url.build(relative_parsed)
    end
end

function url.parse_path(path)
    local parsed = { }
    path = path or ""
    gsub(path, "([^/]+)", function (s)
        insert(parsed, s)
    end)
    for i=1,#parsed do
        parsed[i] = url.unescape(parsed[i])
    end
    if sub(path, 1, 1) == "/" then
        parsed.is_absolute = 1
    end
    if sub(path, -1, -1) == "/" then
        parsed.is_directory = 1
    end
    return parsed
end

function url.build_path(parsed, unsafe)
    local path = ""
    local n = #parsed
    if unsafe then
        for i = 1, n-1 do
            path = path .. parsed[i] .. "/"
        end
        if n > 0 then
            path = path .. parsed[n]
            if parsed.is_directory then
                path = path .. "/"
            end
        end
    else
        for i = 1, n-1 do
            path = path .. protect_segment(parsed[i]) .. "/"
        end
        if n > 0 then
            path = path .. protect_segment(parsed[n])
            if parsed.is_directory then
                path = path .. "/"
            end
        end
    end
    if parsed.is_absolute then
        path = "/" .. path
    end
    return path
end

package.loaded["socket.url"] = url

return url