2026-06-08 11:04:52 +02:00
|
|
|
-- Copyright 2026 Leon van Kammen / coderofsalvation. All rights reserved.
|
|
|
|
|
--
|
|
|
|
|
-- Redistribution and use in source and binary forms, with or without modification, are
|
|
|
|
|
-- permitted provided that the following conditions are met:
|
|
|
|
|
--
|
|
|
|
|
-- 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
|
|
|
-- conditions and the following disclaimer.
|
|
|
|
|
--
|
|
|
|
|
-- 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
|
|
|
-- of conditions and the following disclaimer in the documentation and/or other materials
|
|
|
|
|
-- provided with the distribution.
|
|
|
|
|
--
|
|
|
|
|
-- THIS SOFTWARE IS PROVIDED BY AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
|
|
|
-- WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
|
|
|
-- FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR
|
|
|
|
|
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
|
-- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
|
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
|
|
|
-- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
|
|
|
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
|
|
|
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
--
|
|
|
|
|
-- The views and conclusions contained in the software and documentation are those of the
|
|
|
|
|
-- authors and should not be interpreted as representing official policies, either expressed
|
|
|
|
|
-- or implied, of
|
|
|
|
|
|
|
|
|
|
-- below is a lua adaptation of Godot's URI.gd
|
2026-06-06 12:35:31 +02:00
|
|
|
-- https://gist.github.com/coderofsalvation/b2b111a2631fbdc8e76d6cab3bea8f17
|
|
|
|
|
|
|
|
|
|
local url = {}
|
|
|
|
|
|
|
|
|
|
-- Helper function to split a string by a delimiter
|
|
|
|
|
local function split(str, delimiter)
|
|
|
|
|
local result = {}
|
|
|
|
|
if delimiter == "" then return {str} end
|
|
|
|
|
local pattern = string.format("([^%s]+)", delimiter)
|
|
|
|
|
for match in string.gmatch(str, pattern) do
|
|
|
|
|
table.insert(result, match)
|
|
|
|
|
end
|
|
|
|
|
return result
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Helper for guess_type (converts numbers/booleans from strings)
|
|
|
|
|
local function guess_type(val)
|
|
|
|
|
if tonumber(val) then return tonumber(val) end
|
|
|
|
|
if val == "true" then return true end
|
|
|
|
|
if val == "false" then return false end
|
|
|
|
|
return val
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Helper to parse query parameters and fragments
|
|
|
|
|
local function parseArgs(fragment)
|
|
|
|
|
local ARG = {}
|
|
|
|
|
if not fragment or fragment == "" then return ARG end
|
|
|
|
|
|
|
|
|
|
local items = split(fragment, "&")
|
|
|
|
|
for _, item in ipairs(items) do
|
|
|
|
|
local key_value = split(item, "=")
|
|
|
|
|
if #key_value > 1 then
|
|
|
|
|
ARG[key_value[1]] = guess_type(key_value[2])
|
|
|
|
|
elseif #key_value == 1 then
|
|
|
|
|
ARG[key_value[1]] = ""
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return ARG
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Main URL Parser function attached to the module object
|
|
|
|
|
function url.parse(url_string)
|
|
|
|
|
-- Setup initial URI table structure
|
|
|
|
|
local URI = {
|
|
|
|
|
domain = "",
|
|
|
|
|
fragment = {},
|
|
|
|
|
file = "",
|
|
|
|
|
extension = "",
|
|
|
|
|
string = url_string,
|
|
|
|
|
protocol = "",
|
|
|
|
|
path = "",
|
|
|
|
|
query = {},
|
|
|
|
|
hash = "",
|
|
|
|
|
URN = "",
|
|
|
|
|
isLocal = false
|
|
|
|
|
}
|
|
|
|
|
local remaining = url_string
|
|
|
|
|
local protocol = string.match(remaining, "^(%w+://)") -- protocol
|
|
|
|
|
if protocol then
|
|
|
|
|
URI.protocol = protocol:gsub("://", "")
|
|
|
|
|
remaining = remaining:sub(#protocol + 1)
|
|
|
|
|
end
|
|
|
|
|
local hash = string.match(remaining, "(#.*)$") -- fragment
|
|
|
|
|
if hash then
|
|
|
|
|
URI.hash = hash
|
|
|
|
|
remaining = remaining:sub(1, #remaining - #hash)
|
|
|
|
|
end
|
|
|
|
|
local query = string.match(remaining, "(%?[^#]+)$")
|
|
|
|
|
if query then
|
|
|
|
|
URI.query = query
|
|
|
|
|
remaining = remaining:sub(1, #remaining - #query)
|
|
|
|
|
end
|
|
|
|
|
URI.path = remaining
|
|
|
|
|
-- Process Path, Domain, and File
|
|
|
|
|
if URI.path and URI.path ~= "" then
|
|
|
|
|
local pathParts = split(URI.path, "/")
|
|
|
|
|
|
|
|
|
|
if #pathParts > 1 then
|
|
|
|
|
local firstPart = pathParts[1]
|
|
|
|
|
-- Check if the first part looks like a domain (contains '.' or ':')
|
|
|
|
|
if string.find(firstPart, "%.") or string.find(firstPart, ":") then
|
|
|
|
|
URI.domain = table.remove(pathParts, 1)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
-- Reconstruct path
|
|
|
|
|
URI.path = table.concat(pathParts, "/")
|
|
|
|
|
-- Extract file if present
|
|
|
|
|
if #pathParts > 0 then
|
|
|
|
|
local lastPart = pathParts[#pathParts]
|
|
|
|
|
if string.find(lastPart, "%.") then
|
|
|
|
|
URI.file = lastPart
|
|
|
|
|
URI.extension = URI.file:match("%.([^%.]+)$"):upper()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if URI.hash and URI.hash ~= "" then
|
|
|
|
|
URI.fragment = parseArgs(URI.hash:sub(2)) -- sub(2) skips the '#'
|
|
|
|
|
else
|
|
|
|
|
URI.fragment = {}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if URI.query and type(URI.query) == "string" and URI.query ~= "" then
|
|
|
|
|
URI.query = parseArgs(URI.query:sub(2)) -- sub(2) skips the '?'
|
|
|
|
|
else
|
|
|
|
|
URI.query = {}
|
|
|
|
|
end
|
|
|
|
|
if URI.domain ~= "" then
|
|
|
|
|
URI.URN = URI.string:gsub("%?.*", "")
|
|
|
|
|
else
|
|
|
|
|
URI.URN = ""
|
|
|
|
|
end
|
|
|
|
|
URI.isLocal = (URI.domain == "")
|
|
|
|
|
return URI
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Return the module table so it can be assigned via require()
|
|
|
|
|
return url
|