LUA-URI(3)
π NAME π
Hiii bestie, meet lua-uri β the Lua module for manipulating URIs β¨ Like, the whole personality is URIs, and it's lowkey obsessed with getting them right π
π¦ LOADING THE MODULE π¦
Okay so here's the thing: when the URI module loads, it does not touch any global variables π ββοΈ Not one. Which means you get to decide what name you want to reach it by, and that's honestly so much freedom.
You'll probs want to load it like this:
local URI = require "uri"
You can call the variable literally anything other than "URI" if you want (live your truth), or you could even assign the table that require hands back to a global variable. For this documentation though, we're just gonna assume you're using a variable called "URI" π«Ά
π PARSING, VALIDATING AND NORMALIZING URIS π
When you create a URI object, the string you hand over gets checked to make sure it conforms to the appropriate standards β If everything's chill, the new object gets returned. If not? You get nil and an error message. And you can turn any of those errors into Lua exceptions using the assert function π
local URI = require "URI"
local uri = assert(URI:new("http://example.com/foo"))
-- In this case, these will print the original string.
-- They are both the same.
print(tostring(uri))
print(uri:uri())
You can pull out individual parts of the URI with the various accessor methods, like so:
print(uri:scheme()) -- http
print(uri:host()) -- example.com
print(uri:path()) -- /foo
Some URIs get normalized automatically to produce an equivalent canonical version β¨ Nothing gets changed that would affect how the URI will be interpreted, no cap. For example:
local uri = assert(URI:new("HTTP://EXAMPLE.COM:80/FOO"))
print(tostring(uri)) -- http://example.com/FOO
In this case the scheme and the hostname were both converted to lowercase (but not the path part, because that's case sensitive, bestie, don't touch it π). The port number also got removed because port 80 is the default anyway for HTTP URIs.
If you just wanna make sure a URI is correct without throwing an exception, do it like this:
local uri, err = URI:new(uri_to_test)
if uri then
print("valid, normalized to " .. tostring(uri))
else
print("invalid, error message is " .. err)
end
β οΈ Note that many invalid URIs will get processed as relative URI references β so if you're expecting an absolute URI, it's also a good idea to check that the is_relative method returns false. Like, double-check it, fr fr.
π¦ CLONING URIS π¦
Wanna make a copy of a URI object? Just pass it to the constructor:
local original = URI:new("http://www/foo")
local copy = URI:new(original)
The two objects will contain the same information, but they can be changed independently π Main character energy for each.
πΈ RELATIVE URIS πΈ
Okay so a relative URI reference is not a complete URI. It doesn't have a scheme, so it doesn't really mean anything until it gets resolved against an absolute URI. That's just how it is π
For this reason, when you create a URI object from a relative URI, it will belong to the special class uri._relative. And there is very little you can do with a relative URI object other than get and set its path, query string, and fragment identifier. That's the whole menu, sorry babes π
Relative URI objects get created in the same way as absolute ones:
local uri = assert(URI:new("../path?query#fragment"))
print(uri:is_relative()) -- true
print(uri._NAME) -- uri._relative
There are two ways to resolve a relative URI reference against an absolute URI to get another absolute URI π
One is to create a new URI object, passing the base URI as a second argument to the constructor:
local rel = assert(URI:new("../quux.html"))
local base = assert(URI:new("http://example.com/foo/bar/"))
local abs = assert(URI:new(rel, base))
print(tostring(abs)) -- http://example.com/foo/quux.html
You can also do this by passing strings to new, instead of objects:
local abs = assert(URI:new("../quux.html",
"http://example.com/foo/bar/"))
print(tostring(abs)) -- http://example.com/foo/quux.html
Alternatively β and this one's iconic β a URI object containing a relative URI can be made absolute without creating a new object, using the resolve method:
local uri = assert(URI:new("../quux.html"))
local base = assert(URI:new("http://example.com/foo/bar/"))
uri:resolve(base)
print(tostring(uri)) -- http://example.com/foo/quux.html
The reverse process can be carried out with the relativize method, which creates a relative URI from an absolute one, where the relative URI can be later resolved against a particular base URI:
local uri = assert(URI:new("http://example.com/foo/quux.html"))
local base = assert(URI:new("http://example.com/foo/bar/"))
uri:relativize(base)
print(tostring(uri)) -- ../quux.html
It is possible for a relative URI to have an authority part, although this is very rare in practice. It's unlikely that you'll ever need to do this, but you can create a URI like this:
local uri = assert(URI:new("//example.com/path"))
π METHODS π
Okay bestie, this is the complete list of the methods you can call on a generic URI object once it's been created by calling new π
Some URIs get created in more specific classes (those are listed in the URI schemes section), and those ones may have additional methods. Arguments shown in square brackets below are optional.
β οΈ Heads up: all the accessor methods, likepathanduri, can be used just to return the current value (if they're called without an argument), or they can set a new value while returning the old value. And passingnilas the argument is generally different from not passing an argument at all, or from passing an empty string. Do not mix these up, I'm begging π
uri:default_port()β Returns the default port used for this type of URI when no port number is supplied in the authority part. This will benilif the standard for the URI's current scheme doesn't specify a default port, or if the scheme is one which this library doesn't have any special understanding of.
local uri = assert(URI:new("http://example.com:123/"))
print(uri:default_port()) -- 80
uri:eq(other)β Returns true if the two URI objects contain the same URI.othercan also be a string, which will be converted to a URI object (in order for the normalization to be done). This can also be called as a stand-alone function if you don't know whether either URI is an object or a string. For example:
print(URI.eq("http://example.com",
"HTTP://EXAMPLE.COM/"))
If either value is a string which isn't a valid URI, this will throw an exception. It will however accept relative URIs, and they will be compared as normal. A relative URI is never equal to an absolute one.
There is no less-than comparison function, as URIs don't have any particular ordering. If you want to sort URI objects you're best bet is probably just to compare the string versions:
function urisort (a, b)
return a:uri() < b:uri()
end
table.sort(t, urisort)
uri:fragment([newvalue])β Returns the current fragment part of the URI (the part after the"#"character), ornilif the URI has no fragment part. Note that an empty fragment (zero characters long) is different from one which is completely missing π
If newvalue is supplied, changes the fragment to the new value, percent encoding any characters which would not be valid in a fragment part. Any percent encoding already done on the string will be left in place (not double encoded). If newvalue is nil then any existing fragment will be removed.
The syntax of fragments are meaningful only for particular media types of resources, so there is no special behaviour for different URI schemes.
uri:host([newvalue])β Get and set the host part of the authority in a URI. This can be a domain name, an IPv4 address (four numbers separated by dots), or an IPv6 address (which must include the enclosing square brackets used in URIs).
When setting a new host, the value is normalized to lowercase. An invalid value will cause an exception to be thrown. The value can be an empty string to indicate the default host.
Setting the value to nil will cause the host to be removed altogether, leaving the URI with no authority component. This will throw an exception if there is a userinfo or port component in the URI, because it is impossible to represent a URI with no host when there is an authority component π
Some URI schemes may throw an exception when setting the host to nil or the empty string, and others when setting it to anything other than nil, if those schemes require or disallow authority components.
uri:init()β This method is called internally to make a URI object belong to the right class and do any scheme-specific validation and normalization. It is only of interest if you want to write a newurisubclass for particular types of URIs.
The implementation in the uri class itself changes the class of the object to the one appropriate to the scheme (if there is a more specific class available). It also removes the port number from the authority component if it is unnecessary because the scheme defines it as the default port. Finally, if there is a more specific class available it calls the init method in that.
init is called after the URI has been split into components according to the generic syntax, so it can use the accessor methods to get at them. It should return the same values as new, either the new URI object (the object it was called on), or nil and an error message.
uri:is_relative()β Returns true if this is a relative URI reference, false otherwise. All relative URIs belong to the classuri._relative. All the other URI classes are for absolute URIs.uri:path([newvalue])β Get or set the path component of the URI. Throws an exception if the new value is not valid in the context of the rest of the URI.
local uri = assert(URI:new("http://example.com/foo"))
local old = uri:path("/bar/")
print(old) -- /foo
print(uri:path()) -- /bar/
When a new path value is supplied, it can already be percent encoded, but any characters which aren't allowed are encoded as well. Percent characters are not encoded themselves, because they are assumed to be part of the existing encoding. The existing percent encoding is normalized, and any invalid encoding will cause an exception.
okay so real talk about paths for a sec, because this is where it gets β¨spicyβ¨ β
there are certain paths that literally cannot be expressed in the URI syntax, and the library will not let you lie about it:
- a path that doesn't start with a
/character (unless it's completely empty) can't be represented when there's an authority component β so trying it will throw an exception π - a path that starts with
//when there's no authority component would get misinterpreted, so the second slash gets percent encoded π«Ά
and some URI schemes impose even more restrictions on what's allowed in a path, so other path values may also cause exceptions in certain cases. iykyk, not every scheme is chill like that.
uri:port([newvalue])
get or set the port number in a URI π
the value returned is always an integer number or nil.
if newvalue is supplied it should be a non-negative integer number, or a string containing only digits, or nil to remove any existing port number. an exception is thrown if it's an invalid value, or if the URI scheme doesn't allow port numbers to be specified at all (rude, but okay). and if there's currently no authority part in the URI, then an empty host will be added to create one.
now here's the cute lil detail bestie π if the port number is the default for a URI scheme (the same as the number returned from the default_port method), then the port method will return that number, but the number won't actually be shown in the URI when it's represented as a string, because it would be redundant. setting the port number to nil has the same effect as setting it to the default port number. iconic behavior honestly.
uri:query([newvalue])
get or set the query part of a URI π¦
if newvalue is supplied it should be the new string, or nil to remove any existing query part. the query part can be an empty string, and that's different from it not being present at all β the ? character will still be included to indicate that there's a query part, even if it's not followed by anything else. so ? alone is a whole mood, not a mistake πΈ
any characters which would not be valid in a query part will be percent encoded, but any percent encoding already done on the string will be left in place β so no double encoding, we don't do that here β¨
the base-class implementation of this method never throws exceptions, but some scheme-specific classes may throw exceptions if they impose constraints on the syntax of query parts. so like, watch out for the picky ones.
uri:resolve(base)
given an object representing a relative URI, resolve it against the base URI base (which can be a URI object or string) and update the uri object to contain an absolute URI π₯
has no effect if uri is already an absolute URI. throws an exception if base is not an absolute URI, or if the new URI formed by combining them would be invalid for the given scheme.
see also the section Relative URIs and the uri:relativize(base) method. they're besties, they come as a pair π
uri:scheme([newvalue])
get and set the scheme of the URI. quick heads up though β altering the scheme of an existing URI is very unlikely to be useful. like, not me doing surgery on a perfectly good URI π
throws an exception if newvalue is nil or not a valid scheme, or if the rest of the URI is not valid when interpreted with the new scheme. and after calling this method, the class of the object may have been changed, if the old class is not appropriate for the new value. glow-up moment, the object literally changes species β¨
uri:relativize(base)
if possible, update the absolute URI uri to contain a relative URI which, when resolved again against base, will yield the original URI value. this doesn't return anything, it just modifies the object. no takebacks, it's in-place π«Ά
has no effect if uri is already relative, or if there's no way to create an appropriate relative URI β so the URI will remain absolute, for example if base has a different scheme from uri. throws an exception if base is not absolute.
this method will never result in a network-path reference (a relative URI which includes an authority part). in cases where that would be possible, the value in uri will be left as an absolute URI, which is less likely to cause problems. safety first, always π
see also the section Relative URIs and the uri:resolve(base) method.
uri:uri([newvalue])
returns the URI value as a string π the return value is the same as you'll get from tostring(uri).
if an argument is supplied, this replaces the URI in the uri object with a different one. newvalue must be a complete new URI or relative URI reference in a string, or a URI object.
this is equivalent to creating a new URI object by calling URI:new, except that instead of creating a new object the existing object is updated with the new information. it is also not possible to pass a base URI to the uri method. no shortcuts, sorry babe.
throws an exception if newvalue is nil or if there's any error in parsing the new URI string. and after calling this method the class of the object may have been changed, if the old class is not appropriate for the new value. again β shapeshifter energy π¦
uri:userinfo([newvalue])
get or set the userinfo part of the URI. if newvalue is supplied then it's expected to be percent encoded already. percent encoding is normalized. an exception will be thrown if the new value is invalid, or if the URI scheme does not allow a userinfo part (for example if it's an HTTP URI β sorry, no secrets in there π). if there's currently no authority part in the URI, then an empty host will be added to create one.
if newvalue is nil then any existing userinfo part is removed. gone, deleted, not a trace π
β¨ URI schemes β¨
the following Lua modules provide classes which implement extra validation and normalization, or provide extra methods, for URIs with specific schemes π
uri.datauri.fileuri.ftpuri.httpanduri.httpsuri.popuri.rtspanduri.rtspuuri.telneturi.urn
π§ Other modules π§
other Lua modules provide additional functionality used in the library, or act as base classes for the scheme-specific classes πΈ
uri._loginβ baseclass for URI schemes which use a username and password in their userinfo part, separated by a colon (for example FTP). πuri._utilβ utility functions used by the rest of the library. contains usefuluri_encodeanduri_decodefunctions which might be useful elsewhere. lowkey the little helper that carries the whole friend group π«Ά
References
the parsing of URI syntax is based primarily on RFC 3986 π periodt.
Copyright
this software and documentation is Copyright (c) 2007 Geoff Richards <geoff@geoffrichards.co.uk>. it is free software; you can redistribute it and/or modify it under the terms of the Lua 5.0 license. the full terms are given in the file COPYRIGHT supplied with the source code package, and are also available here: <http://www.lua.org/license.html> π
an older unreleased version of this library was created as a direct port of the Perl URI library, by Gisle Aas and others. it has since been rewritten with a somewhat different design. character development, honestly π
that's the whole page bestie!! this was version1.1and the date on it says2012-00-00(yes the month is literally00, we don't ask questions, we just vibe π), and the source it came from is theLUA-URI(3)manual page. go forth and parse responsibly πβ¨π¦
π¦ where this page comes from
- debian trixie: lua-uri 0.1+20130926+git14fa255d-3 Β· MIT
- ubuntu noble: lua-uri 0.1+20130926+git14fa255d-3 Β· MIT
- ubuntu questing: lua-uri 0.1+20130926+git14fa255d-3
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms