Imported Upstream version 0.1+20130926+git14fa255d

upstream upstream/0.1+20130926+git14fa255d
Victor Seva 12 years ago
commit ed606df300

3
.gitignore vendored

@ -0,0 +1,3 @@
lua-uri-*.tar.bz2
lua-uri-*.tar.gz
lua-uri-*.zip

@ -0,0 +1,23 @@
This software and documentation is distributed under the same terms as
Lua version 5.0, the MIT/X Consortium license. The full terms are as
follows:
Copyright (C) 2007 Geoff Richards
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,9 @@
1.1 2012-00-00
* Fix accidental setting of global variable 'err' in 'uri.ftp' and
'uri.telnet'.
* Switch test suite from Lunit 0.3 to Lunit 0.5.
1.0 2007-11-30
* Initial release.

@ -0,0 +1,79 @@
COPYRIGHT
Changes
MANIFEST
Makefile
README
TODO
debian/Makefile.Debian.conf
debian/changelog
debian/compat
debian/control
debian/copyright
debian/liblua5.1-uri-dev.manpages
debian/rules
doc/lua-uri-_login.3
doc/lua-uri-_login.pod
doc/lua-uri-_util.3
doc/lua-uri-_util.pod
doc/lua-uri-data.3
doc/lua-uri-data.pod
doc/lua-uri-file.3
doc/lua-uri-file.pod
doc/lua-uri-ftp.3
doc/lua-uri-ftp.pod
doc/lua-uri-http.3
doc/lua-uri-http.pod
doc/lua-uri-pop.3
doc/lua-uri-pop.pod
doc/lua-uri-rtsp.3
doc/lua-uri-rtsp.pod
doc/lua-uri-telnet.3
doc/lua-uri-telnet.pod
doc/lua-uri-urn-isbn.3
doc/lua-uri-urn-isbn.pod
doc/lua-uri-urn-issn.3
doc/lua-uri-urn-issn.pod
doc/lua-uri-urn-oid.3
doc/lua-uri-urn-oid.pod
doc/lua-uri-urn.3
doc/lua-uri-urn.pod
doc/lua-uri.3
doc/lua-uri.pod
lunit-console.lua
lunit.lua
test/_generic.lua
test/_pristine.lua
test/_relative.lua
test/_resolve.lua
test/_util.lua
test/data.lua
test/file.lua
test/ftp.lua
test/http.lua
test/pop.lua
test/rtsp.lua
test/telnet.lua
test/urn-isbn.lua
test/urn-issn.lua
test/urn-oid.lua
test/urn.lua
uri-test.lua
uri.lua
uri/_login.lua
uri/_relative.lua
uri/_util.lua
uri/data.lua
uri/file.lua
uri/file/unix.lua
uri/file/win32.lua
uri/ftp.lua
uri/http.lua
uri/https.lua
uri/pop.lua
uri/rtsp.lua
uri/rtspu.lua
uri/telnet.lua
uri/urn.lua
uri/urn/isbn.lua
uri/urn/issn.lua
uri/urn/oid.lua

@ -0,0 +1,58 @@
PACKAGE=lua-uri
VERSION=$(shell head -1 Changes | sed 's/ .*//')
RELEASEDATE=$(shell head -1 Changes | sed 's/.* //')
PREFIX=/usr/local
DISTNAME=$(PACKAGE)-$(VERSION)
# The path to where the module's source files should be installed.
LUA_SPATH:=$(shell pkg-config lua5.1 --define-variable=prefix=$(PREFIX) \
--variable=INSTALL_LMOD)
MANPAGES = doc/lua-uri.3 doc/lua-uri-_login.3 doc/lua-uri-_util.3 doc/lua-uri-data.3 doc/lua-uri-file.3 doc/lua-uri-ftp.3 doc/lua-uri-http.3 doc/lua-uri-pop.3 doc/lua-uri-rtsp.3 doc/lua-uri-telnet.3 doc/lua-uri-urn.3 doc/lua-uri-urn-isbn.3 doc/lua-uri-urn-issn.3 doc/lua-uri-urn-oid.3
all: $(MANPAGES)
doc/lua-%.3: doc/lua-%.pod Changes
sed 's/E<copy>/(c)/g' <$< | sed 's/E<ndash>/-/g' | \
pod2man --center="Lua $(shell echo $< | sed 's/^doc\/lua-//' | sed 's/\.pod$$//' | sed 's/-/./g') module" \
--name="$(shell echo $< | sed 's/^doc\///' | sed 's/\.pod$$//' | tr a-z A-Z)" --section=3 \
--release="$(VERSION)" --date="$(RELEASEDATE)" >$@
test: all
echo 'lunit.main({...})' | $(VALGRIND) lua -llunit - test/*.lua
install: all
mkdir -p $(LUA_SPATH)/uri/{file,urn}
mkdir -p $(PREFIX)/share/man/man3
install --mode=644 uri.lua $(LUA_SPATH)/
for module in _login _relative _util data file ftp http https pop rtsp rtspu telnet urn; do \
install --mode=644 uri/$$module.lua $(LUA_SPATH)/uri/; \
done
for module in unix win32; do \
install --mode=644 uri/file/$$module.lua $(LUA_SPATH)/uri/file/; \
done
for module in isbn issn oid; do \
install --mode=644 uri/urn/$$module.lua $(LUA_SPATH)/uri/urn/; \
done
for manpage in $(MANPAGES); do \
gzip -c $$manpage >$(PREFIX)/share/man/man3/$$(echo $$manpage | sed -e 's/^doc\///').gz; \
done
checktmp:
@if [ -e tmp ]; then \
echo "Can't proceed if file 'tmp' exists"; \
false; \
fi
dist: all checktmp
mkdir -p tmp/$(DISTNAME)
tar cf - --files-from MANIFEST | (cd tmp/$(DISTNAME) && tar xf -)
cd tmp && tar cf - $(DISTNAME) | gzip -9 >../$(DISTNAME).tar.gz
cd tmp && tar cf - $(DISTNAME) | bzip2 -9 >../$(DISTNAME).tar.bz2
rm -f $(DISTNAME).zip
cd tmp && zip -q -r -9 ../$(DISTNAME).zip $(DISTNAME)
rm -rf tmp
clean:
rm -f $(MANPAGES)
.PHONY: all test install checktmp dist clean

@ -0,0 +1,17 @@
This library allows you to normalize and validate URIs, and provides methods
for manipulating them in various ways.
The Lua-URI library is written in pure Lua. No C compilation is required
to install it.
When you unpack the source code everything should already be ready for
installation. Doing "make install" as root will install the Lua source
files and the man pages containing the documentation.
See lua-uri(3) for information about how to use the library. The same
documentation is available on the website, where you can also get the
latest packages:
http://www.geoffrichards.co.uk/lua/uri/
Send bug reports, suggestions, etc. to Geoff Richards <geoff@geoffrichards.co.uk>

50
TODO

@ -0,0 +1,50 @@
Perhaps incorporate Mozilla test suite for data: URIs:
http://www.mozilla.org/quality/networking/testing/datatests.html
also validate the mediatype part and normalize it by omitting things which
will be the same by default.
Check for compliance with latest RFC:
http://tools.ietf.org/html/rfc3986
(uri_encode might use the wrong default for 'patn')
Try to integrate support for IRIs:
http://tools.ietf.org/html/rfc3987
Other schemes:
complete IANA list: http://www.iana.org/assignments/uri-schemes.html
opaquelocktoken - http://www.rfc-editor.org/rfc/rfc4918.txt (Appendix C)
svn
mailto - http://tools.ietf.org/html/rfc2368
info - http://tools.ietf.org/html/rfc4452
prospero - http://tools.ietf.org/html/rfc4157
wais - http://tools.ietf.org/html/rfc4156
tel - http://tools.ietf.org/html/rfc3966
sip and sips - http://tools.ietf.org/html/rfc3261
gopher - http://tools.ietf.org/html/rfc4266
tag - http://tools.ietf.org/html/rfc4151
new IMAP URI RFC:
http://www.rfc-editor.org/rfc/rfc5092.txt
Other NIDs for URNs, some of which are shown here:
http://en.wikipedia.org/wiki/Uniform_Resource_Name
Other NIDs which have RFCs:
ietf - http://tools.ietf.org/html/rfc2648
publicid - http://tools.ietf.org/html/rfc3151
uuid - http://tools.ietf.org/html/rfc4122
sici - http://tools.ietf.org/html/rfc2288 (not really standardised there, but is there a proper RFC?)
service - http://tools.ietf.org/html/rfc5031
epc - http://tools.ietf.org/html/rfc5134
check these CPAN bugs against URI module:
mailto encoding: http://rt.cpan.org/Public/Bug/Display.html?id=24934
encode: http://rt.cpan.org/Public/Bug/Display.html?id=21640
gopher URI found in the wild, use for testing:
<URL:gopher://ftp.cavebear.com/00/pub/Ethernet-codes>
Once again provide the stripping of '<URI:...>' and such like.
See if this API has any good ideas:
http://addressable.rubyforge.org/api/

2
doc/.gitignore vendored

@ -0,0 +1,2 @@
lua-uri.3
lua-uri-*.3

@ -0,0 +1,68 @@
=head1 Name
lua-uri-_login - Lua URI library support for URIs containing usernames and passwords
=head1 Description
The C<uri._login> class is used as a base class by classes implementing URI
schemes which can have a username and password in the userinfo part, separated
by a colon.
A URI of this type where the userinfo part contains more than one colon
is considered invalid. They must also have a non-empty host part. The
username and password are each optional.
The current implementation requires subclasses to call this class's
C<init_base> method within their C<init> method to do the extra validation.
This may change if I think of a better way of doing it.
=head1 Methods
All the methods defined in L<lua-uri(3)> are supported, in addition to the
following:
=over
=item uri:username(...)
Mutator for the username in the userinfo part. Returns an optionally sets
the first part of the userinfo, before the colon. If there is no password
then the username will be the whole of the userinfo part, and no colon will
be present.
=for syntax-highlight lua
local uri = assert(URI:new("ftp://host/path"))
uri:username("fred") -- ftp://fred@host/path
uri:username(nil) -- ftp://host/path
Passing nil as the new username will also remove any password in the userinfo,
since the password is expected to be meaningless without the username.
The username is appropriately percent encoded and decoded by this method.
=item uri:password(...)
Mutator for the password part of the userinfo. This will appear after a
colon, whether or not there is a username.
The password is appropriately percent encoded and decoded by this method.
=for syntax-highlight lua
local password = uri:password()
uri:password("secret")
=back
=head1 References
The main RFC for URIs (L<RFC 3986>) does not specify a syntax for the
userinfo part of the authority, which is why the C<username> and C<password>
methods are not provided in the generic C<uri> class. The use of the colon
to separate these parts, and the escaping conventions, are instead derived
from the older L<RFC 1738 section 3.1>, and the up to date telnet URI
specification in L<RFC 4248>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,110 @@
=head1 Name
lua-uri-_util - Utility functions for Lua URI library
=head1 Description
This module contains various utility functions used by the rest of the
library. They are mostly intended only for internal use, and are subject
to change in future versions, but the URI encoding and decoding functions
may be more widely useful.
On loading, the module returns a table containing the functions, but like
all the modules in this library it does not install itself into any global
variables.
=for syntax-highlight lua
local Util = require "uri._util"
=head1 Functions
The following functions can be found in the table returned from C<require>:
=over
=item uri_encode(text, pattern)
Use URI encoding (or 'percent encoding') to encode any unsafe characters
in C<text>. If C<pattern> is specified then it should be part of a Lua
pattern which can be enclosed in square brackets to make a character class.
Usually it will start with C<^> so that the rest of the characters will be
considered the 'safe' ones, not to be encoded. Any character matched by the
pattern will be encoded.
=for syntax-highlight lua
print(Util.uri_encode("foo bar!"))
---> foo%20bar!
print(Util.uri_encode("foo bar!", "^A-Za-z0-9"))
---> foo%20bar%21
The default pattern is: C<^A-Za-z0-9%-_.!~*'()>
=item uri_decode(text, pattern)
Decode any URI encoding in C<text>. If C<pattern> is nil then all encoded
characters will be decoded. If a pattern is supplied then it should be in
the same form as for C<uri_encode>. Any character not matched by the pattern
will be left encoded as it was.
=for syntax-highlight lua
print(Util.uri_decode("foo%20bar%21"))
---> foo bar!
print(Util.uri_decode("foo%20bar%21", "^!"))
---> foo bar%21
=item remove_dot_segments(path)
Removes single and double dot segments from a URI path.
This is the 'remove_dot_segments' algorithm from L<RFC 3986 section 5.2.4>.
The value of C<path> is used as the input buffer, and the contents of the
output buffer are returned.
=item split(pattern, str, max)
Split the string C<str> wherever C<pattern> matches it, returning the pieces
as individual strings in an array. If C<max> is not nil, then stop splitting
after that many pieces have been created.
=item attempt_require(name)
Calling this function is the same as calling Lua's built in C<require>
function, except that if a module called C<name> cannot be found, it returns
nil instead of throwing an exception. If loading the module is successful
then the result of C<require> is returned. An exception is thrown if any
error occurs loading the module other than it not being found.
=item subclass_of(class, baseclass)
Sets up the metatable and a few other things for the table C<class> so that it
will be a subclass of C<baseclass>. This is used by the classes in this
library to implement inheritance.
=item do_class_changing_change(uri, baseclass, changedesc, newvalue, changefunc)
This is used when a mutator method changes something about a URI which leads it
to need to belong to a different class. C<uri> is the URI object to change,
C<baseclass> is the class to reset it to before making the change,
C<changedesc> is a description to be included in an error message if necessary,
C<newvalue> is the new value to be set (which must be a string, as it is also
included in error messages), and C<changefunc> is a function which is called
with a temporary URI object it should adjust and C<newvalue>.
=item uri_part_not_allowed (class, method)
This should be called in scheme-specific classes where certain parts of URIs
are not allowed to be present (e.g., the 'host' part in a URN). It will
override the named method in the class with one which throws an exception
if an attempt is made to set the part to anything other than nil. If the
rest of the code for the scheme keeps objects internally consistent then the
new method should always return nil, although when a URI is being validated
during the C<init> method's execution, it may return other things, which can
be used to detect disallowed parts in a URI being parsed.
=back
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,67 @@
=head1 Name
lua-uri-data - data URI support for Lua URI library
=head1 Description
The class C<uri.data> is used for URIs with the C<data> scheme. It inherits
from the L<uri|lua-uri(3)> class.
Some of the features of this module require the L<lua-datafilter(3)> library
to be installed, but URI objects can still be created from C<data> URIs
even if it isn't available.
Any C<data> URI containing an authority part is considered to be invalid,
as is one whose path does not contain a comma. If the URI has the
C<;base64> parameter, then the data must consist only of characters allowed
in base64 encoded data (upper and lowercase ASCII letters, digits, and the
forward slash and plus characters).
=head1 Methods
All the methods defined in L<lua-uri(3)> are supported. The C<userinfo>,
C<host>, and C<port> methods will always return nil, and will throw an
exception when passed anything other than nil. The C<path> method will throw
an exception if given a new path which is nil or not valid for the C<data>
scheme.
The following additional methods are supported:
=over
=item uri:data_bytes(...)
Get or set the data stored in the URI. The existing data is decoded and
returned. If a new value is supplied it must be a string, and will cause
the path of the URI to be changed to encode the new data. The method will
choose the encoding which will result in the smallest URI, unless the
datafilter module is not installed, in which case it will always use
percent encoding.
An exception is thrown if the datafilter module is not installed and the data
in the URI is encoded as base64, although a data URI using percent encoding
will not cause an exception.
The data passed in and returned should not be encoded in any special way,
that is taken care of by the library.
=item uri:data_media_type(...)
Get or set the media type (MIME type) stored in the URI's path before the
comma. This should not include the C<;base64> parameter, which will be
included in the path automatically when appropriate.
If there is no media type given in the URI then the default value of
C<text/plain> will be returned, and if there is no C<charset> parameter
given then the default C<;charset=US-ASCII> will be included.
The media type is encoded and decoded automatically by this method.
=back
=head1 References
This class is based on L<RFC 2397>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,137 @@
=head1 Name
lua-uri-file - File URI support for Lua URI library
=head1 Description
The class C<uri.file> is used for URIs with the C<file> scheme. It inherits
from the L<uri|lua-uri(3)> class.
A file URI without an authority doesn't have a well defined meaning. This
library considers such URIs to be invalid when they have a path which does not
start with '/' (for example C<file:foo/bar>). It is likely that any such URI
should really be a relative URI reference. If the path does start with a slash
then this library will attempt to 'repair' the URI by adding an empty authority
part, so that C<file:/foo/bar> will be changed automatically to
C<file:///foo/bar>.
A host value of C<localhost> is normalized to an empty host, so that
C<file://localhost/foo> will become C<file:///foo>. An empty path is
normalized to '/'.
The path part is always considered to be case sensitive, so no case folding
is done even when converting to a filesystem path for Windows.
Query parts and fragments are left alone by this library, but are not used
in converting URIs to filesystem paths.
=head1 Converting between URIs and filesystem paths
A C<uri.file> object can be converted into an absolute path suitable for
use on a particular operating system by calling the C<filesystem_path>
method:
=for syntax-highlight lua
local uri = assert(URI:new("file:///foo/bar"))
print(uri:filesystem_path("unix")) -- /foo/bar
print(uri:filesystem_path("win32")) -- \foo\bar
This method will throw an exception if the path cannot be converted.
For example, a file URI containing a host name cannot be represented on
a Unix filesystem, but on a Win32 system it will be converted to a UNC path:
=for syntax-highlight lua
local uri = assert(URI:new("file://server/path"))
print(uri:filesystem_path("unix")) -- error
print(uri:filesystem_path("win32")) -- \\server\path
To convert a filesystem path into a URI, call the class method
C<make_file_uri>:
=for syntax-highlight lua
local FileURI = require "uri.file"
local uri = FileURI.make_file_uri("/foo/bar", "unix")
print(uri) -- file:///foo/bar
uri = FileURI.make_file_uri("C:\foo\bar", "win32")
print(uri) -- file:///C:/foo/bar
To convert a relative URI reference (a L<uri._relative|lua-uri-_relative(3)>
object) into a filesystem path you should first resolve it against an
appropriate C<file> URI, and then call the C<filesystem_path> method on that.
=head1 Methods
All the methods defined in L<lua-uri(3)> are supported. The C<userinfo>,
and C<port> methods will always return nil, and will throw an
exception when passed anything other than nil. The C<host> method will
normalize C<localhost> to an empty host name, and will throw an exception if
given a new value of nil. The C<path> method will normalize an empty path
or nil value to '/'.
In addition to the standard methods, file URIs support the C<filesystem_path>
method, and the C<uri.file> class contains the C<make_file_uri> function,
both of which are described above.
=head1 Operating systems supported
The conversion between a file URI and a path suitable for use on a particular
operating system are defined in additional classes, which are loaded
automatically based on the operating system name supplied to the two conversion
functions. For example, passing the string C<win32> to the functions will
invoke the implementation in the class C<uri.file.win32>. An exception will be
thrown if no class exists to support a given operating system. The following
operating system classes are provided:
=over
=item C<uri.file.unix>
A URI containing a host name will cause an exception to be thrown, as there
is no obvious way for these to be represented in Unix paths. If the path
contains an encoded null byte (C<%00>) or encoded slash (C<%2F>) then an
exception will be thrown.
Attempting to convert a relative path to a URI will cause an exception.
=item C<uri.file.win32>
Forward slashes ('/') in URIs will be converted to backslashes ('\') in
paths, and vice versa.
URIs containing host names will be converted to UNC paths, starting with
a '\\' followed by the hostname and then the path part. If the path part
of a URI appears to begin with a drive letter, then the first slash will
be removed so that the resulting path starts with the letter. Encoded
pipe characters ('%7C') will be recognized as equivalent to colons for the
purpose of identifying drive letters, since they have been historically
used in that way, but I believe they are not allowed to occur in the path
unencoded in a URI nowadays.
=back
The operating system names are case insensitive, and are folded to lowercase
before being converted into a Lua module name.
Currently there is no way for this library to recognise the operating system it
is running on, since Lua has no built-in way of providing that information.
=head1 References
The most up to date IETF standard for the C<file> URI scheme is still
L<RFC 1738 section 3.10>, but this does not specify exactly how to convert
between URIs and filesystem paths on particular platforms. It does however
specify the equivalence between 'localhost' and an empty host.
The correct form of file URI to represent a Windows filesystem path is
described in a blog article:
L<http://blogs.msdn.com/ie/archive/2006/12/06/file-uris-in-windows.aspx>
There is a standard of sorts describing the conversion between Unix paths
and file URIs:
L<http://equinox-project.org/spec/file-uri-spec.txt>
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,45 @@
=head1 Name
lua-uri-ftp - FTP URI support for Lua URI library
=head1 Description
The class C<uri.ftp> is used for URIs with the C<ftp> scheme. It inherits from
the L<uri._login|lua-uri-_login(3)> class.
FTP URIs with a missing authority part or an empty host part are considered
to be invalid. An empty path is always normalized to '/'. The default port
S<is 21>.
=head1 Methods
All the methods defined in L<lua-uri(3)> and L<lua-uri-_login(3)> are
supported, in addition to the following:
=over
=item uri:ftp_typecode(...)
Mutator for the 'type' parameter at the end of the path. If the optional
argument is supplied then a new type is set, replacing the existing one, or
causing the type parameter to be added to the path if it isn't there already.
=for syntax-highlight lua
local uri = assert(URI:new("ftp://host/path"))
uri:ftp_typecode("a") -- ftp://host/path;type=a
uri:ftp_typecode(nil) -- ftp://host/path
Passing in an empty string has the same effect as nil, removing the parameter.
An empty type parameter will be returned as nil, the same as if the parameter
was missing.
=back
=head1 References
This class is based on L<RFC 1738 section 3.2>. Unfortunately there isn't
currently an RFC for FTP URIs based on the more up to date L<RFC 3986>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,28 @@
=head1 Name
lua-uri-http - HTTP URI support for Lua URI library
=head1 Description
The classes C<uri.http> and C<uri.https> are used for URIs with the C<http> and
C<https> schemes respectively. C<uri.http> inherits from the generic
L<uri|lua-uri(3)> class, and C<uri.https> inherits from C<uri.http>.
An HTTP or HTTPS URI containing any userinfo part is considered to be
invalid. An empty path is normalized to '/', since browsers usually do
that, and an empty path cannot be used in an HTTP GET request.
The default port for the C<http> scheme S<is 80>, and for C<https>
S<is 443>.
There are no extra methods defined for telnet URIs, only those described in
L<lua-uri(3)>.
=head1 References
As far as I can tell there is no up to date specification of the syntax of
HTTP URIs, so this class is based on L<RFC 1738 section 3.3> and
L<RFC 2616 section 3.2.2>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,63 @@
=head1 Name
lua-uri-pop - POP URI support for Lua URI library
=head1 Description
The class C<uri.pop> is used for URIs with the C<pop> scheme. It inherits
from the L<uri|lua-uri(3)> class.
POP URIs with a non-empty path part are considered invalid. There are also
invalid if there is a userinfo part which has an empty POP username or an
empty POP 'auth' type.
The ';auth=' part of a POP URI is normalized so that the word 'auth' is in
lowercase, and if the auth type is the default '*' value then it is removed
altogether, leaving just the username.
The default port S<is 110>.
=head1 Methods
All the methods defined in L<lua-uri(3)> are supported. The C<userinfo>
method will throw an exception if the new userinfo would form an invalid
POP URI, and will normalize the auth type part if appropriate. The C<path>
method will always return empty strings for POP URIs, and will throw an
exception if given a new value which is not the empty string.
The following additional methods are provided:
=over
=item uri:pop_user(...)
Get or set the POP username, which is stored in the userinfo part of the
authority.
The return value will be nil if there is no user information in the URI,
or a fully decoded string if there is.
If the new value is empty, and exception is thrown. The user can be set to
nil to remove the userinfo part from the URI, but this will also throw an
exception if there is an 'auth' type specified.
=item uri:pop_auth(...)
Get or set the POP authentication type, which is stored in the userinfo
part of the authority after the string ';auth='.
The value returned is just the auth type, not the ';auth=' part, and will be
fully decoded. The value returned will never be nil. If there is no auth type
specified then the default value of '*' will be returned.
Setting a new value of nil or the empty string will cause an exception
to be thrown.
=back
=head1 References
This class is based on L<RFC 2384>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,24 @@
=head1 Name
lua-uri-rtsp - RTSP URI support for Lua URI library
=head1 Description
The classes C<uri.rtsp> and C<uri.rtspu> are used for URIs with the C<rtsp> and
C<rtspu> schemes respectively. C<uri.rtsp> inherits from the
L<uri.http|lua-uri-http(3)> class, and C<uri.rtspu> inherits from C<uri.rtsp>.
There is no special validation or normalization applied to these URIs beyond
that done for HTTP URIs.
The default port for both schemes S<is 554>.
There are no extra methods defined for telnet URIs, only those described in
L<lua-uri(3)>.
=head1 References
This class is based on L<RFC 2326 section 3.2>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,25 @@
=head1 Name
lua-uri-telnet - Telnet URI support for Lua URI library
=head1 Description
The class C<uri.telnet> is used for URIs with the C<telnet> scheme. It
inherits from the L<uri._login|lua-uri-_login(3)> class.
Telnet URIs are not allowed to have any information in their path part,
because there isn't any specification defining what it would mean. An empty
path or a path of '/' is acceptable, and normalized to '/'. Any other path
is considered invalid.
The default port for telnet URIs S<is 23>.
There are no extra methods defined for telnet URIs, only those described in
L<lua-uri(3)> and L<lua-uri-_login(3)>.
=head1 References
This class is based on L<RFC 4248>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,52 @@
=head1 Name
lua-uri-urn-isbn - ISBN URN support for Lua URI library
=head1 Description
The class C<uri.urn.isbn> is used for URNs with the NID 'isbn', that is, URIs
which begin C<urn:isbn:>. It inherits from the L<uri.urn|lua-uri-urn(3)>
class.
Some of the functionality of this class depends on the L<lua-isbn(3)> module
being installed, although it can be used without that. In particular, if the
module is installed then full checksum validation of the ISBN is performed,
whereas without it the ISBN is only checked for invalid characters. The ISBN
value is normalized to include hyphens in the conventional places if the
lua-isbn module is installed (the exact hyphen positions depend on the number),
but without it all hyphens are removed instead. If the ISBN ends in a checksum
of 'x', then it folded to uppercase.
=head1 Methods
All the methods defined in L<lua-uri(3)> and L<lua-uri-urn(3)> are supported,
as well as the following:
=over
=item uri:isbn(...)
Get or set the ISBN value as an object of the type provided by the C<isbn>
class in the L<lua-isbn(3)> library. This method will throw an exception
if this library is not installed, or if the object supplied is not a valid
ISBN object (it will currently accept a string, but you shouldn't rely on
this).
=item uri:isbn_digits(...)
Get or set the ISBN value as a string containing just the numbers (and
possibly an 'X' as the last digit). There will be no hyphens in this value,
and it should be exactly 10 or 13 characters long.
If a new value is provided then it must not be nil, and will be validated in
the normal way, causing an exception if it is invalid.
=back
=head1 References
This implements the 'isbn' NID defined in L<RFC 3187>, and is consistent
with the same NID suggested in L<RFC 2288>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,41 @@
=head1 Name
lua-uri-urn-issn - ISSN URN support for Lua URI library
=head1 Description
The class C<uri.urn.issn> is used for URNs with the NID 'issn', that is, URIs
which begin C<urn:issn:>. It inherits from the L<uri.urn|lua-uri-urn(3)>
class.
The URI is considered invalid if it doesn't have 8 digits, if there is
anything extra in the NSS other than the digits and optional single hyphen,
or if the checksum digit is wrong.
As specified, the check digit is canonicalized to uppercase. The canonical
form has a single hyphen in the middle of the digits.
=head1 Methods
All the methods defined in L<lua-uri(3)> and L<lua-uri-urn(3)> as supported, as
well as the following:
=over
=item uri:issn_digits(...)
Get or set the ISSN value as a string containing just the numbers. There
will be no hyphens in this value, and it should be exactly 8 characters long.
If a new value is provided then it must not be nil, and will be validated in
the normal way, causing an exception if it is invalid.
=back
=head1 References
This implements the 'issn' NID defined in L<RFC 3044>, and is consistent
with the same NID suggested in L<RFC 2288>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,51 @@
=head1 Name
lua-uri-urn-oid - OID URN support for Lua URI library
=head1 Description
The class C<uri.urn.oid> is used for URNs with the NID 'oid', that is, URIs
which begin C<urn:oid:>. It inherits from the L<uri.urn|lua-uri-urn(3)>
class.
The URI is considered invalid if its NSS doesn't consist only of non-negative
integers separated by full stop characters. Numbers with leading zeroes
are not allowed (although the number '0' on its own is). There must be at
least one number.
There is no normalization performed beyound that performed by the C<uri.urn>
class.
=head1 Methods
All the methods defined in L<lua-uri(3)> and L<lua-uri-urn(3)> as supported, as
well as the following:
=over
=item uri:oid_numbers(...)
Get or set the OID as an array of Lua number values.
If a new value is provided then it must be a table containing an array of
at least one number. All the values in the table must be non-negative
numbers. Non-integer numbers are rounded down to an integer value. Strings
containing only decimal digits are also allowed.
=for syntax-highlight lua
local uri = assert(URI:new("urn:oid:1.0.23"))
local nums = uri:oid_numbers()
for i, v in ipairs(nums) do print(i, v) end
uri:oid_numbers({ 5, 4, 3, 2, 1 })
print(uri) -- urn:oid:5.4.3.2.1
=back
=head1 References
This implements the 'oid' NID defined in L<RFC 3061>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,72 @@
=head1 Name
lua-uri-urn - URN support for Lua URI library
=head1 Description
The class C<uri.urn> is used for URNs, that is, URIs with the C<urn> scheme.
It inherits from the L<uri|lua-uri(3)> class.
Any URN containing an authority part or query part is considered to be invalid,
as is one which does not have a valid NID. URNs must be of the form
C<urn:nid:nss>, where the NSS part has a syntax specific to the NID. The
scheme and NID part are both normalized to lowercase. Some NIDs have
subclasses which enforce further syntax constraints, do NID-specific
normalization, or provide additional methods.
=head1 Methods
All the methods defined in L<lua-uri(3)> are supported. The C<userinfo>,
C<host>, C<port>, and C<query> methods will always return nil, and will throw
an exception when passed anything other than nil. The C<path> method will
throw an exception if given a new path which is nil or not valid for the C<urn>
scheme.
The following additional methods are supported:
=over
=item uri:nid(...)
Get or set the NID (Namespace Identifier) of the URN (the part of the path
before the first colon). If a new value is supplied then the URI's path will
be changed to have the new NID but with the same NSS value.
An exception will be thrown if the new NID is invalid, or if the existing
NSS value is invalid in the context of the new NID. Note that the value
'urn' is an invalid NID.
This can cause the class of the URI object to change, if a different class
is appropriate for the new NID.
=item uri:nss(...)
Get or set the NSS (Namespace Specific String) part of the URN (the part of the
path after the first colon). If a new value is supplied then the URI's path
will be changed to use the new NSS, but the NID will be unchanged.
This will throw an exception if the new value is invalid for the current NID.
=back
=head1 Subclasses
The following subclasses are used for URNs with certain NIDs. URNs with
other NIDs just use the generic C<uri.urn> class.
=over
=item L<uri.urn.isbn|lua-uri-urn-isbn(3)>
=item L<uri.urn.issn|lua-uri-urn-issn(3)>
=item L<uri.urn.oid|lua-uri-urn-oid(3)>
=back
=head1 References
This class is based on L<RFC 2141>.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,463 @@
=head1 Name
lua-uri - Lua module for manipulating URIs
=head1 Loading the module
The URI module doesn't alter any global variables when it loads, so you can
decide what name you want to use to access it. You will probably want to
load it like this:
=for syntax-highlight lua
local URI = require "uri"
You can use a variable called something other than C<URI> if you'd like,
or you could assign the table returned by C<require> to a global variable.
In this documentation we'll assume you're using a variable called C<URI>.
=head1 Parsing, validating and normalizing URIs
When you create a URI object, the string you supply is checked to make sure
it conforms to the appropriate standards.
If everything is OK, the new object will be returned, otherwise nil
and an error message will be returned. You can convert any errors into
Lua exceptions using the C<assert> function.
=for syntax-highlight lua
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 extract individual parts of the URI with various accessor methods:
=for syntax-highlight lua
print(uri:scheme()) -- http
print(uri:host()) -- example.com
print(uri:path()) -- /foo
Some URIs will be 'normalized' automatically to produce an equivalent
canonical version. Nothing will be changed which would affect how the
URI will be interpreted. For example:
=for syntax-highlight lua
local uri = assert(URI:new("HTTP://EXAMPLE.COM:80/FOO"))
print(tostring(uri)) -- http://example.com/FOO
In this case the scheme and hostname were both converted to lowercase
(but not the path part, because that's case sensitive). The port number
was also removed because S<port 80> is the default anyway for HTTP URIs.
If you just want to make sure a URI is correct, but without throwing an
exception, use code like this:
=for syntax-highlight lua
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 C<is_relative> method returns false.)
=head1 Cloning URIs
To make a copy of a URI object, pass it to the constructor:
=for syntax-highlight lua
local original = URI:new("http://www/foo")
local copy = URI:new(original)
The two objects will contain the same information, but can be changed
independently.
=head1 Relative URIs
A relative URI reference is not a complete URI. It doesn't have a scheme,
so it doesn't really mean anything until it is resolved against an absolute
URI. For this reason, when you create a URI object from a relative URI,
it will belong to the special class C<uri._relative>. There is very little
you can do with a relative URI object other than get and set its path, query
string, and fragment identifier.
Relative URI objects can be created in the same way as absolute ones:
=for syntax-highlight lua
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:
=for syntax-highlight lua
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 C<new>, instead of objects:
=for syntax-highlight lua
local abs = assert(URI:new("../quux.html",
"http://example.com/foo/bar/"))
print(tostring(abs)) -- http://example.com/foo/quux.html
Alternatively, a URI object containing a relative URI can be made absolute
without creating a new object using the C<resolve> method:
=for syntax-highlight lua
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 C<relativize> method,
creating a relative URI from an absolute one, where the relative URI
can be later resolved against a particular base URI:
=for syntax-highlight lua
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 is unlikely that you'll ever need to do this,
but you can create a URI like this:
=for syntax-highlight lua
local uri = assert(URI:new("//example.com/path"))
=head1 Methods
This is a complete list of the methods you can call on a generic C<URI>
object once created by calling C<new>. Some URIs are created in more
specific classes (listed in the I<URI schemes> section), which may have
additional methods. Arguments shown in square brackets below are optional.
Note that all the accessor methods, like C<path> and C<uri>, can be used just
to return the current value (if they are called without an argument), or can
set a new value while returning the old value. Passing nil as the argument is
generally different from not passing an argument at all, or to passing an
empty string.
=over
=item 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 be nil if 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.
=for syntax-highlight lua
local uri = assert(URI:new("http://example.com:123/"))
print(uri:default_port()) -- 80
=item uri:eq(other)
Returns true if the two URI objects contain the same URI. C<other> can 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:
=for syntax-highlight lua
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:
=for syntax-highlight lua
function urisort (a, b)
return a:uri() < b:uri()
end
table.sort(t, urisort)
=item uri:fragment([newvalue])
Returns the current fragment part of the URI (the part after the C<#>
character), or nil if the URI has no fragment part. Note that an empty
fragment (zero characters long) is different from one which is completely
missing.
If C<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 C<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.
=item 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.
=item uri:init()
This method is called internally to make a URI object belong to the right
class and do any scheme-specific validation an normalization. It is only
of interest if you want to write a new C<uri> subclass for particular types
of URIs.
The implementation in the C<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 C<init> method in
that.
C<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 C<new>, either the new URI object (the
object it was called on), or nil and an error message.
=item uri:is_relative()
Returns true if this is a relative URI reference, false otherwise. All
relative URIs belong to the class C<uri._relative>. All the other URI
classes are for absolute URIs.
=item 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.
=for syntax-highlight lua
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.
There are certain paths which cannot be expressed in the URI syntax. A path
which does not start with a C</> character (unless it's completely empty)
cannot be represented when there is an authority component, so this will
cause an exception to be thrown. A path which starts with C<//> when there
is no authority component would be misinterpreted, so the second slash is
percent encoded.
Some URI schemes may impose further restrictions on what is allowed in a
path, so other path values may cause exceptions in certain cases.
=item uri:port([newvalue])
Get or set the port number in a URI. The value returned is always an
integer number or nil.
If C<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 is an invalid value, or if the URI scheme
doesn't allow port numbers to be specified. If there is currently no
authority part in the URI, then an empty host will be added to create one.
If the port number is the default for a URI scheme (the same as the number
returned from the C<default_port> method), then the C<port> method will
return that number, but the number won't actually be shown in the URI when
it is 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.
=item uri:query([newvalue])
Get or set the query part of a URI.
If C<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, which is
different from it not being present at all (the C<?> character will still
be included to indicate that there is a query part, even if it is not
followed by anything else). 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 (not double encoded).
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.
=item uri:resolve(base)
Given an object representing a relative URI, resolve it against the base
URI C<base> (which can be a URI object or string) and update the C<uri>
object to contain an absolute URI.
Has no effect if C<uri> is already an absolute URI. Throws an exception
if C<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 I<Relative URIs> and the C<uri:relativize(base)> method.
=item uri:scheme([newvalue])
Get and set the scheme of the URI. Altering the scheme of an existing URI
is very unlikely to be useful.
Throws an exception if C<newvalue> is nil or not a valid scheme, or if the
rest of the URI is not valid when interpreted with the new scheme.
After calling this method the class of the object may have been changed,
if the old class is not appropriate for the new value.
=item uri:relativize(base)
If possible, update the absolute URI C<uri> to contain a relative URI
which, when resolved again against C<base>, will yield the original URI
value. This doesn't return anything, just modifies the object.
Has no effect if C<uri> is already relative, or if there is no way to create
an appropriate relative URI (so the URI will remain absolute for example if
C<base> has a different scheme from C<uri>). Throws an exception if C<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 C<uri> will be left as an absolute URI, which is less likely
to cause problems.
See also the section I<Relative URIs> and the C<uri:resolve(base)> method.
=item uri:uri([newvalue])
Returns the URI value as a string. The return value is the same as you'll
get from C<tostring(uri)>.
If an argument is supplied, this replaces the URI in the C<uri> object with
a different one. C<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 C<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 C<uri> method.
Throws an exception if C<newvalue> is nil or if there is any error in parsing
the new URI string. After calling this method the class of the object may
have been changed, if the old class is not appropriate for the new value.
=item uri:userinfo([newvalue])
Get or set the userinfo part of the URI. If C<newvalue> is supplied then
it is 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 is an HTTP URI). If there
is currently no authority part in the URI, then an empty host will be added
to create one.
If C<newvalue> is nil then any existing userinfo part is removed.
=back
=head1 URI schemes
The following Lua modules provide classes which implement extra validation
and normalization, or provide extra methods, for URIs which specific schemes:
=over
=item L<uri.data|lua-uri-data(3)>
=item L<uri.file|lua-uri-file(3)>
=item L<uri.ftp|lua-uri-ftp(3)>
=item L<uri.http|lua-uri-http(3)> and uri.https
=item L<uri.pop|lua-uri-pop(3)>
=item L<uri.rtsp|lua-uri-rtsp(3)> and uri.rtspu
=item L<uri.telnet|lua-uri-telnet(3)>
=item L<uri.urn|lua-uri-urn(3)>
=back
=head1 Other modules
Other Lua modules provide additional functionality used in the library,
or act as base classes for the scheme-specific classes:
=over
=item L<uri._login|lua-uri-_login(3)>
Baseclass for URI schemes which use a username and password in their userinfo
part, separated by a colon (for example FTP).
=item L<uri._util|lua-uri-_util(3)>
Utility functions used by the rest of the library. Contains useful
C<uri_encode> and C<uri_decode> functions which might be useful elsewhere.
=back
=head1 References
The parsing of URI syntax is based primarily on L<RFC 3986>.
=head1 Copyright
This software and documentation is Copyright E<copy> 2007 Geoff Richards
E<lt>geoff@geoffrichards.co.ukE<gt>. It is free software; you can redistribute it
and/or modify it under the terms of the S<Lua 5.0> license. The full terms
are given in the file F<COPYRIGHT> supplied with the source code package,
and are also available here: L<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.
=for comment
vi:ts=4 sw=4 expandtab

@ -0,0 +1,141 @@
--[[--------------------------------------------------------------------------
This file is part of lunit 0.5.
For Details about lunit look at: http://www.mroth.net/lunit/
Author: Michael Roth <mroth@nessie.de>
Copyright (c) 2006-2008 Michael Roth <mroth@nessie.de>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]--------------------------------------------------------------------------
--[[
begin()
run(testcasename, testname)
err(fullname, message, traceback)
fail(fullname, where, message, usermessage)
pass(testcasename, testname)
done()
Fullname:
testcase.testname
testcase.testname:setupname
testcase.testname:teardownname
--]]
require "lunit"
module( "lunit-console", package.seeall )
local function printformat(format, ...)
io.write( string.format(format, ...) )
end
local columns_printed = 0
local function writestatus(char)
if columns_printed == 0 then
io.write(" ")
end
if columns_printed == 60 then
io.write("\n ")
columns_printed = 0
end
io.write(char)
io.flush()
columns_printed = columns_printed + 1
end
local msgs = {}
function begin()
local total_tc = 0
local total_tests = 0
for tcname in lunit.testcases() do
total_tc = total_tc + 1
for testname, test in lunit.tests(tcname) do
total_tests = total_tests + 1
end
end
printformat("Loaded testsuite with %d tests in %d testcases.\n\n", total_tests, total_tc)
end
function run(testcasename, testname)
-- NOP
end
function err(fullname, message, traceback)
writestatus("E")
msgs[#msgs+1] = "Error! ("..fullname.."):\n"..message.."\n\t"..table.concat(traceback, "\n\t") .. "\n"
end
function fail(fullname, where, message, usermessage)
writestatus("F")
local text = "Failure ("..fullname.."):\n"..
where..": "..message.."\n"
if usermessage then
text = text .. where..": "..usermessage.."\n"
end
msgs[#msgs+1] = text
end
function pass(testcasename, testname)
writestatus(".")
end
function done()
printformat("\n\n%d Assertions checked.\n", lunit.stats.assertions )
print()
for i, msg in ipairs(msgs) do
printformat( "%3d) %s\n", i, msg )
end
printformat("Testsuite finished (%d passed, %d failed, %d errors).\n",
lunit.stats.passed, lunit.stats.failed, lunit.stats.errors )
end

@ -0,0 +1,670 @@
--[[--------------------------------------------------------------------------
This file is part of lunit 0.5.
For Details about lunit look at: http://www.mroth.net/lunit/
Author: Michael Roth <mroth@nessie.de>
Copyright (c) 2004, 2006-2009 Michael Roth <mroth@nessie.de>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]--------------------------------------------------------------------------
local orig_assert = assert
local pairs = pairs
local ipairs = ipairs
local next = next
local type = type
local error = error
local tostring = tostring
local string_sub = string.sub
local string_format = string.format
module("lunit", package.seeall) -- FIXME: Remove package.seeall
local lunit = _M
local __failure__ = {} -- Type tag for failed assertions
local typenames = { "nil", "boolean", "number", "string", "table", "function", "thread", "userdata" }
local traceback_hide -- Traceback function which hides lunit internals
local mypcall -- Protected call to a function with own traceback
do
local _tb_hide = setmetatable( {}, {__mode="k"} )
function traceback_hide(func)
_tb_hide[func] = true
end
local function my_traceback(errobj)
if is_table(errobj) and errobj.type == __failure__ then
local info = debug.getinfo(5, "Sl") -- FIXME: Hardcoded integers are bad...
errobj.where = string_format( "%s:%d", info.short_src, info.currentline)
else
errobj = { msg = tostring(errobj) }
errobj.tb = {}
local i = 2
while true do
local info = debug.getinfo(i, "Snlf")
if not is_table(info) then
break
end
if not _tb_hide[info.func] then
local line = {} -- Ripped from ldblib.c...
line[#line+1] = string_format("%s:", info.short_src)
if info.currentline > 0 then
line[#line+1] = string_format("%d:", info.currentline)
end
if info.namewhat ~= "" then
line[#line+1] = string_format(" in function '%s'", info.name)
else
if info.what == "main" then
line[#line+1] = " in main chunk"
elseif info.what == "C" or info.what == "tail" then
line[#line+1] = " ?"
else
line[#line+1] = string_format(" in function <%s:%d>", info.short_src, info.linedefined)
end
end
errobj.tb[#errobj.tb+1] = table.concat(line)
end
i = i + 1
end
end
return errobj
end
function mypcall(func)
orig_assert( is_function(func) )
local ok, errobj = xpcall(func, my_traceback)
if not ok then
return errobj
end
end
traceback_hide(mypcall)
end
-- Type check functions
for _, typename in ipairs(typenames) do
lunit["is_"..typename] = function(x)
return type(x) == typename
end
end
local is_nil = is_nil
local is_boolean = is_boolean
local is_number = is_number
local is_string = is_string
local is_table = is_table
local is_function = is_function
local is_thread = is_thread
local is_userdata = is_userdata
local function failure(name, usermsg, defaultmsg, ...)
local errobj = {
type = __failure__,
name = name,
msg = string_format(defaultmsg,...),
usermsg = usermsg
}
error(errobj, 0)
end
traceback_hide( failure )
local function format_arg(arg)
local argtype = type(arg)
if argtype == "string" then
return "'"..arg.."'"
elseif argtype == "number" or argtype == "boolean" or argtype == "nil" then
return tostring(arg)
else
return "["..tostring(arg).."]"
end
end
function fail(msg)
stats.assertions = stats.assertions + 1
failure( "fail", msg, "failure" )
end
traceback_hide( fail )
function assert(assertion, msg)
stats.assertions = stats.assertions + 1
if not assertion then
failure( "assert", msg, "assertion failed" )
end
return assertion
end
traceback_hide( assert )
function assert_true(actual, msg)
stats.assertions = stats.assertions + 1
local actualtype = type(actual)
if actualtype ~= "boolean" then
failure( "assert_true", msg, "true expected but was a "..actualtype )
end
if actual ~= true then
failure( "assert_true", msg, "true expected but was false" )
end
return actual
end
traceback_hide( assert_true )
function assert_false(actual, msg)
stats.assertions = stats.assertions + 1
local actualtype = type(actual)
if actualtype ~= "boolean" then
failure( "assert_false", msg, "false expected but was a "..actualtype )
end
if actual ~= false then
failure( "assert_false", msg, "false expected but was true" )
end
return actual
end
traceback_hide( assert_false )
function assert_equal(expected, actual, msg)
stats.assertions = stats.assertions + 1
if expected ~= actual then
failure( "assert_equal", msg, "expected %s but was %s", format_arg(expected), format_arg(actual) )
end
return actual
end
traceback_hide( assert_equal )
function assert_not_equal(unexpected, actual, msg)
stats.assertions = stats.assertions + 1
if unexpected == actual then
failure( "assert_not_equal", msg, "%s not expected but was one", format_arg(unexpected) )
end
return actual
end
traceback_hide( assert_not_equal )
function assert_match(pattern, actual, msg)
stats.assertions = stats.assertions + 1
local patterntype = type(pattern)
if patterntype ~= "string" then
failure( "assert_match", msg, "expected the pattern as a string but was a "..patterntype )
end
local actualtype = type(actual)
if actualtype ~= "string" then
failure( "assert_match", msg, "expected a string to match pattern '%s' but was a %s", pattern, actualtype )
end
if not string.find(actual, pattern) then
failure( "assert_match", msg, "expected '%s' to match pattern '%s' but doesn't", actual, pattern )
end
return actual
end
traceback_hide( assert_match )
function assert_not_match(pattern, actual, msg)
stats.assertions = stats.assertions + 1
local patterntype = type(pattern)
if patterntype ~= "string" then
failure( "assert_not_match", msg, "expected the pattern as a string but was a "..patterntype )
end
local actualtype = type(actual)
if actualtype ~= "string" then
failure( "assert_not_match", msg, "expected a string to not match pattern '%s' but was a %s", pattern, actualtype )
end
if string.find(actual, pattern) then
failure( "assert_not_match", msg, "expected '%s' to not match pattern '%s' but it does", actual, pattern )
end
return actual
end
traceback_hide( assert_not_match )
function assert_error(msg, func)
stats.assertions = stats.assertions + 1
if func == nil then
func, msg = msg, nil
end
local functype = type(func)
if functype ~= "function" then
failure( "assert_error", msg, "expected a function as last argument but was a "..functype )
end
local ok, errmsg = pcall(func)
if ok then
failure( "assert_error", msg, "error expected but no error occurred" )
end
end
traceback_hide( assert_error )
function assert_error_match(msg, pattern, func)
stats.assertions = stats.assertions + 1
if func == nil then
msg, pattern, func = nil, msg, pattern
end
local patterntype = type(pattern)
if patterntype ~= "string" then
failure( "assert_error_match", msg, "expected the pattern as a string but was a "..patterntype )
end
local functype = type(func)
if functype ~= "function" then
failure( "assert_error_match", msg, "expected a function as last argument but was a "..functype )
end
local ok, errmsg = pcall(func)
if ok then
failure( "assert_error_match", msg, "error expected but no error occurred" )
end
local errmsgtype = type(errmsg)
if errmsgtype ~= "string" then
failure( "assert_error_match", msg, "error as string expected but was a "..errmsgtype )
end
if not string.find(errmsg, pattern) then
failure( "assert_error_match", msg, "expected error '%s' to match pattern '%s' but doesn't", errmsg, pattern )
end
end
traceback_hide( assert_error_match )
function assert_pass(msg, func)
stats.assertions = stats.assertions + 1
if func == nil then
func, msg = msg, nil
end
local functype = type(func)
if functype ~= "function" then
failure( "assert_pass", msg, "expected a function as last argument but was a %s", functype )
end
local ok, errmsg = pcall(func)
if not ok then
failure( "assert_pass", msg, "no error expected but error was: '%s'", errmsg )
end
end
traceback_hide( assert_pass )
-- lunit.assert_typename functions
for _, typename in ipairs(typenames) do
local assert_typename = "assert_"..typename
lunit[assert_typename] = function(actual, msg)
stats.assertions = stats.assertions + 1
local actualtype = type(actual)
if actualtype ~= typename then
failure( assert_typename, msg, typename.." expected but was a "..actualtype )
end
return actual
end
traceback_hide( lunit[assert_typename] )
end
-- lunit.assert_not_typename functions
for _, typename in ipairs(typenames) do
local assert_not_typename = "assert_not_"..typename
lunit[assert_not_typename] = function(actual, msg)
stats.assertions = stats.assertions + 1
if type(actual) == typename then
failure( assert_not_typename, msg, typename.." not expected but was one" )
end
end
traceback_hide( lunit[assert_not_typename] )
end
function lunit.clearstats()
stats = {
assertions = 0;
passed = 0;
failed = 0;
errors = 0;
}
end
local report, reporterrobj
do
local testrunner
function lunit.setrunner(newrunner)
if not ( is_table(newrunner) or is_nil(newrunner) ) then
return error("lunit.setrunner: Invalid argument", 0)
end
local oldrunner = testrunner
testrunner = newrunner
return oldrunner
end
function lunit.loadrunner(name)
if not is_string(name) then
return error("lunit.loadrunner: Invalid argument", 0)
end
local ok, runner = pcall( require, name )
if not ok then
return error("lunit.loadrunner: Can't load test runner: "..runner, 0)
end
return setrunner(runner)
end
function report(event, ...)
local f = testrunner and testrunner[event]
if is_function(f) then
pcall(f, ...)
end
end
function reporterrobj(context, tcname, testname, errobj)
local fullname = tcname .. "." .. testname
if context == "setup" then
fullname = fullname .. ":" .. setupname(tcname, testname)
elseif context == "teardown" then
fullname = fullname .. ":" .. teardownname(tcname, testname)
end
if errobj.type == __failure__ then
stats.failed = stats.failed + 1
report("fail", fullname, errobj.where, errobj.msg, errobj.usermsg)
else
stats.errors = stats.errors + 1
report("err", fullname, errobj.msg, errobj.tb)
end
end
end
local function key_iter(t, k)
return (next(t,k))
end
local testcase
do
-- Array with all registered testcases
local _testcases = {}
-- Marks a module as a testcase.
-- Applied over a module from module("xyz", lunit.testcase).
function lunit.testcase(m)
orig_assert( is_table(m) )
--orig_assert( m._M == m )
orig_assert( is_string(m._NAME) )
--orig_assert( is_string(m._PACKAGE) )
-- Register the module as a testcase
_testcases[m._NAME] = m
-- Import lunit, fail, assert* and is_* function to the module/testcase
m.lunit = lunit
m.fail = lunit.fail
for funcname, func in pairs(lunit) do
if "assert" == string_sub(funcname, 1, 6) or "is_" == string_sub(funcname, 1, 3) then
m[funcname] = func
end
end
end
-- Iterator (testcasename) over all Testcases
function lunit.testcases()
-- Make a copy of testcases to prevent confusing the iterator when
-- new testcase are defined
local _testcases2 = {}
for k,v in pairs(_testcases) do
_testcases2[k] = true
end
return key_iter, _testcases2, nil
end
function testcase(tcname)
return _testcases[tcname]
end
end
do
-- Finds a function in a testcase case insensitive
local function findfuncname(tcname, name)
for key, value in pairs(testcase(tcname)) do
if is_string(key) and is_function(value) and string.lower(key) == name then
return key
end
end
end
function lunit.setupname(tcname)
return findfuncname(tcname, "setup")
end
function lunit.teardownname(tcname)
return findfuncname(tcname, "teardown")
end
-- Iterator over all test names in a testcase.
-- Have to collect the names first in case one of the test
-- functions creates a new global and throws off the iteration.
function lunit.tests(tcname)
local testnames = {}
for key, value in pairs(testcase(tcname)) do
if is_string(key) and is_function(value) then
local lfn = string.lower(key)
if string.sub(lfn, 1, 4) == "test" or string.sub(lfn, -4) == "test" then
testnames[key] = true
end
end
end
return key_iter, testnames, nil
end
end
function lunit.runtest(tcname, testname)
orig_assert( is_string(tcname) )
orig_assert( is_string(testname) )
local function callit(context, func)
if func then
local err = mypcall(func)
if err then
reporterrobj(context, tcname, testname, err)
return false
end
end
return true
end
traceback_hide(callit)
report("run", tcname, testname)
local tc = testcase(tcname)
local setup = tc[setupname(tcname)]
local test = tc[testname]
local teardown = tc[teardownname(tcname)]
local setup_ok = callit( "setup", setup )
local test_ok = setup_ok and callit( "test", test )
local teardown_ok = setup_ok and callit( "teardown", teardown )
if setup_ok and test_ok and teardown_ok then
stats.passed = stats.passed + 1
report("pass", tcname, testname)
end
end
traceback_hide(runtest)
function lunit.run()
clearstats()
report("begin")
for testcasename in lunit.testcases() do
-- Run tests in the testcases
for testname in lunit.tests(testcasename) do
runtest(testcasename, testname)
end
end
report("done")
return stats
end
traceback_hide(run)
function lunit.loadonly()
clearstats()
report("begin")
report("done")
return stats
end
local lunitpat2luapat
do
local conv = {
["^"] = "%^",
["$"] = "%$",
["("] = "%(",
[")"] = "%)",
["%"] = "%%",
["."] = "%.",
["["] = "%[",
["]"] = "%]",
["+"] = "%+",
["-"] = "%-",
["?"] = ".",
["*"] = ".*"
}
function lunitpat2luapat(str)
return "^" .. string.gsub(str, "%W", conv) .. "$"
end
end
local function in_patternmap(map, name)
if map[name] == true then
return true
else
for _, pat in ipairs(map) do
if string.find(name, pat) then
return true
end
end
end
return false
end
-- Called from 'lunit' shell script.
function main(argv)
argv = argv or {}
-- FIXME: Error handling and error messages aren't nice.
local function checkarg(optname, arg)
if not is_string(arg) then
return error("lunit.main: option "..optname..": argument missing.", 0)
end
end
local function loadtestcase(filename)
if not is_string(filename) then
return error("lunit.main: invalid argument")
end
local chunk, err = loadfile(filename)
if err then
return error(err)
else
chunk()
end
end
local testpatterns = nil
local doloadonly = false
local runner = nil
local i = 0
while i < #argv do
i = i + 1
local arg = argv[i]
if arg == "--loadonly" then
doloadonly = true
elseif arg == "--runner" or arg == "-r" then
local optname = arg; i = i + 1; arg = argv[i]
checkarg(optname, arg)
runner = arg
elseif arg == "--test" or arg == "-t" then
local optname = arg; i = i + 1; arg = argv[i]
checkarg(optname, arg)
testpatterns = testpatterns or {}
testpatterns[#testpatterns+1] = arg
elseif arg == "--" then
while i < #argv do
i = i + 1; arg = argv[i]
loadtestcase(arg)
end
else
loadtestcase(arg)
end
end
loadrunner(runner or "lunit-console")
if doloadonly then
return loadonly()
else
return run(testpatterns)
end
end
clearstats()

@ -0,0 +1,636 @@
require "uri-test"
local URI = require "uri"
module("test.generic", lunit.testcase, package.seeall)
function test_normalize_percent_encoding ()
-- Don't use unnecessary percent encoding for unreserved characters.
test_norm("x:ABCDEFGHIJKLM", "x:%41%42%43%44%45%46%47%48%49%4A%4b%4C%4d")
test_norm("x:NOPQRSTUVWXYZ", "x:%4E%4f%50%51%52%53%54%55%56%57%58%59%5A")
test_norm("x:abcdefghijklm", "x:%61%62%63%64%65%66%67%68%69%6A%6b%6C%6d")
test_norm("x:nopqrstuvwxyz", "x:%6E%6f%70%71%72%73%74%75%76%77%78%79%7A")
test_norm("x:0123456789", "x:%30%31%32%33%34%35%36%37%38%39")
test_norm("x:-._~", "x:%2D%2e%5F%7e")
-- Keep percent encoding for other characters in US-ASCII.
test_norm_already("x:%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F")
test_norm_already("x:%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F")
test_norm_already("x:%20%21%22%23%24%25%26%27%28%29%2A%2B%2C")
test_norm_already("x:%2F")
test_norm_already("x:%3A%3B%3C%3D%3E%3F%40")
test_norm_already("x:%5B%5C%5D%5E")
test_norm_already("x:%60")
test_norm_already("x:%7B%7C%7D")
test_norm_already("x:%7F")
-- Normalize hex digits in percent encoding to uppercase.
test_norm("x:%0A%0B%0C%0D%0E%0F", "x:%0a%0b%0c%0d%0e%0f")
test_norm("x:%AA%BB%CC%DD%EE%FF", "x:%aA%bB%cC%dD%eE%fF")
-- Keep percent encoding, and normalize hex digit case, for all characters
-- outside US-ASCII.
for i = 0x80, 0xFF do
test_norm_already(string.format("x:%%%02X", i))
test_norm(string.format("x:%%%02X", i), string.format("x:%%%02x", i))
end
end
function test_bad_percent_encoding ()
assert_error("double percent", function () URI:new("x:foo%%2525") end)
assert_error("no hex digits", function () URI:new("x:foo%") end)
assert_error("no hex digits 2nd time", function () URI:new("x:f%20o%") end)
assert_error("1 hex digit", function () URI:new("x:foo%2") end)
assert_error("1 hex digit 2nd time", function () URI:new("x:f%20o%2") end)
assert_error("bad hex digit 1", function () URI:new("x:foo%G2bar") end)
assert_error("bad hex digit 2", function () URI:new("x:foo%2Gbar") end)
assert_error("bad hex digit both", function () URI:new("x:foo%GGbar") end)
end
function test_scheme ()
test_norm_already("foo:")
test_norm_already("foo:-+.:")
test_norm_already("foo:-+.0123456789:")
test_norm_already("x:")
test_norm("example:FooBar:Baz", "ExAMplE:FooBar:Baz")
local uri = assert(URI:new("Foo-Bar:Baz%20Quux"))
is("foo-bar", uri:scheme())
end
function test_change_scheme ()
local uri = assert(URI:new("x-foo://example.com/blah"))
is("x-foo://example.com/blah", tostring(uri))
is("x-foo", uri:scheme())
is("uri", uri._NAME)
-- x-foo -> x-bar
is("x-foo", uri:scheme("x-bar"))
is("x-bar", uri:scheme())
is("x-bar://example.com/blah", tostring(uri))
is("uri", uri._NAME)
-- x-bar -> http
is("x-bar", uri:scheme("http"))
is("http", uri:scheme())
is("http://example.com/blah", tostring(uri))
is("uri.http", uri._NAME)
-- http -> x-foo
is("http", uri:scheme("x-foo"))
is("x-foo", uri:scheme())
is("x-foo://example.com/blah", tostring(uri))
is("uri", uri._NAME)
end
function test_change_scheme_bad ()
local uri = assert(URI:new("x-foo://foo@bar/"))
-- Try changing the scheme to something invalid
assert_error("bad scheme '-x-foo'", function () uri:scheme("-x-foo") end)
assert_error("bad scheme 'x,foo'", function () uri:scheme("x,foo") end)
assert_error("bad scheme 'x:foo'", function () uri:scheme("x:foo") end)
assert_error("bad scheme 'x-foo:'", function () uri:scheme("x-foo:") end)
-- Change to valid scheme, but where the rest of the URI is not valid for it
assert_error("bad HTTP URI", function () uri:scheme("http") end)
-- Original URI should be left unchanged
is("x-foo://foo@bar/", tostring(uri))
is("x-foo", uri:scheme())
is("uri", uri._NAME)
end
function test_auth_userinfo ()
local uri = assert(URI:new("X://a-zA-Z09!$:&%40@FOO.com:80/"))
is("x://a-zA-Z09!$:&%40@foo.com:80/", tostring(uri))
is("x", uri:scheme())
is("a-zA-Z09!$:&%40", uri:userinfo())
is("foo.com", uri:host())
is(80, uri:port())
end
function test_auth_userinfo_bad ()
is_bad_uri("bad character in userinfo", "x-a://foo^bar@example.com/")
end
function test_auth_set_userinfo ()
local uri = assert(URI:new("X-foo://user:pass@FOO.com:80/"))
is("user:pass", uri:userinfo("newuserinfo"))
is("newuserinfo", uri:userinfo())
is("x-foo://newuserinfo@foo.com:80/", tostring(uri))
-- Userinfo should be supplied already percent-encoded, but the percent
-- encoding should be normalized.
is("newuserinfo", uri:userinfo("foo%3abar%3A:%78"))
is("foo%3Abar%3A:x", uri:userinfo())
-- It should be OK to use more than one colon in userinfo for generic URIs,
-- although not for ones which specificly divide it into username:password.
is("foo%3Abar%3A:x", uri:userinfo("foo:bar:baz::"))
is("foo:bar:baz::", uri:userinfo())
end
function test_auth_set_bad_userinfo ()
local uri = assert(URI:new("X-foo://user:pass@FOO.com:80/"))
assert_error("/ in userinfo", function () uri:userinfo("foo/bar") end)
assert_error("@ in userinfo", function () uri:userinfo("foo@bar") end)
is("user:pass", uri:userinfo())
is("x-foo://user:pass@foo.com:80/", tostring(uri))
end
function test_auth_reg_name ()
local uri = assert(URI:new("x://azAZ0-9--foo.bqr_baz~%20!$;/"))
-- TODO - %20 should probably be rejected. Apparently only UTF-8 pctenc
-- should be produced, so after unescaping unreserved chars there should
-- be nothing left percent encoded other than valid UTF-8 sequences. If
-- that's right I could safely decode the host before returning it.
is("azaz0-9--foo.bqr_baz~%20!$;", uri:host())
end
function test_auth_ip4 ()
local uri = assert(URI:new("x://0.0.0.0/path"))
is("0.0.0.0", uri:host())
uri = assert(URI:new("x://192.168.0.1/path"))
is("192.168.0.1", uri:host())
uri = assert(URI:new("x://255.255.255.255/path"))
is("255.255.255.255", uri:host())
end
function test_auth_ip4_or_reg_name_bad ()
is_bad_uri("bad character in host part", "x://foo:bar/")
end
function test_auth_ip6 ()
-- The example addresses in here are all from RFC 4291 section 2.2, except
-- that they get normalized to lowercase here in the results.
local uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]"))
is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]/"))
is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:"))
is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:/"))
is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
uri = assert(URI:new("x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:0/"))
is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
uri = assert(URI:new("x://y:z@[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]:80/"))
is("[abcd:ef01:2345:6789:abcd:ef01:2345:6789]", uri:host())
uri = assert(URI:new("x://[2001:DB8:0:0:8:800:200C:417A]/"))
is("[2001:db8:0:0:8:800:200c:417a]", uri:host())
uri = assert(URI:new("x://[FF01:0:0:0:0:0:0:101]/"))
is("[ff01:0:0:0:0:0:0:101]", uri:host())
uri = assert(URI:new("x://[ff01::101]/"))
is("[ff01::101]", uri:host())
uri = assert(URI:new("x://[0:0:0:0:0:0:0:1]/"))
is("[0:0:0:0:0:0:0:1]", uri:host())
uri = assert(URI:new("x://[::1]/"))
is("[::1]", uri:host())
uri = assert(URI:new("x://[0:0:0:0:0:0:0:0]/"))
is("[0:0:0:0:0:0:0:0]", uri:host())
uri = assert(URI:new("x://[0:0:0:0:0:0:13.1.68.3]/"))
is("[0:0:0:0:0:0:13.1.68.3]", uri:host())
uri = assert(URI:new("x://[::13.1.68.3]/"))
is("[::13.1.68.3]", uri:host())
uri = assert(URI:new("x://[0:0:0:0:0:FFFF:129.144.52.38]/"))
is("[0:0:0:0:0:ffff:129.144.52.38]", uri:host())
uri = assert(URI:new("x://[::FFFF:129.144.52.38]/"))
is("[::ffff:129.144.52.38]", uri:host())
-- These try all the cominations of abbreviating using '::'.
uri = assert(URI:new("x://[08:19:2a:3B:4c:5D:6e:7F]/"))
is("[08:19:2a:3b:4c:5d:6e:7f]", uri:host())
uri = assert(URI:new("x://[::19:2a:3B:4c:5D:6e:7F]/"))
is("[::19:2a:3b:4c:5d:6e:7f]", uri:host())
uri = assert(URI:new("x://[::2a:3B:4c:5D:6e:7F]/"))
is("[::2a:3b:4c:5d:6e:7f]", uri:host())
uri = assert(URI:new("x://[::3B:4c:5D:6e:7F]/"))
is("[::3b:4c:5d:6e:7f]", uri:host())
uri = assert(URI:new("x://[::4c:5D:6e:7F]/"))
is("[::4c:5d:6e:7f]", uri:host())
uri = assert(URI:new("x://[::5D:6e:7F]/"))
is("[::5d:6e:7f]", uri:host())
uri = assert(URI:new("x://[::6e:7F]/"))
is("[::6e:7f]", uri:host())
uri = assert(URI:new("x://[::7F]/"))
is("[::7f]", uri:host())
uri = assert(URI:new("x://[::]/"))
is("[::]", uri:host())
uri = assert(URI:new("x://[08::]/"))
is("[08::]", uri:host())
uri = assert(URI:new("x://[08:19::]/"))
is("[08:19::]", uri:host())
uri = assert(URI:new("x://[08:19:2a::]/"))
is("[08:19:2a::]", uri:host())
uri = assert(URI:new("x://[08:19:2a:3B::]/"))
is("[08:19:2a:3b::]", uri:host())
uri = assert(URI:new("x://[08:19:2a:3B:4c::]/"))
is("[08:19:2a:3b:4c::]", uri:host())
uri = assert(URI:new("x://[08:19:2a:3B:4c:5D::]/"))
is("[08:19:2a:3b:4c:5d::]", uri:host())
uri = assert(URI:new("x://[08:19:2a:3B:4c:5D:6e::]/"))
is("[08:19:2a:3b:4c:5d:6e::]", uri:host())
-- Try extremes of good IPv4 addresses mapped to IPv6.
uri = assert(URI:new("x://[::FFFF:0.0.0.0]/path"))
is("[::ffff:0.0.0.0]", uri:host())
uri = assert(URI:new("x://[::ffff:255.255.255.255]/path"))
is("[::ffff:255.255.255.255]", uri:host())
end
function test_auth_ip6_bad ()
is_bad_uri("empty brackets", "x://[]")
is_bad_uri("just colon", "x://[:]")
is_bad_uri("3 colons only", "x://[:::]")
is_bad_uri("3 colons at start", "x://[:::1234]")
is_bad_uri("3 colons at end", "x://[1234:::]")
is_bad_uri("3 colons in middle", "x://[1234:::5678]")
is_bad_uri("non-hex char", "x://[ABCD:EF01:2345:6789:ABCD:EG01:2345:6789]")
is_bad_uri("chunk too big",
"x://[ABCD:EF01:2345:6789:ABCD:EFF01:2345:6789]")
is_bad_uri("too many chunks",
"x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789:1]")
is_bad_uri("not enough chunks", "x://[ABCD:EF01:2345:6789:ABCD:EF01:2345]")
is_bad_uri("too many chunks with ellipsis in middle",
"x://[ABCD:EF01:2345:6789:ABCD::EF01:2345:6789]")
is_bad_uri("too many chunks with ellipsis at end",
"x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789::]")
is_bad_uri("too many chunks with ellipsis at start",
"x://[::ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]")
is_bad_uri("two elipses, middle and end",
"x://[EF01:2345::6789:ABCD:EF01:2345::]")
is_bad_uri("two elipses, start and middle",
"x://[::EF01:2345::6789:ABCD:EF01:2345]")
is_bad_uri("two elipses, both ends",
"x://[::EF01:2345:6789:ABCD:EF01:2345::]")
is_bad_uri("two elipses, both middle",
"x://[EF01:2345::6789:ABCD:::EF01:2345]")
is_bad_uri("extra colon at start",
"x://[:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789]")
is_bad_uri("missing chunk at start",
"x://[:EF01:2345:6789:ABCD:EF01:2345:6789]")
is_bad_uri("extra colon at end",
"x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:6789:]")
is_bad_uri("missing chunk at end",
"x://[ABCD:EF01:2345:6789:ABCD:EF01:2345:]")
-- Bad IPv4 addresses mapped to IPv6.
is_bad_uri("octet 1 too big", "x://[::FFFF:256.2.3.4]/")
is_bad_uri("octet 2 too big", "x://[::FFFF:1.256.3.4]/")
is_bad_uri("octet 3 too big", "x://[::FFFF:1.2.256.4]/")
is_bad_uri("octet 4 too big", "x://[::FFFF:1.2.3.256]/")
is_bad_uri("octet 1 leading zeroes", "x://[::FFFF:01.2.3.4]/")
is_bad_uri("octet 2 leading zeroes", "x://[::FFFF:1.02.3.4]/")
is_bad_uri("octet 3 leading zeroes", "x://[::FFFF:1.2.03.4]/")
is_bad_uri("octet 4 leading zeroes", "x://[::FFFF:1.2.3.04]/")
is_bad_uri("only 2 octets", "x://[::FFFF:1.2]/")
is_bad_uri("only 3 octets", "x://[::FFFF:1.2.3]/")
is_bad_uri("5 octets", "x://[::FFFF:1.2.3.4.5]/")
end
function test_auth_ipvfuture ()
local uri = assert(URI:new("x://[v123456789ABCdef.foo=bar]/"))
is("[v123456789abcdef.foo=bar]", uri:host())
end
function test_auth_ipvfuture_bad ()
is_bad_uri("missing dot", "x://[v999]")
is_bad_uri("missing hex num", "x://[v.foo]")
is_bad_uri("missing bit after dot", "x://[v999.]")
is_bad_uri("bad character in hex num", "x://[v99g.foo]")
is_bad_uri("bad character after dot", "x://[v999.foo:bar]")
end
function test_auth_set_host ()
local uri = assert(URI:new("x-a://host/path"))
is("host", uri:host("FOO.BAR"))
is("x-a://foo.bar/path", tostring(uri))
is("foo.bar", uri:host("[::6e:7F]"))
is("x-a://[::6e:7f]/path", tostring(uri))
is("[::6e:7f]", uri:host("[v7F.foo=BAR]"))
is("x-a://[v7f.foo=bar]/path", tostring(uri))
is("[v7f.foo=bar]", uri:host(""))
is("x-a:///path", tostring(uri))
is("", uri:host(nil))
is(nil, uri:host())
is("x-a:/path", tostring(uri))
end
function test_auth_set_host_bad ()
local uri = assert(URI:new("x-a://host/path"))
assert_error("bad char in host", function () uri:host("foo^bar") end)
assert_error("invalid IPv6 host", function () uri:host("[::3G]") end)
assert_error("invalid IPvFuture host", function () uri:host("[v7.]") end)
is("host", uri:host())
is("x-a://host/path", tostring(uri))
-- There must be a hsot when there is a userinfo or port.
uri = assert(URI:new("x-a://foo@/"))
assert_error("userinfo but no host", function () uri:host(nil) end)
is("x-a://foo@/", tostring(uri))
uri = assert(URI:new("x-a://:123/"))
assert_error("port but no host", function () uri:host(nil) end)
is("x-a://:123/", tostring(uri))
end
function test_auth_port ()
local uri = assert(URI:new("x://localhost:0/path"))
is(0, uri:port())
uri = assert(URI:new("x://localhost:0"))
is(0, uri:port())
uri = assert(URI:new("x://foo:bar@localhost:0"))
is(0, uri:port())
uri = assert(URI:new("x://localhost:00/path"))
is(0, uri:port())
uri = assert(URI:new("x://localhost:00"))
is(0, uri:port())
uri = assert(URI:new("x://foo:bar@localhost:00"))
is(0, uri:port())
uri = assert(URI:new("x://localhost:54321/path"))
is(54321, uri:port())
uri = assert(URI:new("x://localhost:54321"))
is(54321, uri:port())
uri = assert(URI:new("x://foo:bar@localhost:54321"))
is(54321, uri:port())
uri = assert(URI:new("x://foo:bar@localhost:"))
is(nil, uri:port())
uri = assert(URI:new("x://foo:bar@localhost:/"))
is(nil, uri:port())
uri = assert(URI:new("x://foo:bar@localhost"))
is(nil, uri:port())
uri = assert(URI:new("x://foo:bar@localhost/"))
is(nil, uri:port())
end
function test_auth_set_port ()
-- Test unusual but valid values for port.
local uri = assert(URI:new("x://localhost/path"))
is(nil, uri:port("12345")) -- string
is(12345, uri:port())
is("x://localhost:12345/path", tostring(uri))
uri = assert(URI:new("x://localhost/path"))
is(nil, uri:port(12345.0)) -- float
is(12345, uri:port())
is("x://localhost:12345/path", tostring(uri))
end
function test_auth_set_port_without_host ()
local uri = assert(URI:new("x:///path"))
is(nil, uri:port(80))
is(80, uri:port())
is("", uri:host())
is("x://:80/path", tostring(uri))
uri = assert(URI:new("x:/path"))
is(nil, uri:port(80))
is(80, uri:port())
is("", uri:host())
is("x://:80/path", tostring(uri))
end
function test_auth_set_port_bad ()
local uri = assert(URI:new("x://localhost:54321/path"))
assert_error("negative port number", function () uri:port(-23) end)
assert_error("port not integer", function () uri:port(23.00001) end)
assert_error("string not number", function () uri:port("x") end)
assert_error("string not all number", function () uri:port("x23") end)
assert_error("string negative number", function () uri:port("-23") end)
assert_error("string empty", function () uri:port("") end)
is(54321, uri:port())
is("x://localhost:54321/path", tostring(uri))
end
function test_path ()
local uri = assert(URI:new("x:"))
is("", uri:path())
uri = assert(URI:new("x:?"))
is("", uri:path())
uri = assert(URI:new("x:#"))
is("", uri:path())
uri = assert(URI:new("x:/"))
is("/", uri:path())
uri = assert(URI:new("x://"))
is("", uri:path())
uri = assert(URI:new("x://?"))
is("", uri:path())
uri = assert(URI:new("x://#"))
is("", uri:path())
uri = assert(URI:new("x:///"))
is("/", uri:path())
uri = assert(URI:new("x:////"))
is("//", uri:path())
uri = assert(URI:new("x:foo"))
is("foo", uri:path())
uri = assert(URI:new("x:/foo"))
is("/foo", uri:path())
uri = assert(URI:new("x://foo"))
is("", uri:path())
uri = assert(URI:new("x://foo?"))
is("", uri:path())
uri = assert(URI:new("x://foo#"))
is("", uri:path())
uri = assert(URI:new("x:///foo"))
is("/foo", uri:path())
uri = assert(URI:new("x:////foo"))
is("//foo", uri:path())
uri = assert(URI:new("x://foo/"))
is("/", uri:path())
uri = assert(URI:new("x://foo/bar"))
is("/bar", uri:path())
end
function test_path_bad ()
is_bad_uri("bad character in path", "x-a://host/^/")
end
function test_set_path_without_auth ()
local uri = assert(URI:new("x:blah"))
is("blah", uri:path("frob%25%3a%78/%2F"))
is("frob%25%3Ax/%2F", uri:path("/foo/bar"))
is("/foo/bar", uri:path("//foo//bar"))
is("/%2Ffoo//bar", uri:path("x ?#\"\0\127\255"))
is("x%20%3F%23%22%00%7F%FF", uri:path(""))
is("", uri:path(nil))
is("", uri:path())
is("x:", tostring(uri))
end
function test_set_path_with_auth ()
local uri = assert(URI:new("x://host/wibble"))
is("/wibble", uri:path("/foo/bar"))
is("/foo/bar", uri:path("//foo//bar"))
is("//foo//bar", uri:path(nil))
is("", uri:path(""))
is("", uri:path())
is("x://host", tostring(uri))
end
function test_set_path_bad ()
local uri = assert(URI:new("x://host/wibble"))
tostring(uri)
assert_error("with authority, path must start with /",
function () uri:path("foo") end)
assert_error("bad %-encoding, % at end", function () uri:path("foo%") end)
assert_error("bad %-encoding, %2 at end", function () uri:path("foo%2") end)
assert_error("bad %-encoding, %gf", function () uri:path("%gf") end)
assert_error("bad %-encoding, %fg", function () uri:path("%fg") end)
is("/wibble", uri:path())
is("x://host/wibble", tostring(uri))
end
function test_query ()
local uri = assert(URI:new("x:?"))
is("", uri:query())
uri = assert(URI:new("x:"))
is(nil, uri:query())
uri = assert(URI:new("x:/foo"))
is(nil, uri:query())
uri = assert(URI:new("x:/foo#"))
is(nil, uri:query())
uri = assert(URI:new("x:/foo#bar?baz"))
is(nil, uri:query())
uri = assert(URI:new("x:/foo?"))
is("", uri:query())
uri = assert(URI:new("x://foo?"))
is("", uri:query())
uri = assert(URI:new("x://foo/?"))
is("", uri:query())
uri = assert(URI:new("x:/foo?bar"))
is("bar", uri:query())
uri = assert(URI:new("x:?foo?bar?"))
is("foo?bar?", uri:query())
uri = assert(URI:new("x:?foo?bar?#quux?frob"))
is("foo?bar?", uri:query())
uri = assert(URI:new("x://foo/bar%3Fbaz?"))
is("", uri:query())
uri = assert(URI:new("x:%3F?foo"))
is("%3F", uri:path())
is("foo", uri:query())
end
function test_query_bad ()
is_bad_uri("bad character in query", "x-a://host/path/?foo^bar")
end
function test_set_query ()
local uri = assert(URI:new("x://host/path"))
is(nil, uri:query("foo/bar?baz"))
is("x://host/path?foo/bar?baz", tostring(uri))
is("foo/bar?baz", uri:query(""))
is("x://host/path?", tostring(uri))
is("", uri:query("foo^bar#baz"))
is("x://host/path?foo%5Ebar%23baz", tostring(uri))
is("foo%5Ebar%23baz", uri:query(nil))
is(nil, uri:query())
is("x://host/path", tostring(uri))
end
function test_fragment ()
local uri = assert(URI:new("x:"))
is(nil, uri:fragment())
uri = assert(URI:new("x:#"))
is("", uri:fragment())
uri = assert(URI:new("x://#"))
is("", uri:fragment())
uri = assert(URI:new("x:///#"))
is("", uri:fragment())
uri = assert(URI:new("x:////#"))
is("", uri:fragment())
uri = assert(URI:new("x:#foo"))
is("foo", uri:fragment())
uri = assert(URI:new("x:%23#foo"))
is("%23", uri:path())
is("foo", uri:fragment())
uri = assert(URI:new("x:?foo?bar?#quux?frob"))
is("quux?frob", uri:fragment())
end
function test_fragment_bad ()
is_bad_uri("bad character in fragment", "x-a://host/path/#foo^bar")
end
function test_set_fragment ()
local uri = assert(URI:new("x://host/path"))
is(nil, uri:fragment("foo/bar#baz"))
is("x://host/path#foo/bar%23baz", tostring(uri))
is("foo/bar%23baz", uri:fragment(""))
is("x://host/path#", tostring(uri))
is("", uri:fragment("foo^bar?baz"))
is("x://host/path#foo%5Ebar?baz", tostring(uri))
is("foo%5Ebar?baz", uri:fragment(nil))
is(nil, uri:fragment())
is("x://host/path", tostring(uri))
end
function test_bad_usage ()
assert_error("missing uri arg", function () URI:new() end)
assert_error("nil uri arg", function () URI:new(nil) end)
end
function test_clone_with_new ()
-- Test cloning with as many components set as possible.
local uri = assert(URI:new("x-foo://user:pass@bar.com:123/blah?q#frag"))
tostring(uri)
local clone = URI:new(uri)
assert_table(clone)
is("x-foo://user:pass@bar.com:123/blah?q#frag", tostring(uri))
is("x-foo://user:pass@bar.com:123/blah?q#frag", tostring(clone))
is("uri", getmetatable(uri)._NAME)
is("uri", getmetatable(clone)._NAME)
-- Test cloning with less stuff specified, but not in the base class.
uri = assert(URI:new("http://example.com/"))
clone = URI:new(uri)
assert_table(clone)
is("http://example.com/", tostring(uri))
is("http://example.com/", tostring(clone))
is("uri.http", getmetatable(uri)._NAME)
is("uri.http", getmetatable(clone)._NAME)
end
function test_set_uri ()
local uri = assert(URI:new("x-foo://user:pass@bar.com:123/blah?q#frag"))
is("x-foo://user:pass@bar.com:123/blah?q#frag",
uri:uri("http://example.com:81/blah2?q2#frag2"))
is("http://example.com:81/blah2?q2#frag2", uri:uri())
is("uri.http", getmetatable(uri)._NAME)
is("http", uri:scheme())
is("q2", uri:query())
is("http://example.com:81/blah2?q2#frag2", uri:uri("Urn:X-FOO:bar"))
is("uri.urn", getmetatable(uri)._NAME)
is("x-foo", uri:nid())
is("urn:x-foo:bar", tostring(uri))
end
function test_set_uri_bad ()
local uri = assert(URI:new("x-foo://user:pass@bar.com:123/blah?q#frag"))
assert_error("can't set URI to nil", function () uri:uri(nil) end)
assert_error("invalid authority", function () uri:uri("foo://@@") end)
is("x-foo://user:pass@bar.com:123/blah?q#frag", uri:uri())
is("uri", getmetatable(uri)._NAME)
is("x-foo", uri:scheme())
end
function test_eq ()
local uri1str, uri2str = "x-a://host/foo", "x-a://host/bar"
local uri1obj, uri2obj = assert(URI:new(uri1str)), assert(URI:new(uri2str))
assert_true(URI.eq(uri1str, uri1str), "str == str")
assert_false(URI.eq(uri1str, uri2str), "str ~= str")
assert_true(URI.eq(uri1str, uri1obj), "str == obj")
assert_false(URI.eq(uri1str, uri2obj), "str ~= obj")
assert_true(URI.eq(uri1obj, uri1str), "obj == str")
assert_false(URI.eq(uri1obj, uri2str), "obj ~= str")
assert_true(URI.eq(uri1obj, uri1obj), "obj == obj")
assert_false(URI.eq(uri1obj, uri2obj), "obj ~= obj")
end
function test_eq_bad_uri ()
-- Check that an exception is thrown when 'eq' is given a bad URI string,
-- and also that it's not just the error from trying to call the 'uri'
-- method on nil, because that won't be very helpful to the caller.
local ok, err = pcall(URI.eq, "^", "x-a://x/")
assert_false(ok)
assert_not_match("a nil value", err)
ok, err = pcall(URI.eq, "x-a://x/", "^")
assert_false(ok)
assert_not_match("a nil value", err)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,36 @@
require "lunit"
module("test.pristine", lunit.testcase, package.seeall)
function test_no_global_clobbering ()
local globals = {}
for key in pairs(_G) do globals[key] = true end
-- Load all the modules for the different types of URIs, in case any one
-- of those treads on a global. I keep them around in a table to make
-- sure they're all loaded at the same time, just in case that does
-- anything interesting.
local schemes = {
"_login", "_relative", "_util", "data",
"file", "file.unix", "file.win32",
"ftp", "http", "https",
"pop", "rtsp", "rtspu", "telnet",
"urn", "urn.isbn", "urn.issn", "urn.oid"
}
local loaded = {}
local URI = require "uri"
for _, name in ipairs(schemes) do
loaded[name] = require("uri." .. name)
end
for key in pairs(_G) do
lunit.assert_not_nil(globals[key],
"global '" .. key .. "' created by lib")
end
for key in pairs(globals) do
lunit.assert_not_nil(_G[key],
"global '" .. key .. "' destroyed by lib")
end
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,54 @@
require "uri-test"
local URI = require "uri"
module("test.relative", lunit.testcase, package.seeall)
local function test_rel (input, userinfo, host, port, path, query, frag,
expected)
local uri = assert(URI:new(input))
assert_true(uri:is_relative())
is("uri._relative", getmetatable(uri)._NAME)
is(nil, uri:scheme())
is(userinfo, uri:userinfo())
is(host, uri:host())
is(port, uri:port())
is(path, uri:path())
is(query, uri:query())
is(frag, uri:fragment())
if not expected then expected = input end
is(expected, uri:uri())
is(expected, tostring(uri))
end
function test_relative ()
test_rel("", nil, nil, nil, "", nil, nil)
test_rel("foo/bar", nil, nil, nil, "foo/bar", nil, nil)
test_rel("/foo/bar", nil, nil, nil, "/foo/bar", nil, nil)
test_rel("?query", nil, nil, nil, "", "query", nil)
test_rel("?", nil, nil, nil, "", "", nil)
test_rel("#foo", nil, nil, nil, "", nil, "foo")
test_rel("#", nil, nil, nil, "", nil, "")
test_rel("?q#f", nil, nil, nil, "", "q", "f")
test_rel("?#", nil, nil, nil, "", "", "")
test_rel("foo?q#f", nil, nil, nil, "foo", "q", "f")
test_rel("//host.com", nil, "host.com", nil, "", nil, nil)
test_rel("//host.com/blah?q#f", nil, "host.com", nil, "/blah", "q", "f")
test_rel("//host.com:123/blah?q#f", nil, "host.com", 123, "/blah", "q", "f")
test_rel("//u:p@host.com:123/blah?q#f",
"u:p", "host.com", 123, "/blah", "q", "f")
-- Paths shouldn't be normalized in a relative reference, only after it
-- has been used to create an absolute one.
test_rel("./foo/bar", nil, nil, nil, "./foo/bar", nil, nil)
test_rel("././foo/./bar", nil, nil, nil, "././foo/./bar", nil, nil)
test_rel("../foo/bar", nil, nil, nil, "../foo/bar", nil, nil)
test_rel("../../foo/../bar", nil, nil, nil, "../../foo/../bar", nil, nil)
end
function test_bad_usage ()
local uri = assert(URI:new("foo"))
assert_error("set scheme on relative ref",
function () uri:scheme("x-foo") end)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,201 @@
require "uri-test"
local URI = require "uri"
module("test.resolve", lunit.testcase, package.seeall)
-- Test data from RFC 3986. The 'http' prefix has been changed throughout
-- to 'x-foo' so as not to trigger any scheme-specific normalization.
local resolve_tests = {
-- 5.4.1. Normal Examples
["g:h"] = "g:h",
["g"] = "x-foo://a/b/c/g",
["./g"] = "x-foo://a/b/c/g",
["g/"] = "x-foo://a/b/c/g/",
["/g"] = "x-foo://a/g",
["//g"] = "x-foo://g",
["?y"] = "x-foo://a/b/c/d;p?y",
["g?y"] = "x-foo://a/b/c/g?y",
["#s"] = "x-foo://a/b/c/d;p?q#s",
["g#s"] = "x-foo://a/b/c/g#s",
["g?y#s"] = "x-foo://a/b/c/g?y#s",
[";x"] = "x-foo://a/b/c/;x",
["g;x"] = "x-foo://a/b/c/g;x",
["g;x?y#s"] = "x-foo://a/b/c/g;x?y#s",
[""] = "x-foo://a/b/c/d;p?q",
["."] = "x-foo://a/b/c/",
["./"] = "x-foo://a/b/c/",
[".."] = "x-foo://a/b/",
["../"] = "x-foo://a/b/",
["../g"] = "x-foo://a/b/g",
["../.."] = "x-foo://a/",
["../../"] = "x-foo://a/",
["../../g"] = "x-foo://a/g",
-- 5.4.2. Abnormal Examples
["../../../g"] = "x-foo://a/g",
["../../../../g"] = "x-foo://a/g",
["/./g"] = "x-foo://a/g",
["/../g"] = "x-foo://a/g",
["g."] = "x-foo://a/b/c/g.",
[".g"] = "x-foo://a/b/c/.g",
["g.."] = "x-foo://a/b/c/g..",
["..g"] = "x-foo://a/b/c/..g",
["./../g"] = "x-foo://a/b/g",
["./g/."] = "x-foo://a/b/c/g/",
["g/./h"] = "x-foo://a/b/c/g/h",
["g/../h"] = "x-foo://a/b/c/h",
["g;x=1/./y"] = "x-foo://a/b/c/g;x=1/y",
["g;x=1/../y"] = "x-foo://a/b/c/y",
["g?y/./x"] = "x-foo://a/b/c/g?y/./x",
["g?y/../x"] = "x-foo://a/b/c/g?y/../x",
["g#s/./x"] = "x-foo://a/b/c/g#s/./x",
["g#s/../x"] = "x-foo://a/b/c/g#s/../x",
["x-foo:g"] = "x-foo:g",
-- Some extra tests for good measure
["#foo?"] = "x-foo://a/b/c/d;p?q#foo?",
["?#foo"] = "x-foo://a/b/c/d;p?#foo",
}
local function test_abs_rel (base, uref, expect)
local bad = false
-- Test 'resolve' method with object as argument.
local u = assert(URI:new(uref))
local b = assert(URI:new(base))
u:resolve(b)
local got = tostring(u)
if got ~= expect then
bad = true
print("URI:new(" .. uref .. "):resolve(URI:new(" .. base .. ") ===> " ..
expect .. " (not " .. got .. ")")
end
-- Test 'resolve' method with string as argument.
u = assert(URI:new(uref))
u:resolve(base)
local got = tostring(u)
if got ~= expect then
bad = true
print("URI:new(" .. uref .. "):resolve(URI:new(" .. base .. ") ===> " ..
expect .. " (not " .. got .. ")")
end
-- Test resolving relative URI using the constructor.
local u = assert(URI:new(uref, base))
local got = tostring(u)
if got ~= expect then
bad = true
print("URI:new(" .. uref .. ", " .. base .. ") ==> " .. expect ..
" (not " .. got .. ")")
end
return bad
end
function test_resolve ()
local base = "x-foo://a/b/c/d;p?q"
local testno = 1
local bad = false
for rel, abs in pairs(resolve_tests) do
if test_abs_rel(base, rel, abs) then bad = true end
end
if bad then fail("one of the checks went wrong") end
end
function test_resolve_error ()
local base = assert(URI:new("urn:oid:1.2.3"))
local uri = assert(URI:new("not-valid-path-for-urn"))
-- The 'resolve' method should throw an exception if the absolute URI
-- that results from the resolution would be invalid.
assert_error("calling resolve() creates invalid URI",
function () uri:resolve(base) end)
assert_true(uri:is_relative())
is("not-valid-path-for-urn", tostring(uri))
-- But the constructor should return an error in its normal fashion.
local ok, err = URI:new(uri, base)
assert_nil(ok)
assert_string(err)
end
local relativize_tests = {
-- Empty path if the path is the same as the base URI's.
{ "http://ex/", "http://ex/", "" },
{ "http://ex/a/b", "http://ex/a/b", "" },
{ "http://ex/a/b/", "http://ex/a/b/", "" },
-- Absolute path if the base URI's path doesn't help.
{ "http://ex/", "http://ex/a/b", "/" },
{ "http://ex/", "http://ex/a/b/", "/" },
{ "http://ex/x/y", "http://ex/", "/x/y" },
{ "http://ex/x/y/", "http://ex/", "/x/y/" },
{ "http://ex/x", "http://ex/a", "/x" },
{ "http://ex/x", "http://ex/a/", "/x" },
{ "http://ex/x/", "http://ex/a", "/x/" },
{ "http://ex/x/", "http://ex/a/", "/x/" },
{ "http://ex/x/y", "http://ex/a/b", "/x/y" },
{ "http://ex/x/y", "http://ex/a/b/", "/x/y" },
{ "http://ex/x/y/", "http://ex/a/b", "/x/y/" },
{ "http://ex/x/y/", "http://ex/a/b/", "/x/y/" },
-- Add to the end of the base path.
{ "x-a://ex/a/b/c", "x-a://ex/a/b/", "c" },
{ "x-a://ex/a/b/c/", "x-a://ex/a/b/", "c/" },
{ "x-a://ex/a/b/c/d", "x-a://ex/a/b/", "c/d" },
{ "x-a://ex/a/b/c/d/", "x-a://ex/a/b/", "c/d/" },
{ "x-a://ex/a/b/c/d/e", "x-a://ex/a/b/", "c/d/e" },
{ "x-a://ex/a/b/c:foo/d/e", "x-a://ex/a/b/", "./c:foo/d/e" },
-- Change last segment in base path, and add to it.
{ "x-a://ex/a/b/", "x-a://ex/a/b/c", "./" },
{ "x-a://ex/a/b/x", "x-a://ex/a/b/c", "x" },
{ "x-a://ex/a/b/x/", "x-a://ex/a/b/c", "x/" },
{ "x-a://ex/a/b/x/y", "x-a://ex/a/b/c", "x/y" },
{ "x-a://ex/a/b/x:foo/y", "x-a://ex/a/b/c", "./x:foo/y" },
-- Use '..' segments.
{ "x-a://ex/a/b/c", "x-a://ex/a/b/c/d", "../c" },
{ "x-a://ex/a/b/c", "x-a://ex/a/b/c/", "../c" },
{ "x-a://ex/a/b/", "x-a://ex/a/b/c/", "../" },
{ "x-a://ex/a/b/", "x-a://ex/a/b/c/d", "../" },
{ "x-a://ex/a/b", "x-a://ex/a/b/c/", "../../b" },
{ "x-a://ex/a/b", "x-a://ex/a/b/c/d", "../../b" },
{ "x-a://ex/a/", "x-a://ex/a/b/c/", "../../" },
{ "x-a://ex/a/", "x-a://ex/a/b/c/d", "../../" },
-- Preserve query and fragment parts.
{ "http://ex/a/b", "http://ex/a/b?baseq#basef", "b" },
{ "http://ex/a/b:c", "http://ex/a/b:c?baseq#basef", "./b:c" },
{ "http://ex/a/b?", "http://ex/a/b?baseq#basef", "?" },
{ "http://ex/a/b?foo", "http://ex/a/b?baseq#basef", "?foo" },
{ "http://ex/a/b?foo#", "http://ex/a/b?baseq#basef", "?foo#" },
{ "http://ex/a/b?foo#bar", "http://ex/a/b?baseq#basef", "?foo#bar" },
{ "http://ex/a/b#bar", "http://ex/a/b?baseq#basef", "b#bar" },
{ "http://ex/a/b:foo#bar", "http://ex/a/b:foo?baseq#basef", "./b:foo#bar" },
{ "http://ex/a/b:foo#bar", "http://ex/a/b:foo#basef", "#bar" },
}
function test_relativize ()
for _, test in ipairs(relativize_tests) do
local uri = assert(URI:new(test[1]))
uri:relativize(test[2])
is(test[3], tostring(uri))
-- Make sure it will resolve back to the original value.
uri:resolve(test[2])
is(test[1], tostring(uri))
end
end
function test_relativize_already_is ()
local uri = assert(URI:new("../foo"))
uri:relativize("http://host/")
is("../foo", tostring(uri))
end
function test_relativize_urn ()
local uri = assert(URI:new("urn:oid:1.2.3"))
uri:relativize("urn:oid:1")
is("urn:oid:1.2.3", tostring(uri))
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,92 @@
require "uri-test"
local Util = require "uri._util"
module("test.util", lunit.testcase, package.seeall)
function test_metadata ()
is("uri._util", Util._NAME)
end
function test_uri_encode ()
is("%7Cabc%E5", Util.uri_encode("|abc\229"))
is("a%62%63", Util.uri_encode("abc", "b-d"))
assert_nil(Util.uri_encode(nil))
end
function test_uri_decode ()
is("|abc\229", Util.uri_decode("%7Cabc%e5"))
is("@AB", Util.uri_decode("%40A%42"))
is("CDE", Util.uri_decode("CDE"))
end
function test_uri_decode ()
is("/%2F%25/..!%A1", Util.uri_decode("/%2F%25/%2e.%21%A1", "%-.!"))
end
function test_remove_dot_segments ()
is("/", Util.remove_dot_segments("/foo/../"))
is("/bar", Util.remove_dot_segments("/foo/./../bar"))
end
function test_split ()
local list
list = Util.split(";", "")
assert_array_shallow_equal({}, list)
list = Util.split(";", "foo")
assert_array_shallow_equal({"foo"}, list)
list = Util.split(";", "foo;bar")
assert_array_shallow_equal({"foo","bar"}, list)
list = Util.split(";", "foo;bar;baz")
assert_array_shallow_equal({"foo","bar","baz"}, list)
list = Util.split(";", ";")
assert_array_shallow_equal({"",""}, list)
list = Util.split(";", "foo;")
assert_array_shallow_equal({"foo",""}, list)
list = Util.split(";", ";foo")
assert_array_shallow_equal({"","foo"}, list)
-- TODO test with multi-char and more complex patterns
end
function test_split_with_max ()
local list
list = Util.split(";", "foo;bar;baz", 4)
assert_array_shallow_equal({"foo","bar","baz"}, list)
list = Util.split(";", "foo;bar;baz", 3)
assert_array_shallow_equal({"foo","bar","baz"}, list)
list = Util.split(";", "foo;bar;baz", 2)
assert_array_shallow_equal({"foo","bar;baz"}, list)
list = Util.split(";", "foo;bar;baz", 1)
assert_array_shallow_equal({"foo;bar;baz"}, list)
end
function test_attempt_require ()
local mod = Util.attempt_require("string")
assert_table(mod)
mod = Util.attempt_require("lua-module-which-doesn't-exist")
assert_nil(mod)
end
function test_subclass_of ()
local baseclass = {}
baseclass.__index = baseclass
baseclass.overridden = function () return "baseclass" end
baseclass.inherited = function () return "inherited" end
local subclass = {}
Util.subclass_of(subclass, baseclass)
subclass.overridden = function () return "subclass" end
assert(getmetatable(subclass) == baseclass)
assert(subclass._SUPER == baseclass)
local baseobject, subobject = {}, {}
setmetatable(baseobject, baseclass)
setmetatable(subobject, subclass)
is("baseclass", baseobject:overridden())
is("subclass", subobject:overridden())
is("inherited", baseobject:inherited())
is("inherited", subobject:inherited())
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,125 @@
require "uri-test"
local URI = require "uri"
local Util = require "uri._util"
local Filter = Util.attempt_require("datafilter")
module("test.data", lunit.testcase, package.seeall)
function test_data_uri_encoded ()
local uri = assert(URI:new("data:,A%20brief%20note"))
is("uri.data", uri._NAME)
is(",A%20brief%20note", uri:path())
is("data", uri:scheme())
is("text/plain;charset=US-ASCII", uri:data_media_type())
is("A brief note", uri:data_bytes())
local old = uri:data_bytes("F\229r-i-k\229l er tingen!")
is("A brief note", old)
is("data:,F%E5r-i-k%E5l%20er%20tingen!", tostring(uri))
old = uri:data_media_type("text/plain;charset=iso-8859-1")
is("text/plain;charset=US-ASCII", old)
is("data:text/plain;charset=iso-8859-1,F%E5r-i-k%E5l%20er%20tingen!",
tostring(uri))
end
function test_data_big_base64_chunk ()
local imgdata = "R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7"
local uri = assert(URI:new("data:image/gif;base64," .. imgdata))
is("image/gif", uri:data_media_type())
if Filter then
local gotdata = uri:data_bytes()
is(273, gotdata:len())
is(imgdata, Filter.base64_encode(gotdata))
end
end
function test_data_containing_commas ()
local uri = assert(URI:new("data:application/vnd-xxx-query,select_vcount,fcol_from_fieldtable/local"))
is("application/vnd-xxx-query", uri:data_media_type())
is("select_vcount,fcol_from_fieldtable/local", uri:data_bytes())
uri:data_bytes("")
is("data:application/vnd-xxx-query,", tostring(uri))
uri:data_bytes("a,b")
uri:data_media_type(nil)
is("data:,a,b", tostring(uri))
is("a,b", uri:data_bytes(nil))
is("", uri:data_bytes())
end
function test_automatic_selection_of_uri_or_base64_encoding ()
local uri = assert(URI:new("data:,"))
uri:data_bytes("")
is("data:,", tostring(uri))
uri:data_bytes(">")
is("data:,%3E", tostring(uri))
is(">", uri:data_bytes())
uri:data_bytes(">>>>>")
is("data:,%3E%3E%3E%3E%3E", tostring(uri))
if Filter then
uri:data_bytes(">>>>>>")
is("data:;base64,Pj4+Pj4+", tostring(uri))
uri:data_media_type("text/plain;foo=bar")
is("data:text/plain;foo=bar;base64,Pj4+Pj4+", tostring(uri))
uri:data_media_type("foo")
is("data:foo;base64,Pj4+Pj4+", tostring(uri))
uri:data_bytes((">"):rep(3000))
is("data:foo;base64," .. ("Pj4+"):rep(1000), tostring(uri))
is((">"):rep(3000), uri:data_bytes())
else
uri:data_bytes(">>>>>>")
is("data:,%3E%3E%3E%3E%3E%3E", tostring(uri))
uri:data_media_type("foo")
is("data:foo,%3E%3E%3E%3E%3E%3E", tostring(uri))
end
uri:data_media_type(nil)
uri:data_bytes(nil)
is("data:,", tostring(uri))
end
function test_bad_uri ()
is_bad_uri("missing comma", "data:foo")
is_bad_uri("no path at all", "data:")
is_bad_uri("has host", "data://host/,")
end
function test_set_path ()
local uri = assert(URI:new("data:image/gif,foobar"))
is("image/gif,foobar", uri:path("image/jpeg;foo=bar,x y,?"))
is("image/jpeg;foo=bar,x%20y,%3F", uri:path(",blah"))
is(",blah", uri:path(","))
is(",", uri:path())
is("data:,", tostring(uri))
end
function test_set_path_bad ()
local uri = assert(URI:new("data:image/gif,foobar"))
assert_error("no path", function () uri:path(nil) end)
assert_error("empty path", function () uri:path("") end)
assert_error("no comma", function () uri:path("foo;bar") end)
assert_error("bad base64 encoding", function () uri:path(";base64,x_0") end)
is("image/gif,foobar", uri:path())
is("data:image/gif,foobar", tostring(uri))
end
function test_set_disallowed_stuff ()
local uri = assert(URI:new("data:,"))
assert_error("can't set userinfo", function () uri:userinfo("x") end)
assert_error("can't set host", function () uri:host("x") end)
assert_error("can't set port", function () uri:port(23) end)
is("data:,", tostring(uri))
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,147 @@
require "uri-test"
local URI = require "uri"
local URIFile = require "uri.file"
module("test.file", lunit.testcase, package.seeall)
function test_normalize ()
test_norm("file:///foo", "file://LocalHost/foo")
test_norm("file:///", "file://localhost/")
test_norm("file:///", "file://localhost")
test_norm("file:///", "file://")
test_norm("file:///", "file:/")
test_norm("file:///foo", "file:/foo")
test_norm("file://foo/", "file://foo")
end
function test_invalid ()
is_bad_uri("just scheme", "file:")
is_bad_uri("scheme with relative path", "file:foo/bar")
end
function test_set_host ()
local uri = assert(URI:new("file:///foo"))
is("", uri:host())
is("", uri:host("LocalHost"))
is("file:///foo", tostring(uri))
is("", uri:host("host.name"))
is("file://host.name/foo", tostring(uri))
is("host.name", uri:host(""))
is("file:///foo", tostring(uri))
end
function test_set_path ()
local uri = assert(URI:new("file:///foo"))
is("/foo", uri:path())
is("/foo", uri:path(nil))
is("file:///", tostring(uri))
is("/", uri:path(""))
is("file:///", tostring(uri))
is("/", uri:path("/bar/frob"))
is("file:///bar/frob", tostring(uri))
is("/bar/frob", uri:path("/"))
is("file:///", tostring(uri))
end
function test_bad_usage ()
local uri = assert(URI:new("file:///foo"))
assert_error("nil host", function () uri:host(nil) end)
assert_error("set userinfo", function () uri:userinfo("foo") end)
assert_error("set port", function () uri:userinfo(23) end)
assert_error("set relative path", function () uri:userinfo("foo/") end)
end
local function uri_to_fs (os, uristr, expected)
local uri = assert(URI:new(uristr))
is(expected, uri:filesystem_path(os))
end
local function fs_to_uri (os, path, expected)
is(expected, tostring(URIFile.make_file_uri(path, os)))
end
function test_uri_to_fs_unix ()
uri_to_fs("unix", "file:///", "/")
uri_to_fs("unix", "file:///c:", "/c:")
uri_to_fs("unix", "file:///C:/", "/C:/")
uri_to_fs("unix", "file:///C:/Program%20Files", "/C:/Program Files")
uri_to_fs("unix", "file:///C:/Program%20Files/", "/C:/Program Files/")
uri_to_fs("unix", "file:///Program%20Files/", "/Program Files/")
end
function test_uri_to_fs_unix_bad ()
-- On Unix platforms, there's no equivalent of UNC paths.
local uri = assert(URI:new("file://laptop/My%20Documents/FileSchemeURIs.doc"))
assert_error("Unix path with host name",
function () uri:filesystem_path("unix") end)
-- Unix paths can't contain null bytes or encoded slashes.
uri = assert(URI:new("file:///frob/foo%00bar/quux"))
assert_error("Unix path with null byte",
function () uri:filesystem_path("unix") end)
uri = assert(URI:new("file:///frob/foo%2Fbar/quux"))
assert_error("Unix path with encoded slash",
function () uri:filesystem_path("unix") end)
end
function test_fs_to_uri_unix ()
fs_to_uri("unix", "/", "file:///")
fs_to_uri("unix", "//", "file:///")
fs_to_uri("unix", "///", "file:///")
fs_to_uri("unix", "/foo/bar", "file:///foo/bar")
fs_to_uri("unix", "/foo/bar/", "file:///foo/bar/")
fs_to_uri("unix", "//foo///bar//", "file:///foo/bar/")
fs_to_uri("unix", "/foo bar/%2F", "file:///foo%20bar/%252F")
end
function test_fs_to_uri_unix_bad ()
-- Relative paths can't be converted to URIs, because URIs are inherently
-- absolute.
assert_error("relative Unix path",
function () URIFile.make_file_uri("foo/bar", "unix") end)
assert_error("relative empty Unix path",
function () URIFile.make_file_uri("", "unix") end)
end
function test_uri_to_fs_win32 ()
uri_to_fs("win32", "file:///", "\\")
uri_to_fs("win32", "file:///c:", "c:\\")
uri_to_fs("win32", "file:///C:/", "C:\\")
uri_to_fs("win32", "file:///C:/Program%20Files", "C:\\Program Files")
uri_to_fs("win32", "file:///C:/Program%20Files/", "C:\\Program Files\\")
uri_to_fs("win32", "file:///Program%20Files/", "\\Program Files\\")
-- http://blogs.msdn.com/ie/archive/2006/12/06/file-uris-in-windows.aspx
uri_to_fs("win32", "file://laptop/My%20Documents/FileSchemeURIs.doc",
"\\\\laptop\\My Documents\\FileSchemeURIs.doc")
uri_to_fs("win32",
"file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc",
"C:\\Documents and Settings\\davris\\FileSchemeURIs.doc")
-- For backwards compatibility with deprecated way of indicating drives.
uri_to_fs("win32", "file:///c%7C", "c:\\")
uri_to_fs("win32", "file:///c%7C/", "c:\\")
uri_to_fs("win32", "file:///C%7C/foo/", "C:\\foo\\")
end
function test_fs_to_uri_win32 ()
fs_to_uri("win32", "", "file:///")
fs_to_uri("win32", "\\", "file:///")
fs_to_uri("win32", "c:", "file:///c:/")
fs_to_uri("win32", "C:\\", "file:///C:/")
fs_to_uri("win32", "C:/", "file:///C:/")
fs_to_uri("win32", "C:\\Program Files", "file:///C:/Program%20Files")
fs_to_uri("win32", "C:\\Program Files\\", "file:///C:/Program%20Files/")
fs_to_uri("win32", "C:/Program Files/", "file:///C:/Program%20Files/")
fs_to_uri("win32", "\\Program Files\\", "file:///Program%20Files/")
fs_to_uri("win32", "\\\\laptop\\My Documents\\FileSchemeURIs.doc",
"file://laptop/My%20Documents/FileSchemeURIs.doc")
fs_to_uri("win32", "c:\\foo bar\\%2F", "file:///c:/foo%20bar/%252F")
end
function test_convert_on_unknown_os ()
local uri = assert(URI:new("file:///foo"))
assert_error("filesystem_path, unknown os",
function () uri:filesystem_path("NonExistent") end)
assert_error("make_file_uri, unknown os",
function () URIFile.make_file_uri("/foo", "NonExistent") end)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,55 @@
require "uri-test"
local URI = require "uri"
module("test.ftp", lunit.testcase, package.seeall)
function test_ftp ()
local uri = assert(URI:new("ftp://ftp.example.com/path"))
is("ftp", uri:scheme())
is("ftp.example.com", uri:host())
is(21, uri:port())
is(nil, uri:userinfo())
is(nil, uri:username())
is(nil, uri:password())
end
function test_ftp_typecode ()
local uri = assert(URI:new("ftp://host/path"))
is(nil, uri:ftp_typecode())
is(nil, uri:ftp_typecode("d"))
is("/path;type=d", uri:path())
is("ftp://host/path;type=d", tostring(uri))
is("d", uri:ftp_typecode("a"))
is("/path;type=a", uri:path())
is("ftp://host/path;type=a", tostring(uri))
is("a", uri:ftp_typecode(""))
is("/path", uri:path())
is("ftp://host/path", tostring(uri))
local uri = assert(URI:new("ftp://host/path;type=xyzzy"))
is("/path;type=xyzzy", uri:path())
is("ftp://host/path;type=xyzzy", tostring(uri))
is("xyzzy", uri:ftp_typecode())
is("xyzzy", uri:ftp_typecode(nil))
is(nil, uri:ftp_typecode())
is("/path", uri:path())
is("ftp://host/path", tostring(uri))
end
function test_normalize_path ()
local uri = assert(URI:new("ftp://host"))
is("ftp://host/", tostring(uri))
is("/", uri:path("/foo"))
is("/foo", uri:path(""))
is("/", uri:path("/foo"))
is("/foo", uri:path(nil))
is("/", uri:path())
end
function test_bad_host ()
is_bad_uri("missing authority, just scheme", "ftp:")
is_bad_uri("missing authority, just scheme and path", "ftp:/foo")
is_bad_uri("empty host", "ftp:///foo")
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,67 @@
require "uri-test"
local URI = require "uri"
module("test.http", lunit.testcase, package.seeall)
function test_http ()
local uri = assert(URI:new("HTtp://FOo/Blah?Search#Id"))
is("uri.http", uri._NAME)
is("http://foo/Blah?Search#Id", uri:uri())
is("http://foo/Blah?Search#Id", tostring(uri))
is("http", uri:scheme())
is("foo", uri:host())
is(80, uri:port())
is("/Blah", uri:path())
is(nil, uri:userinfo())
is("Search", uri:query())
is("Id", uri:fragment())
end
function test_https ()
local uri = assert(URI:new("HTtpS://FOo/Blah?Search#Id"))
is("uri.https", uri._NAME)
is("https://foo/Blah?Search#Id", uri:uri())
is("https://foo/Blah?Search#Id", tostring(uri))
is("https", uri:scheme())
is("foo", uri:host())
is(443, uri:port())
is("/Blah", uri:path())
is(nil, uri:userinfo())
is("Search", uri:query())
is("Id", uri:fragment())
end
function test_http_port ()
local uri = assert(URI:new("http://example.com:8080/foo"))
is(8080, uri:port())
local old = uri:port(1234)
is(8080, old)
is(1234, uri:port())
is("http://example.com:1234/foo", tostring(uri))
old = uri:port(80)
is(1234, old)
is(80, uri:port())
is("http://example.com/foo", tostring(uri))
end
function test_normalize_port ()
local uri = assert(URI:new("http://foo:80/"))
is("http://foo/", tostring(uri))
is(80, uri:port())
uri = assert(URI:new("http://foo:443/"))
is("http://foo:443/", tostring(uri))
is(443, uri:port())
uri = assert(URI:new("https://foo:443/"))
is("https://foo/", tostring(uri))
is(443, uri:port())
uri = assert(URI:new("https://foo:80/"))
is("https://foo:80/", tostring(uri))
is(80, uri:port())
end
function test_set_userinfo ()
local uri = assert(URI:new("http://host/path"))
assert_error("can't set userinfo", function () uri:userinfo("x") end)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,129 @@
require "uri-test"
local URI = require "uri"
module("test.pop", lunit.testcase, package.seeall)
function test_pop_parse_1 ()
local uri = assert(URI:new("Pop://rg@MAILSRV.qualcomm.COM"))
is("pop://rg@mailsrv.qualcomm.com", tostring(uri))
is("pop", uri:scheme())
is("rg", uri:userinfo())
is("mailsrv.qualcomm.com", uri:host())
is(110, uri:port())
is("rg", uri:pop_user())
is("*", uri:pop_auth())
end
function test_pop_parse_2 ()
local uri = assert(URI:new("pop://rg;AUTH=+APOP@mail.eudora.com:8110"))
is("pop://rg;auth=+APOP@mail.eudora.com:8110", tostring(uri))
is("rg;auth=+APOP", uri:userinfo())
is("mail.eudora.com", uri:host())
is(8110, uri:port())
is("rg", uri:pop_user())
is("+APOP", uri:pop_auth())
end
function test_pop_parse_3 ()
local uri = assert(URI:new("pop://baz;AUTH=SCRAM-MD5@foo.bar"))
is("pop://baz;auth=SCRAM-MD5@foo.bar", tostring(uri))
is("baz;auth=SCRAM-MD5", uri:userinfo())
is("foo.bar", uri:host())
is(110, uri:port())
is("baz", uri:pop_user())
is("SCRAM-MD5", uri:pop_auth())
end
function test_pop_normalize ()
local uri = assert(URI:new("Pop://Baz;Auth=*@Foo.Bar:110"))
is("pop://Baz@foo.bar", tostring(uri))
is("Baz", uri:userinfo())
is("foo.bar", uri:host())
is(110, uri:port())
is("Baz", uri:pop_user())
is("*", uri:pop_auth())
end
function test_pop_set_user ()
local uri = assert(URI:new("pop://host"))
is(nil, uri:pop_user("foo ;bar"))
is("pop://foo%20%3Bbar@host", tostring(uri))
assert_error("empty user not allowed", function () uri:pop_user("") end)
is("foo ;bar", uri:pop_user(nil))
is(nil, uri:pop_user())
is("pop://host", tostring(uri))
end
function test_pop_set_user_bad ()
local uri = assert(URI:new("pop://foo@host"))
assert_error("empty user not allowed", function () uri:pop_user("") end)
is("foo", uri:pop_user())
is("pop://foo@host", tostring(uri))
uri = assert(URI:new("pop://foo;auth=+APOP@host"))
assert_error("user required when auth specified",
function () uri:pop_user(nil) end)
is("foo", uri:pop_user())
is("+APOP", uri:pop_auth())
is("pop://foo;auth=+APOP@host", tostring(uri))
end
function test_pop_set_auth ()
local uri = assert(URI:new("pop://user@host"))
is("*", uri:pop_auth("foo ;bar"))
is("pop://user;auth=foo%20%3Bbar@host", tostring(uri))
is("foo ;bar", uri:pop_auth("*"))
is("*", uri:pop_auth())
is("pop://user@host", tostring(uri))
end
function test_pop_set_auth_bad ()
local uri = assert(URI:new("pop://host"))
assert_error("auth not allowed without user",
function () uri:pop_auth("+APOP") end)
uri:pop_user("user")
assert_error("empty auth not allowed", function () uri:pop_auth("") end)
assert_error("nil auth not allowed", function () uri:pop_auth(nil) end)
is("pop://user@host", tostring(uri))
end
function test_pop_bad_syntax ()
is_bad_uri("path not empty", "pop://foo@host/")
is_bad_uri("user empty", "pop://@host")
is_bad_uri("user empty with auth", "pop://;auth=+APOP@host")
is_bad_uri("auth empty", "pop://user;auth=@host")
end
function test_set_userinfo ()
local uri = assert(URI:new("pop://host"))
is(nil, uri:userinfo("foo ;bar"))
is("pop://foo%20%3Bbar@host", tostring(uri))
is("foo%20%3Bbar", uri:userinfo("foo;auth=+APOP"))
is("pop://foo;auth=+APOP@host", tostring(uri))
is("foo;auth=+APOP", uri:userinfo("foo;AUTH=+APOP"))
is("pop://foo;auth=+APOP@host", tostring(uri))
is("foo;auth=+APOP", uri:userinfo("bar;auth=*"))
is("pop://bar@host", tostring(uri))
is("bar", uri:userinfo(nil))
is("pop://host", tostring(uri))
end
function test_set_userinfo_bad ()
local uri = assert(URI:new("pop://host"))
assert_error("empty userinfo", function () uri:userinfo("") end)
assert_error("empty user with auth",
function () uri:userinfo(";auth=*") end)
assert_error("empty auth on its own",
function () uri:userinfo(";auth=") end)
assert_error("empty auth with user",
function () uri:userinfo("foo;auth=") end)
end
function test_set_path ()
local uri = assert(URI:new("pop://host"))
is("", uri:path(""))
is("", uri:path(nil))
is("", uri:path())
assert_error("non-empty path", function () uri:path("/") end)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,44 @@
require "uri-test"
local URI = require "uri"
module("test.rtsp", lunit.testcase, package.seeall)
function test_rtsp ()
local u = assert(URI:new("RTSP://MEDIA.EXAMPLE.COM:554/twister/audiotrack"))
is("rtsp://media.example.com/twister/audiotrack", tostring(u))
is("media.example.com", u:host())
is("/twister/audiotrack", u:path())
end
function test_rtspu ()
local uri = assert(URI:new("rtspu://media.perl.com/f%C3%B4o.smi/"))
is("rtspu://media.perl.com/f%C3%B4o.smi/", tostring(uri))
is("media.perl.com", uri:host())
is("/f%C3%B4o.smi/", uri:path())
end
function test_switch_scheme ()
-- Should be no problem switching between TCP and UDP URIs, because they
-- have the same syntax.
local uri = assert(URI:new("rtsp://media.example.com/twister/audiotrack"))
is("rtsp://media.example.com/twister/audiotrack", tostring(uri))
is("rtsp", uri:scheme("rtspu"))
is("rtspu://media.example.com/twister/audiotrack", tostring(uri))
is("rtspu", uri:scheme("rtsp"))
is("rtsp://media.example.com/twister/audiotrack", tostring(uri))
is("rtsp", uri:scheme())
end
function test_rtsp_default_port ()
local uri = assert(URI:new("rtsp://host/path/"))
is(554, uri:port())
uri = assert(URI:new("rtspu://host/path/"))
is(554, uri:port())
is(554, uri:port(8554))
is("rtspu://host:8554/path/", tostring(uri))
is(8554, uri:port(554))
is("rtspu://host/path/", tostring(uri))
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,141 @@
require "uri-test"
local URI = require "uri"
module("test.telnet", lunit.testcase, package.seeall)
-- This tests the generic login stuff ('username' and 'password' methods, and
-- additional userinfo validation), as well as the stuff specific to telnet.
function test_telnet ()
local uri = assert(URI:new("telnet://telnet.example.com/"))
is("telnet://telnet.example.com/", uri:uri())
is("telnet://telnet.example.com/", tostring(uri))
is("uri.telnet", uri._NAME)
is("telnet", uri:scheme())
is("telnet.example.com", uri:host())
is("/", uri:path())
end
function test_telnet_normalize ()
local uri = assert(URI:new("telnet://user:password@host.com"))
is("telnet://user:password@host.com/", tostring(uri))
is("/", uri:path())
is(23, uri:port())
uri = assert(URI:new("telnet://user:password@host.com:23/"))
is("telnet://user:password@host.com/", tostring(uri))
is("/", uri:path())
is(23, uri:port())
end
function test_telnet_invalid ()
is_bad_uri("no authority, empty path", "telnet:")
is_bad_uri("no authority, normal path", "telnet:/")
is_bad_uri("empty authority, empty path", "telnet://")
is_bad_uri("empty authority, normal path", "telnet:///")
is_bad_uri("bad path /x", "telnet://host/x")
is_bad_uri("bad path //", "telnet://host//")
end
function test_telnet_set_path ()
local uri = assert(URI:new("telnet://foo/"))
is("/", uri:path("/"))
is("/", uri:path(""))
is("/", uri:path(nil))
is("/", uri:path())
end
function test_telnet_set_bad_path ()
local uri = assert(URI:new("telnet://foo/"))
assert_error("bad path x", function () uri:path("x") end)
assert_error("bad path /x", function () uri:path("/x") end)
assert_error("bad path //", function () uri:path("//") end)
end
-- These test the generic stuff in uri._login. Some of the examples are
-- directly from RFC 1738 section 3.1, but substituting 'telnet' for 'ftp'.
function test_telnet_userinfo ()
local uri = assert(URI:new("telnet://host.com/"))
is(nil, uri:userinfo())
is(nil, uri:username())
is(nil, uri:password())
uri = assert(URI:new("telnet://foo:bar@host.com/"))
is("foo:bar", uri:userinfo())
is("foo", uri:username())
is("bar", uri:password())
uri = assert(URI:new("telnet://%3a%40:%3a%40@host.com/"))
is("%3A%40:%3A%40", uri:userinfo())
is(":@", uri:username())
is(":@", uri:password())
uri = assert(URI:new("telnet://foo:@host.com/"))
is("foo:", uri:userinfo())
is("foo", uri:username())
is("", uri:password())
uri = assert(URI:new("telnet://@host.com/"))
is("", uri:userinfo())
is("", uri:username())
is(nil, uri:password())
uri = assert(URI:new("telnet://:@host.com/"))
is(":", uri:userinfo())
is("", uri:username())
is("", uri:password())
end
function test_telnet_set_userinfo ()
local uri = assert(URI:new("telnet://host.com/"))
is(nil, uri:userinfo(""))
is("telnet://@host.com/", tostring(uri))
is("", uri:userinfo(":"))
is("telnet://:@host.com/", tostring(uri))
is(":", uri:userinfo("foo:"))
is("telnet://foo:@host.com/", tostring(uri))
is("foo:", uri:userinfo(":bar"))
is("telnet://:bar@host.com/", tostring(uri))
is(":bar", uri:userinfo("foo:bar"))
is("telnet://foo:bar@host.com/", tostring(uri))
is("foo:bar", uri:userinfo())
end
function test_telnet_set_bad_userinfo ()
local uri = assert(URI:new("telnet://host.com/"))
assert_error("more than one colon", function () uri:userinfo("x::y") end)
assert_error("invalid character", function () uri:userinfo("x/y") end)
end
function test_telnet_set_username ()
local uri = assert(URI:new("telnet://host.com/"))
is(nil, uri:username("foo"))
is(nil, uri:password())
is("telnet://foo@host.com/", tostring(uri))
is("foo", uri:username("x:y@z%"))
is(nil, uri:password())
is("telnet://x%3Ay%40z%25@host.com/", tostring(uri))
is("x:y@z%", uri:username(""))
is(nil, uri:password())
is("telnet://@host.com/", tostring(uri))
is("", uri:username(nil))
is(nil, uri:password())
is("telnet://host.com/", tostring(uri))
is(nil, uri:username())
end
function test_telnet_set_password ()
local uri = assert(URI:new("telnet://host.com/"))
is(nil, uri:password("foo"))
is("", uri:username())
is("telnet://:foo@host.com/", tostring(uri))
is("foo", uri:password("x:y@z%"))
is("", uri:username())
is("telnet://:x%3Ay%40z%25@host.com/", tostring(uri))
is("x:y@z%", uri:password(""))
is("", uri:username())
is("telnet://:@host.com/", tostring(uri))
is("", uri:password(nil))
is("", uri:username())
is("telnet://@host.com/", tostring(uri))
is("", uri:username(nil))
is(nil, uri:password(nil))
is("telnet://host.com/", tostring(uri))
is(nil, uri:password())
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,110 @@
require "uri-test"
local URI = require "uri"
local Util = require "uri._util"
local have_isbn_module = Util.attempt_require("isbn")
module("test.urn_isbn", lunit.testcase, package.seeall)
function test_isbn ()
-- Example from RFC 2288
local u = URI:new("URN:ISBN:0-395-36341-1")
is(have_isbn_module and "urn:isbn:0-395-36341-1" or "urn:isbn:0395363411",
u:uri())
is("urn", u:scheme())
is("isbn", u:nid())
is(have_isbn_module and "0-395-36341-1" or "0395363411", u:nss())
is("0395363411", u:isbn_digits())
u = URI:new("URN:ISBN:0395363411")
is(have_isbn_module and "urn:isbn:0-395-36341-1" or "urn:isbn:0395363411",
u:uri())
is("urn", u:scheme())
is("isbn", u:nid())
is(have_isbn_module and "0-395-36341-1" or "0395363411", u:nss())
is("0395363411", u:isbn_digits())
if have_isbn_module then
local isbn = u:isbn()
assert_table(isbn)
is("0-395-36341-1", tostring(isbn))
is("0", isbn:group_code())
is("395", isbn:publisher_code())
is("978-0-395-36341-6", tostring(isbn:as_isbn13()))
end
assert_true(URI.eq("urn:isbn:088730866x", "URN:ISBN:0-88-73-08-66-X"))
end
function test_set_nss ()
local uri = assert(URI:new("urn:isbn:039-53-63411"))
is(have_isbn_module and "0-395-36341-1" or "0395363411",
uri:nss("088-7308-66x"))
is(have_isbn_module and "urn:isbn:0-88730-866-X" or "urn:isbn:088730866X",
tostring(uri))
is(have_isbn_module and "0-88730-866-X" or "088730866X", uri:nss())
end
function test_set_bad_nss ()
local uri = assert(URI:new("urn:ISBN:039-53-63411"))
assert_error("set NSS to non-string value", function () uri:nss({}) end)
assert_error("set NSS to empty", function () uri:nss("") end)
assert_error("set NSS to wrong length", function () uri:nss("123") end)
-- None of that should have had any affect
is(have_isbn_module and "urn:isbn:0-395-36341-1" or "urn:isbn:0395363411",
tostring(uri))
is(have_isbn_module and "0-395-36341-1" or "0395363411", uri:nss())
is("0395363411", uri:isbn_digits())
is("uri.urn.isbn", uri._NAME)
end
function test_set_path ()
local uri = assert(URI:new("urn:ISBN:039-53-63411"))
is(have_isbn_module and "isbn:0-395-36341-1" or "isbn:0395363411",
uri:path("ISbn:088-73-0866x"))
is(have_isbn_module and "urn:isbn:0-88730-866-X" or "urn:isbn:088730866X",
tostring(uri))
assert_error("bad path", function () uri:path("isbn:1234567") end)
is(have_isbn_module and "urn:isbn:0-88730-866-X" or "urn:isbn:088730866X",
tostring(uri))
is(have_isbn_module and "isbn:0-88730-866-X" or "isbn:088730866X",
uri:path())
end
function test_isbn_setting_digits ()
local u = assert(URI:new("URN:ISBN:0395363411"))
local old = u:isbn_digits("0-88730-866-x")
is("0395363411", old)
is("088730866X", u:isbn_digits())
is(have_isbn_module and "0-88730-866-X" or "088730866X", u:nss())
if have_isbn_module then
is("0-88730-866-X", tostring(u:isbn()))
end
end
function test_isbn_setting_object ()
if have_isbn_module then
local ISBN = require "isbn"
local u = assert(URI:new("URN:ISBN:0395363411"))
local old = u:isbn(ISBN:new("0-88730-866-x"))
assert_table(old)
is("0-395-36341-1", tostring(old))
is("088730866X", u:isbn_digits())
is("0-88730-866-X", u:nss())
local new = u:isbn()
assert_table(new)
is("0-88730-866-X", tostring(new))
end
end
function test_illegal_isbn ()
is_bad_uri("invalid characters", "urn:ISBN:abc")
if have_isbn_module then
is_bad_uri("bad checksum", "urn:isbn:0395363412")
is_bad_uri("wrong length", "urn:isbn:03953634101")
end
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,110 @@
require "uri-test"
local URI = require "uri"
module("test.urn_issn", lunit.testcase, package.seeall)
local good_issn_digits = {
"02613077", -- The Guardian
"14734966", -- Photography Monthly
-- From the Wikipedia article on ISSN.
"03178471",
"15340481",
-- From RFC 3044 section 5.
"0259000X",
"15601560",
}
function test_parse_and_normalize ()
local uri = assert(URI:new("urn:ISSN:1560-1560"))
is("uri.urn.issn", uri._NAME)
is("urn:issn:1560-1560", uri:uri())
is("15601560", uri:issn_digits())
uri = assert(URI:new("URN:Issn:0259-000X"))
is("urn:issn:0259-000X", uri:uri())
is("0259000X", uri:issn_digits())
uri = assert(URI:new("urn:issn:0259000x"))
is("urn:issn:0259-000X", uri:uri())
is("0259000X", uri:issn_digits())
end
function test_bad_syntax ()
is_bad_uri("too many digits", "urn:issn:026130707")
is_bad_uri("not enough digits", "urn:issn:0261377")
is_bad_uri("too many hyphens in middle", "urn:issn:0261--3077")
is_bad_uri("hyphen in wrong place", "urn:issn:026-13077")
is_bad_uri("X digit in wrong place", "urn:issn:025900X0")
end
-- Try all the known-good sequences of digits with all possible checksums
-- other than the right one, to make sure they're all detected as errors.
function test_bad_checksum ()
for _, issn in ipairs(good_issn_digits) do
local digits, good_checksum = issn:sub(1, 7), issn:sub(8, 8)
good_checksum = (good_checksum == "X") and 10 or tonumber(good_checksum)
for i = 0, 10 do
if i ~= good_checksum then
local urn = "urn:issn:" .. digits .. (i == 10 and "X" or i)
is_bad_uri("bad checksum in " .. urn, urn)
end
end
end
end
function test_set_nss ()
local uri = assert(URI:new("urn:issn:0261-3077"))
is("0261-3077", uri:nss("14734966"))
is("urn:issn:1473-4966", tostring(uri))
is("1473-4966", uri:nss("0259-000x"))
is("urn:issn:0259-000X", tostring(uri))
is("0259-000X", uri:nss())
end
function test_set_bad_nss ()
local uri = assert(URI:new("urn:ISSN:02613077"))
assert_error("set NSS to non-string value", function () uri:nss({}) end)
assert_error("set NSS to empty", function () uri:nss("") end)
assert_error("set NSS to bad char", function () uri:nss("x") end)
-- None of that should have had any affect
is("urn:issn:0261-3077", tostring(uri))
is("0261-3077", uri:nss())
is("02613077", uri:issn_digits())
is("uri.urn.issn", uri._NAME)
end
function test_set_path ()
local uri = assert(URI:new("urn:ISSN:02613077"))
is("issn:0261-3077", uri:path("ISsn:14734966"))
is("urn:issn:1473-4966", tostring(uri))
assert_error("bad path", function () uri:path("issn:1234567") end)
is("urn:issn:1473-4966", tostring(uri))
is("issn:1473-4966", uri:path())
end
function test_set_issn_digits ()
local uri = assert(URI:new("urn:ISSN:0261-3077"))
is("02613077", uri:issn_digits(nil))
local old = uri:issn_digits("14734966")
is("02613077", old)
is("14734966", uri:issn_digits())
is("urn:issn:1473-4966", uri:uri())
old = uri:issn_digits("0259-000x")
is("14734966", old)
is("0259000X", uri:issn_digits())
is("urn:issn:0259-000X", uri:uri())
end
function test_set_bad_issn_digits ()
local uri = assert(URI:new("urn:ISSN:0261-3077"))
assert_error("set ISSN with bad char",
function () uri:issn_digits("0261-3077Y") end)
assert_error("set ISSN with too many digits",
function () uri:issn_digits("0261-30770") end)
assert_error("set ISSN of empty string",
function () uri:issn_digits("") end)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,101 @@
require "uri-test"
local URI = require "uri"
module("test.urn_oid", lunit.testcase, package.seeall)
function test_parse_and_normalize ()
local uri = assert(URI:new("urn:OId:1.3.50403060.0.23"))
is("uri.urn.oid", uri._NAME)
is("urn:oid:1.3.50403060.0.23", uri:uri())
is("urn:oid:1.3.50403060.0.23", tostring(uri))
is("oid", uri:nid())
is("1.3.50403060.0.23", uri:nss())
is("oid:1.3.50403060.0.23", uri:path())
assert_array_shallow_equal({ 1, 3, 50403060, 0, 23 }, uri:oid_numbers())
-- Examples from RFC 3061 section 3
uri = assert(URI:new("urn:oid:1.3.6.1"))
is("urn:oid:1.3.6.1", tostring(uri))
assert_array_shallow_equal({ 1, 3, 6, 1 }, uri:oid_numbers())
uri = assert(URI:new("urn:oid:1.3.6.1.4.1"))
is("urn:oid:1.3.6.1.4.1", tostring(uri))
assert_array_shallow_equal({ 1, 3, 6, 1, 4, 1 }, uri:oid_numbers())
uri = assert(URI:new("urn:oid:1.3.6.1.2.1.27"))
is("urn:oid:1.3.6.1.2.1.27", tostring(uri))
assert_array_shallow_equal({ 1, 3, 6, 1, 2, 1, 27 }, uri:oid_numbers())
uri = assert(URI:new("URN:OID:0.9.2342.19200300.100.4"))
is("urn:oid:0.9.2342.19200300.100.4", tostring(uri))
assert_array_shallow_equal({ 0, 9, 2342, 19200300, 100, 4 },
uri:oid_numbers())
end
function test_bad_syntax ()
is_bad_uri("empty nss", "urn:oid:")
is_bad_uri("bad character", "urn:oid:1.2.x.3")
is_bad_uri("missing number", "urn:oid:1.2..3")
is_bad_uri("leading zero", "urn:oid:1.2.03.3")
is_bad_uri("leading zero at start", "urn:oid:01.2.3.3")
end
function test_set_nss ()
local uri = assert(URI:new("urn:oid:0.1.23"))
is("0.1.23", uri:nss("1"))
is("urn:oid:1", tostring(uri))
is("1", uri:nss("234252345.340.4.0"))
is("urn:oid:234252345.340.4.0", tostring(uri))
is("234252345.340.4.0", uri:nss())
end
function test_set_bad_nss ()
local uri = assert(URI:new("urn:OID:0.1.23"))
assert_error("set NSS to non-string value", function () uri:nss({}) end)
assert_error("set NSS to empty", function () uri:nss("") end)
assert_error("set NSS to bad char", function () uri:nss("x") end)
-- None of that should have had any affect
is("urn:oid:0.1.23", tostring(uri))
is("0.1.23", uri:nss())
assert_array_shallow_equal({ 0, 1, 23 }, uri:oid_numbers())
is("uri.urn.oid", uri._NAME)
end
function test_set_path ()
local uri = assert(URI:new("urn:OID:0.1.23"))
is("oid:0.1.23", uri:path("OId:23.1.0"))
is("urn:oid:23.1.0", tostring(uri))
assert_error("bad path", function () uri:path("oid:1.02") end)
is("urn:oid:23.1.0", tostring(uri))
is("oid:23.1.0", uri:path())
end
function test_set_oid_numbers ()
local uri = assert(URI:new("urn:oid:0.1.23"))
assert_array_shallow_equal({ 0, 1, 23 }, uri:oid_numbers({ 1 }))
is("urn:oid:1", tostring(uri))
assert_array_shallow_equal({ 1 }, uri:oid_numbers({ 234252345, 340, 4, 0 }))
is("urn:oid:234252345.340.4.0", tostring(uri))
assert_array_shallow_equal({ 234252345, 340, 4, 0 },
uri:oid_numbers({ 23.42 }))
is("urn:oid:23", tostring(uri))
assert_array_shallow_equal({ 23 }, uri:oid_numbers())
end
function test_set_bad_oid_numbers ()
local uri = assert(URI:new("urn:OID:0.1.23"))
assert_error("set OID numbers to non-table value",
function () uri:oid_numbers("1") end)
assert_error("set OID to empty list of numbers",
function () uri:oid_numbers({}) end)
assert_error("set OID number to negative number",
function () uri:oid_numbers({ -23 }) end)
assert_error("set OID number array containing bad type",
function () uri:oid_numbers({ "x" }) end)
-- None of that should have had any affect
is("urn:oid:0.1.23", tostring(uri))
assert_array_shallow_equal({ 0, 1, 23 }, uri:oid_numbers())
is("uri.urn.oid", uri._NAME)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,137 @@
require "uri-test"
local URI = require "uri"
module("test.urn", lunit.testcase, package.seeall)
function test_urn_parsing ()
local uri = assert(URI:new("urn:x-FOO-01239-:Nss"))
is("urn:x-foo-01239-:Nss", uri:uri())
is("urn", uri:scheme())
is("x-foo-01239-:Nss", uri:path())
is("x-foo-01239-", uri:nid())
is("Nss", uri:nss())
is(nil, uri:userinfo())
is(nil, uri:host())
is(nil, uri:port())
is(nil, uri:query())
is(nil, uri:fragment())
end
function test_set_nss ()
local uri = assert(URI:new("urn:x-FOO-01239-:Nss"))
is("Nss", uri:nss("FooBar"))
is("urn:x-foo-01239-:FooBar", tostring(uri))
assert_error("bad NSS, empty", function () uri:nss("") end)
assert_error("bad NSS, illegal character", function () uri:nss('x"y') end)
is("urn:x-foo-01239-:FooBar", tostring(uri))
end
function test_bad_urn_syntax ()
is_bad_uri("missing nid", "urn::bar")
is_bad_uri("hyphen at start of nid", "urn:-x-foo:bar")
is_bad_uri("plus in middle of nid", "urn:x+foo:bar")
is_bad_uri("underscore in middle of nid", "urn:x_foo:bar")
is_bad_uri("dot in middle of nid", "urn:x.foo:bar")
is_bad_uri("nid too long", "urn:x-012345678901234567890123456789x:bar")
is_bad_uri("reserved 'urn' nid", "urn:urn:bar")
is_bad_uri("missing nss", "urn:x-foo:")
is_bad_uri("bad char in nss", "urn:x-foo:bar&")
is_bad_uri("shoudn't have host part", "urn://foo.com/x-foo:bar")
is_bad_uri("shoudn't have query part", "urn:x-foo:bar?baz")
end
function test_change_nid ()
local urn = assert(URI:new("urn:x-foo:14734966"))
is("urn:x-foo:14734966", tostring(urn))
is("x-foo", urn:nid())
is("uri.urn", urn._NAME)
-- x-foo -> x-bar
is("x-foo", urn:nid("X-BAR"))
is("x-bar", urn:nid())
is("urn:x-bar:14734966", tostring(urn))
is("uri.urn", urn._NAME)
-- x-bar -> issn
is("x-bar", urn:nid("issn"))
is("issn", urn:nid())
is("urn:issn:1473-4966", tostring(urn))
is("uri.urn.issn", urn._NAME)
-- issn -> x-foo
is("issn", urn:nid("x-foo"))
is("x-foo", urn:nid())
is("urn:x-foo:1473-4966", tostring(urn))
is("uri.urn", urn._NAME)
end
function test_change_nid_bad ()
local urn = assert(URI:new("urn:x-foo:frob"))
-- Try changing the NID to something invalid
assert_error("bad NID 'urn'", function () urn:nid("urn") end)
assert_error("bad NID '-x-foo'", function () urn:nid("-x-foo") end)
assert_error("bad NID 'x+foo'", function () urn:nid("x+foo") end)
-- Change to valid NID, but where the NSS is not valid for it
assert_error("bad NSS for ISSN URN", function () urn:nid("issn") end)
-- Original URN should be left unchanged
is("urn:x-foo:frob", tostring(urn))
is("x-foo", urn:nid())
is("uri.urn", urn._NAME)
end
function test_change_path ()
local urn = assert(URI:new("urn:x-foo:foopath"))
is("x-foo:foopath", urn:path())
-- x-foo -> x-bar
is("x-foo:foopath", urn:path("X-BAR:barpath"))
is("x-bar:barpath", urn:path())
is("urn:x-bar:barpath", tostring(urn))
is("uri.urn", urn._NAME)
-- x-bar -> issn
is("x-bar:barpath", urn:path("issn:14734966"))
is("issn:1473-4966", urn:path())
is("urn:issn:1473-4966", tostring(urn))
is("uri.urn.issn", urn._NAME)
-- issn -> x-foo
is("issn:1473-4966", urn:path("x-foo:foopath2"))
is("x-foo:foopath2", urn:path())
is("urn:x-foo:foopath2", tostring(urn))
is("uri.urn", urn._NAME)
end
function test_change_path_bad ()
local urn = assert(URI:new("urn:x-foo:frob"))
-- Try changing the NID to something invalid
assert_error("bad NID 'urn'", function () urn:path("urn:frob") end)
assert_error("bad NID '-x-foo'", function () urn:path("-x-foo:frob") end)
assert_error("bad NID 'x+foo'", function () urn:path("x+foo:frob") end)
assert_error("bad NSS, empty", function () urn:path("x-foo:") end)
assert_error("bad NSS, bad char", function () urn:path('x-foo:x"y') end)
-- Change to valid NID, but where the NSS is not valid for it
assert_error("bad NSS for ISSN URN", function () urn:path("issn:frob") end)
-- Original URN should be left unchanged
is("urn:x-foo:frob", tostring(urn))
is("x-foo:frob", urn:path())
is("x-foo", urn:nid())
is("frob", urn:nss())
is("uri.urn", urn._NAME)
end
function test_set_disallowed_stuff ()
local urn = assert(URI:new("urn:x-foo:frob"))
assert_error("can't set userinfo", function () urn:userinfo("x") end)
assert_error("can't set host", function () urn:host("x") end)
assert_error("can't set port", function () urn:port(23) end)
assert_error("can't set query", function () urn:query("x") end)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,85 @@
require "lunit"
local URI = require "uri"
is = lunit.assert_equal
function is_one_of (expecteds, actual, msg)
for _, v in ipairs(expecteds) do
if actual == v then return end
end
-- Not any of the expected answers matched. In order to report the error
-- usefully, we have to list the alternatives in the error message.
local err = "expected one of {"
for i, v in ipairs(expecteds) do
if i > 1 then err = err .. ", " end
err = err .. "'" .. tostring(v) .. "'"
end
err = err .. "}, but was '" .. tostring(actual) .. "'"
if msg then err = err .. ": " .. msg end
lunit.fail(err)
end
function assert_isa(actual, class)
lunit.assert_table(actual)
lunit.assert_table(class)
local mt = actual
while true do
mt = getmetatable(mt)
if not mt then error"class not found as metatable at any level" end
if mt == actual then error"circular metatables" end
if mt == class then return nil end
end
end
function assert_array_shallow_equal (expected, actual, msg)
if not msg then msg = "assert_array_shallow_equal" end
lunit.assert_table(actual, msg .. ", is table")
is(#expected, #actual, msg .. ", same size")
if #expected == #actual then
for i = 1, #expected do
is(expected[i], actual[i], msg .. ", element " .. i)
end
end
for key in pairs(actual) do
lunit.assert_number(key, msg .. ", non-number key in array")
end
end
local function _count_hash_pairs (hash)
local count = 0
for _, _ in pairs(hash) do count = count + 1 end
return count
end
function assert_hash_shallow_equal (expected, actual, msg)
if not msg then msg = "assert_hash_shallow_equal" end
lunit.assert_table(actual, msg .. ", is table")
local expsize, actualsize = _count_hash_pairs(expected),
_count_hash_pairs(actual)
is(expsize, actualsize, msg .. ", same size")
if expsize == actualsize then
for k, v in pairs(expected) do
is(expected[k], actual[k], msg .. ", element " .. tostring(k))
end
end
end
function is_bad_uri (msg, uri)
local ok, err = URI:new(uri)
lunit.assert_nil(ok, msg)
lunit.assert_string(err, msg)
end
function test_norm (expected, input)
local uri = assert(URI:new(input))
is(expected, uri:uri())
is(expected, tostring(uri))
lunit.assert_false(uri:is_relative())
end
function test_norm_already (input)
test_norm(input, input)
end
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,507 @@
local M = { _NAME = "uri", VERSION = "1.0" }
M.__index = M
local Util = require "uri._util"
local _UNRESERVED = "A-Za-z0-9%-._~"
local _GEN_DELIMS = ":/?#%[%]@"
local _SUB_DELIMS = "!$&'()*+,;="
local _RESERVED = _GEN_DELIMS .. _SUB_DELIMS
local _USERINFO = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":]*$"
local _REG_NAME = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. "]*$"
local _IP_FUTURE_LITERAL = "^v[0-9A-Fa-f]+%." ..
"[" .. _UNRESERVED .. _SUB_DELIMS .. "]+$"
local _QUERY_OR_FRAG = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/?]*$"
local _PATH_CHARS = "^[" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/]*$"
local function _normalize_percent_encoding (s)
if s:find("%%$") or s:find("%%.$") then
error("unfinished percent encoding at end of URI '" .. s .. "'", 3)
end
return s:gsub("%%(..)", function (hex)
if not hex:find("^[0-9A-Fa-f][0-9A-Fa-f]$") then
error("invalid percent encoding '%" .. hex ..
"' in URI '" .. s .. "'", 5)
end
-- Never percent-encode unreserved characters, and always use uppercase
-- hexadecimal for percent encoding. RFC 3986 section 6.2.2.2.
local char = string.char(tonumber("0x" .. hex))
return char:find("^[" .. _UNRESERVED .. "]") and char or "%" .. hex:upper()
end)
end
local function _is_ip4_literal (s)
if not s:find("^[0-9]+%.[0-9]+%.[0-9]+%.[0-9]+$") then return false end
for dec_octet in s:gmatch("[0-9]+") do
if dec_octet:len() > 3 or dec_octet:find("^0.") or
tonumber(dec_octet) > 255 then
return false
end
end
return true
end
local function _is_ip6_literal (s)
local had_elipsis = false -- true when '::' found
local num_chunks = 0
while s ~= "" do
num_chunks = num_chunks + 1
local p1, p2 = s:find("::?")
local chunk
if p1 then
chunk = s:sub(1, p1 - 1)
s = s:sub(p2 + 1)
if p2 ~= p1 then -- found '::'
if had_elipsis then return false end -- two of '::'
had_elipsis = true
if chunk == "" then num_chunks = num_chunks - 1 end
else
if chunk == "" then return false end -- ':' at start
if s == "" then return false end -- ':' at end
end
else
chunk = s
s = ""
end
-- Chunk is neither 4-digit hex num, nor IPv4address in last chunk.
if (not chunk:find("^[0-9a-f]+$") or chunk:len() > 4) and
(s ~= "" or not _is_ip4_literal(chunk)) and
chunk ~= "" then
return false
end
-- IPv4address in last position counts for two chunks of hex digits.
if chunk:len() > 4 then num_chunks = num_chunks + 1 end
end
if had_elipsis then
if num_chunks > 7 then return false end
else
if num_chunks ~= 8 then return false end
end
return true
end
local function _is_valid_host (host)
if host:find("^%[.*%]$") then
local ip_literal = host:sub(2, -2)
if ip_literal:find("^v") then
if not ip_literal:find(_IP_FUTURE_LITERAL) then
return "invalid IPvFuture literal '" .. ip_literal .. "'"
end
else
if not _is_ip6_literal(ip_literal) then
return "invalid IPv6 address '" .. ip_literal .. "'"
end
end
elseif not _is_ip4_literal(host) and not host:find(_REG_NAME) then
return "invalid host value '" .. host .. "'"
end
return nil
end
local function _normalize_and_check_path (s, normalize)
if not s:find(_PATH_CHARS) then return false end
if not normalize then return s end
-- Remove unnecessary percent encoding for path values.
-- TODO - I think this should be HTTP-specific (probably file also).
--s = Util.uri_decode(s, _SUB_DELIMS .. ":@")
return Util.remove_dot_segments(s)
end
function M.new (class, uri, base)
if not class or not uri then
error("usage: URI:new(uristring, [baseuri])", 2)
end
if type(uri) ~= "string" then uri = tostring(uri) end
if base then
local uri, err = M.new(class, uri)
if not uri then return nil, err end
if type(base) ~= "table" then
base, err = M.new(class, base)
if not base then return nil, "error parsing base URI: " .. err end
end
if base:is_relative() then return nil, "base URI must be absolute" end
local ok, err = pcall(uri.resolve, uri, base)
if not ok then return nil, err end
return uri
end
local s = _normalize_percent_encoding(uri)
local _, p
local scheme, authority, userinfo, host, port, path, query, fragment
_, p, scheme = s:find("^([a-zA-Z][-+.a-zA-Z0-9]*):")
if scheme then
scheme = scheme:lower()
s = s:sub(p + 1)
end
_, p, authority = s:find("^//([^/?#]*)")
if authority then
s = s:sub(p + 1)
_, p, userinfo = authority:find("^([^@]*)@")
if userinfo then
if not userinfo:find(_USERINFO) then
return nil, "invalid userinfo value '" .. userinfo .. "'"
end
authority = authority:sub(p + 1)
end
p, _, port = authority:find(":([0-9]*)$")
if port then
port = (port ~= "") and tonumber(port) or nil
authority = authority:sub(1, p - 1)
end
host = authority:lower()
local err = _is_valid_host(host)
if err then return nil, err end
end
_, p, path = s:find("^([^?#]*)")
if path ~= "" then
local normpath = _normalize_and_check_path(path, scheme)
if not normpath then return nil, "invalid path '" .. path .. "'" end
path = normpath
s = s:sub(p + 1)
end
_, p, query = s:find("^%?([^#]*)")
if query then
s = s:sub(p + 1)
if not query:find(_QUERY_OR_FRAG) then
return nil, "invalid query value '?" .. query .. "'"
end
end
_, p, fragment = s:find("^#(.*)")
if fragment then
if not fragment:find(_QUERY_OR_FRAG) then
return nil, "invalid fragment value '#" .. fragment .. "'"
end
end
local o = {
_scheme = scheme,
_userinfo = userinfo,
_host = host,
_port = port,
_path = path,
_query = query,
_fragment = fragment,
}
setmetatable(o, scheme and class or (require "uri._relative"))
return o:init()
end
function M.uri (self, ...)
local uri = self._uri
if not uri then
local scheme = self:scheme()
if scheme then
uri = scheme .. ":"
else
uri = ""
end
local host, port, userinfo = self:host(), self._port, self:userinfo()
if host or port or userinfo then
uri = uri .. "//"
if userinfo then uri = uri .. userinfo .. "@" end
if host then uri = uri .. host end
if port then uri = uri .. ":" .. port end
end
local path = self:path()
if uri == "" and path:find("^[^/]*:") then
path = "./" .. path
end
uri = uri .. path
if self:query() then uri = uri .. "?" .. self:query() end
if self:fragment() then uri = uri .. "#" .. self:fragment() end
self._uri = uri -- cache
end
if select("#", ...) > 0 then
local new = ...
if not new then error("URI can't be set to nil", 2) end
local newuri, err = M:new(new)
if not newuri then
error("new URI string is invalid (" .. err .. ")", 2)
end
setmetatable(self, getmetatable(newuri))
for k in pairs(self) do self[k] = nil end
for k, v in pairs(newuri) do self[k] = v end
end
return uri
end
function M.__tostring (self) return self:uri() end
function M.eq (a, b)
if type(a) == "string" then a = assert(M:new(a)) end
if type(b) == "string" then b = assert(M:new(b)) end
return a:uri() == b:uri()
end
function M.scheme (self, ...)
local old = self._scheme
if select("#", ...) > 0 then
local new = ...
if not new then error("can't remove scheme from absolute URI", 2) end
if type(new) ~= "string" then new = tostring(new) end
if not new:find("^[a-zA-Z][-+.a-zA-Z0-9]*$") then
error("invalid scheme '" .. new .. "'", 2)
end
Util.do_class_changing_change(self, M, "scheme", new,
function (uri, new) uri._scheme = new end)
end
return old
end
function M.userinfo (self, ...)
local old = self._userinfo
if select("#", ...) > 0 then
local new = ...
if new then
if not new:find(_USERINFO) then
error("invalid userinfo value '" .. new .. "'", 2)
end
new = _normalize_percent_encoding(new)
end
self._userinfo = new
if new and not self._host then self._host = "" end
self._uri = nil
end
return old
end
function M.host (self, ...)
local old = self._host
if select("#", ...) > 0 then
local new = ...
if new then
new = tostring(new):lower()
local err = _is_valid_host(new)
if err then error(err, 2) end
else
if self._userinfo or self._port then
error("there must be a host if there is a userinfo or port," ..
" although it can be the empty string", 2)
end
end
self._host = new
self._uri = nil
end
return old
end
function M.port (self, ...)
local old = self._port or self:default_port()
if select("#", ...) > 0 then
local new = ...
if new then
if type(new) == "string" then new = tonumber(new) end
if not new then error("port number must be a number", 2) end
if new < 0 then error("port number must not be negative", 2) end
local newint = new - new % 1
if newint ~= new then error("port number not integer", 2) end
if new == self:default_port() then new = nil end
end
self._port = new
if new and not self._host then self._host = "" end
self._uri = nil
end
return old
end
function M.path (self, ...)
local old = self._path
if select("#", ...) > 0 then
local new = ... or ""
new = _normalize_percent_encoding(new)
new = Util.uri_encode(new, "^A-Za-z0-9%-._~%%!$&'()*+,;=:@/")
if self._host then
if new ~= "" and not new:find("^/") then
error("path must begin with '/' when there is an authority", 2)
end
else
if new:find("^//") then new = "/%2F" .. new:sub(3) end
end
self._path = new
self._uri = nil
end
return old
end
function M.query (self, ...)
local old = self._query
if select("#", ...) > 0 then
local new = ...
if new then
new = Util.uri_encode(new, "^" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/?")
end
self._query = new
self._uri = nil
end
return old
end
function M.fragment (self, ...)
local old = self._fragment
if select("#", ...) > 0 then
local new = ...
if new then
new = Util.uri_encode(new, "^" .. _UNRESERVED .. "%%" .. _SUB_DELIMS .. ":@/?")
end
self._fragment = new
self._uri = nil
end
return old
end
function M.init (self)
local scheme_class
= Util.attempt_require("uri." .. self._scheme:gsub("[-+.]", "_"))
if scheme_class then
setmetatable(self, scheme_class)
if self._port and self._port == self:default_port() then
self._port = nil
end
-- Call the subclass 'init' method, if it has its own.
if scheme_class ~= M and self.init ~= M.init then
return self:init()
end
end
return self
end
function M.default_port () return nil end
function M.is_relative () return false end
function M.resolve () end -- only does anything in uri._relative
-- TODO - there should probably be an option or something allowing you to
-- choose between making a link relative whenever possible (always using a
-- relative path if the scheme and authority are the same as the base URI) or
-- just using a relative reference to make the link as small as possible, which
-- might meaning using a path of '/' instead if '../../../' or whatever.
-- This method's algorithm is loosely based on the one described here:
-- http://lists.w3.org/Archives/Public/uri/2007Sep/0003.html
function M.relativize (self, base)
if type(base) == "string" then base = assert(M:new(base)) end
-- Leave it alone if we can't a relative URI, or if it would be a network
-- path reference.
if self._scheme ~= base._scheme or self._host ~= base._host or
self._port ~= base._port or self._userinfo ~= base._userinfo then
return
end
local basepath = base._path
local oldpath = self._path
-- This is to avoid trying to make a URN or something relative, which
-- is likely to lead to grief.
if not basepath:find("^/") or not oldpath:find("^/") then return end
-- Turn it into a relative reference.
self._uri = nil
self._scheme = nil
self._host = nil
self._port = nil
self._userinfo = nil
setmetatable(self, require "uri._relative")
-- Use empty path if the path in the base URI is already correct.
if oldpath == basepath then
if self._query or not base._query then
self._path = ""
else
-- An empty URI reference leaves the query string in the base URI
-- unchanged, so to get a result with no query part we have to
-- have something in the relative path.
local _, _, lastseg = oldpath:find("/([^/]+)$")
if lastseg and lastseg:find(":") then lastseg = "./" .. lastseg end
self._path = lastseg or "."
end
return
end
if oldpath == "/" or basepath == "/" then return end
local basesegs = Util.split("/", basepath:sub(2))
local oldsegs = Util.split("/", oldpath:sub(2))
if oldsegs[1] ~= basesegs[1] then return end
table.remove(basesegs)
while #oldsegs > 1 and #basesegs > 0 and oldsegs[1] == basesegs[1] do
table.remove(oldsegs, 1)
table.remove(basesegs, 1)
end
local path_naked = true
local newpath = ""
while #basesegs > 0 do
table.remove(basesegs, 1)
newpath = newpath .. "../"
path_naked = false
end
if path_naked and #oldsegs == 1 and oldsegs[1] == "" then
newpath = "./"
table.remove(oldsegs)
end
while #oldsegs > 0 do
if path_naked then
if oldsegs[1]:find(":") then
newpath = newpath .. "./"
elseif #oldsegs > 1 and oldsegs[1] == "" and oldsegs[2] == "" then
newpath = newpath .. "/."
end
end
newpath = newpath .. oldsegs[1]
path_naked = false
table.remove(oldsegs, 1)
if #oldsegs > 0 then newpath = newpath .. "/" end
end
self._path = newpath
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,96 @@
local M = { _NAME = "uri._login" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)
-- Generic terminal logins. This is used as a base class for 'telnet' and
-- 'ftp' URL schemes.
local function _valid_userinfo (userinfo)
if userinfo then
local colon = userinfo:find(":")
if colon and userinfo:find(":", colon + 1) then
return nil, "only one colon allowed in userinfo"
end
end
return true
end
-- TODO - this is a bit of a hack because currently subclasses are required
-- to know whether their superclass has one of these that needs calling.
-- It should be called from 'init' before anything more specific is done,
-- and it has the same calling convention.
-- According to RFC 1738 there should be at most one colon in the userinfo.
-- I apply that restriction for schemes where it's used for a username/password
-- pair.
function M.init_base (self)
local host = self:host()
if not host or host == "" then
return nil, "host missing from login URI"
end
local ok, err = _valid_userinfo(self:userinfo())
if not ok then return nil, err end
return self
end
function M.userinfo (self, ...)
if select("#", ...) > 0 then
local ok, err = _valid_userinfo(...)
if not ok then error("invalid userinfo value (" .. err .. ")", 2) end
end
return M._SUPER.userinfo(self, ...)
end
function M.username (self, ...)
local info = M._SUPER.userinfo(self)
local old, colon
if info then
local colon = info and info:find(":")
old = colon and info:sub(1, colon - 1) or info
old = Util.uri_decode(old)
end
if select('#', ...) > 0 then
local pass = colon and info:sub(colon) or "" -- includes colon
local new = ...
if not new then
M._SUPER.userinfo(self, nil)
else
-- Escape anything that's not allowed in a userinfo, and also
-- colon, because that indicates the end of the username.
new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+,;=")
M._SUPER.userinfo(self, new .. pass)
end
end
return old
end
function M.password (self, ...)
local info = M._SUPER.userinfo(self)
local old, colon
if info then
colon = info and info:find(":")
old = colon and info:sub(colon + 1) or nil
if old then old = Util.uri_decode(old) end
end
if select('#', ...) > 0 then
local new = ...
local user = colon and info:sub(1, colon - 1) or info
if not new then
M._SUPER.userinfo(self, user)
else
if not user then user = "" end
new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+,;=")
M._SUPER.userinfo(self, user .. ":" .. new)
end
end
return old
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,81 @@
local M = { _NAME = "uri._relative" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)
-- There needs to be an 'init' method in this class, to because the base-class
-- one expects there to be a 'scheme' value.
function M.init (self)
return self
end
function M.scheme (self, ...)
if select("#", ...) > 0 then
error("relative URI references can't have a scheme, perhaps you" ..
" need to resolve this against an absolute URI instead", 2)
end
return nil
end
function M.is_relative () return true end
-- This implements the algorithm from RFC 3986 section 5.2.3
-- Note that this takes an additional argument which appears to be required
-- by the algorithm, but isn't shown when it is used in the RFC.
local function _merge_paths (base, r, base_has_auth)
if base_has_auth and base == "" then
return "/" .. r
end
return base:gsub("[^/]+$", "", 1) .. r
end
local function _do_resolve (self, base)
if type(base) == "string" then base = assert(URI:new(base)) end
setmetatable(self, URI)
if self:host() or self:userinfo() or self:port() then
-- network path reference, just needs a scheme
self:path(Util.remove_dot_segments(self:path()))
self:scheme(base:scheme())
return
end
local path = self:path()
if path == "" then
self:path(base:path())
if not self:query() then self:query(base:query()) end
else
if path:find("^/") then
self:path(Util.remove_dot_segments(path))
else
local base_has_auth = base:host() or base:userinfo() or base:port()
local merged = _merge_paths(base:path(), path, base_has_auth)
self:path(Util.remove_dot_segments(merged))
end
end
self:host(base:host())
self:userinfo(base:userinfo())
self:port(base:port())
self:scheme(base:scheme())
end
function M.resolve (self, base)
local orig = tostring(self)
local ok, result = pcall(_do_resolve, self, base)
if ok then return end
-- If the resolving causes an exception, it means that the resulting URI
-- would be invalid, so we restore self to its original state and rethrow
-- the exception.
local restored = assert(URI:new(orig))
for k in pairs(self) do self[k] = nil end
for k, v in pairs(restored) do self[k] = v end
setmetatable(self, getmetatable(restored))
error("resolved URI reference would be invalid: " .. result, 2)
end
function M.relativize (self, base) end -- already relative
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,130 @@
local M = { _NAME = "uri._util" }
local string_char, string_format = string.char, string.format
-- Build a char->hex map
local escapes = {}
for i = 0, 255 do
escapes[string_char(i)] = string_format("%%%02X", i)
end
local function _encode_char (chr) return escapes[chr] end
function M.uri_encode (text, patn)
if not text then return end
if not patn then
-- Default unsafe characters. RFC 2732 ^(uric - reserved)
-- TODO - this should be updated to the latest RFC.
patn = "^A-Za-z0-9%-_.!~*'()"
end
return (text:gsub("([" .. patn .. "])", _encode_char))
end
function M.uri_decode (str, patn)
-- Note from RFC1630: "Sequences which start with a percent sign
-- but are not followed by two hexadecimal characters are reserved
-- for future extension"
if not str then return end
if patn then patn = "[" .. patn .. "]" end
return (str:gsub("%%(%x%x)", function (hex)
local char = string_char(tonumber(hex, 16))
return (patn and not char:find(patn)) and "%" .. hex or char
end))
end
-- This is the remove_dot_segments algorithm from RFC 3986 section 5.2.4.
-- The input buffer is 's', the output buffer 'path'.
function M.remove_dot_segments (s)
local path = ""
while s ~= "" do
if s:find("^%.%.?/") then -- A
s = s:gsub("^%.%.?/", "", 1)
elseif s:find("^/%./") or s == "/." then -- B
s = s:gsub("^/%./?", "/", 1)
elseif s:find("^/%.%./") or s == "/.." then -- C
s = s:gsub("^/%.%./?", "/", 1)
if path:find("/") then
path = path:gsub("/[^/]*$", "", 1)
else
path = ""
end
elseif s == "." or s == ".." then -- D
s = ""
else -- E
local _, p, seg = s:find("^(/?[^/]*)")
s = s:sub(p + 1)
path = path .. seg
end
end
return path
end
-- TODO - wouldn't this be better as a method on string? s:split(patn)
function M.split (patn, s, max)
if s == "" then return {} end
local i, j = 1, string.find(s, patn)
if not j then return { s } end
local list = {}
while true do
if #list + 1 == max then list[max] = s:sub(i); return list end
list[#list + 1] = s:sub(i, j - 1)
i = j + 1
j = string.find(s, patn, i)
if not j then
list[#list + 1] = s:sub(i)
break
end
end
return list
end
function M.attempt_require (modname)
local ok, result = pcall(require, modname)
if ok then
return result
elseif type(result) == "string" and
result:find("module '.*' not found") then
return nil
else
error(result)
end
end
function M.subclass_of (class, baseclass)
class.__index = class
class.__tostring = baseclass.__tostring
class._SUPER = baseclass
setmetatable(class, baseclass)
end
function M.do_class_changing_change (uri, baseclass, changedesc, newvalue,
changefunc)
local tmpuri = {}
setmetatable(tmpuri, baseclass)
for k, v in pairs(uri) do tmpuri[k] = v end
changefunc(tmpuri, newvalue)
tmpuri._uri = nil
local foo, err = tmpuri:init()
if not foo then
error("URI not valid after " .. changedesc .. " changed to '" ..
newvalue .. "': " .. err, 3)
end
setmetatable(uri, getmetatable(tmpuri))
for k in pairs(uri) do uri[k] = nil end
for k, v in pairs(tmpuri) do uri[k] = v end
end
function M.uri_part_not_allowed (class, method)
class[method] = function (self, new)
if new then error(method .. " not allowed on this kind of URI") end
return self["_" .. method]
end
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,116 @@
local M = { _NAME = "uri.data" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)
-- This implements the 'data' scheme defined in RFC 2397.
local Filter = Util.attempt_require("datafilter")
local function _valid_base64 (data) return data:find("^[0-9a-zA-Z/+]*$") end
local function _split_path (path)
local _, _, mediatype, data = path:find("^([^,]*),(.*)")
if not mediatype then return "must have comma in path" end
local base64 = false
if mediatype:find(";base64$") then
base64 = true
mediatype = mediatype:sub(1, -8)
end
if base64 and not _valid_base64(data) then
return "illegal character in base64 encoding"
end
return nil, mediatype, base64, data
end
function M.init (self)
if M._SUPER.host(self) then
return nil, "data URIs may not have authority parts"
end
local err, mediatype, base64, data = _split_path(M._SUPER.path(self))
if err then return nil, "invalid data URI (" .. err .. ")" end
return self
end
function M.data_media_type (self, ...)
local _, old, base64, data = _split_path(M._SUPER.path(self))
if select('#', ...) > 0 then
local new = ... or ""
new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+;=:@/")
if base64 then new = new .. ";base64" end
M._SUPER.path(self, new .. "," .. data)
end
if old ~= "" then
if old:find("^;") then old = "text/plain" .. old end
return Util.uri_decode(old)
else
return "text/plain;charset=US-ASCII" -- default type
end
end
local function _urienc_len (s)
local num_unsafe_chars = s:gsub("[A-Za-z0-9%-._~!$&'()*+,;=:@/]", ""):len()
local num_safe_chars = s:len() - num_unsafe_chars
return num_safe_chars + num_unsafe_chars * 3
end
local function _base64_len (s)
local num_blocks = (s:len() + 2) / 3
num_blocks = num_blocks - num_blocks % 1
return num_blocks * 4
+ 7 -- because of ";base64" marker
end
local function _do_filter (algorithm, input)
return Filter[algorithm](input)
end
function M.data_bytes (self, ...)
local _, mediatype, base64, old = _split_path(M._SUPER.path(self))
if base64 then
if not Filter then
error("'datafilter' Lua module required to decode base64 data", 2)
end
old = _do_filter("base64_decode", old)
else
old = Util.uri_decode(old)
end
if select('#', ...) > 0 then
local new = ... or ""
local urienc_len = _urienc_len(new)
local base64_len = _base64_len(new)
if base64_len < urienc_len and Filter then
mediatype = mediatype .. ";base64"
new = _do_filter("base64_encode", new)
else
new = new:gsub("%%", "%%25")
end
M._SUPER.path(self, mediatype .. "," .. new)
end
return old
end
function M.path (self, ...)
local old = M._SUPER.path(self)
if select('#', ...) > 0 then
local new = ...
if not new then error("there must be a path in a data URI") end
local err = _split_path(new)
if err then error("invalid data URI (" .. err .. ")") end
M._SUPER.path(self, new)
end
return old
end
Util.uri_part_not_allowed(M, "userinfo")
Util.uri_part_not_allowed(M, "host")
Util.uri_part_not_allowed(M, "port")
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,72 @@
local M = { _NAME = "uri.file" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)
function M.init (self)
if self:userinfo() or self:port() then
return nil, "usernames and passwords are not allowed in HTTP URIs"
end
local host = self:host()
local path = self:path()
if host then
if host:lower() == "localhost" then self:host("") end
else
if not path:find("^/") then
return nil, "file URIs must contain a host, even if it's empty"
end
self:host("")
end
if path == "" then self:path("/") end
return self
end
function M.host (self, ...)
local old = M._SUPER.host(self)
if select('#', ...) > 0 then
local new = ...
if not new then error("file URIs must have an authority part", 2) end
if new:lower() == "localhost" then new = "" end
M._SUPER.host(self, new)
end
return old
end
function M.path (self, ...)
local old = M._SUPER.path(self)
if select('#', ...) > 0 then
local new = ...
if not new or new == "" then new = "/" end
M._SUPER.path(self, new)
end
return old
end
local function _os_implementation (os)
local FileImpl = Util.attempt_require("uri.file." .. os:lower(), 3)
if not FileImpl then
error("no file URI implementation for operating system " .. os)
end
return FileImpl
end
function M.filesystem_path (self, os)
return _os_implementation(os).filesystem_path(self)
end
function M.make_file_uri (path, os)
return _os_implementation(os).make_file_uri(path)
end
Util.uri_part_not_allowed(M, "userinfo")
Util.uri_part_not_allowed(M, "port")
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,27 @@
local M = { _NAME = "uri.file.unix" }
local URI = require "uri"
local Util = require "uri._util"
function M.filesystem_path (uri)
if uri:host() ~= "" then
error("a file URI with a host name can't be converted to a Unix path",
2)
end
local path = uri:path()
if path:find("%%00") or path:find("%%2F") then
error("Unix paths cannot contain encoded null bytes or slashes", 2)
end
return Util.uri_decode(path)
end
function M.make_file_uri (path)
if not path:find("^/") then
error("Unix relative paths can't be converted to file URIs", 2)
end
path = path:gsub("//+", "/")
path = Util.uri_encode(path, "^A-Za-z0-9%-._~!$&'()*+,;=:@/")
return assert(URI:new("file://" .. path))
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,34 @@
local M = { _NAME = "uri.file.win32" }
local URI = require "uri"
local Util = require "uri._util"
function M.filesystem_path (uri)
local host = uri:host()
local path = Util.uri_decode(uri:path())
if host ~= "" then path = "//" .. host .. path end
if path:find("^/[A-Za-z]|/") or path:find("^/[A-Za-z]|$") then
path = path:gsub("|", ":", 1)
end
if path:find("^/[A-Za-z]:/") then
path = path:sub(2)
elseif path:find("^/[A-Za-z]:$") then
path = path:sub(2) .. "/"
end
path = path:gsub("/", "\\")
return path
end
function M.make_file_uri (path)
if path:find("^[A-Za-z]:$") then path = path .. "\\" end
local _, _, host, hostpath = path:find("^\\\\([A-Za-z.]+)\\(.*)$")
host = host or ""
hostpath = hostpath or path
hostpath = hostpath:gsub("\\", "/")
:gsub("//+", "/")
hostpath = Util.uri_encode(hostpath, "^A-Za-z0-9%-._~!$&'()*+,;=:@/")
if not hostpath:find("^/") then hostpath = "/" .. hostpath end
return assert(URI:new("file://" .. host .. hostpath))
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,54 @@
local M = { _NAME = "uri.ftp" }
local Util = require "uri._util"
local LoginURI = require "uri._login"
Util.subclass_of(M, LoginURI)
function M.default_port () return 21 end
function M.init (self)
local err
self, err = M._SUPER.init_base(self)
if not self then return nil, err end
local host = self:host()
if not host or host == "" then
return nil, "FTP URIs must have a hostname"
end
-- I don't think there's any distinction in FTP URIs between empty path
-- and the root directory, so probably best to normalize as we do for HTTP.
if self:path() == "" then self:path("/") end
return self
end
function M.path (self, ...)
local old = M._SUPER.path(self)
if select("#", ...) > 0 then
local new = ...
if not new or new == "" then new = "/" end
M._SUPER.path(self, new)
end
return old
end
function M.ftp_typecode (self, ...)
local path = M._SUPER.path(self)
local _, _, withouttype, old = path:find("^(.*);type=(.*)$")
if not withouttype then withouttype = path end
if old == "" then old = nil end
if select("#", ...) > 0 then
local new = ...
if not new then new = "" end
if new ~= "" then new = ";type=" .. new end
M._SUPER.path(self, withouttype .. new)
end
return old
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,32 @@
local M = { _NAME = "uri.http" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)
-- This implementation is based on RFC 2616 section 3.2 and RFC 1738
-- section 3.3.
--
-- An HTTP URI with a 'userinfo' field is considered invalid, because it isn't
-- shown in the syntax given in RFC 2616, and is explicitly disallowed by
-- RFC 1738.
function M.default_port () return 80 end
function M.init (self)
if self:userinfo() then
return nil, "usernames and passwords are not allowed in HTTP URIs"
end
-- RFC 2616 section 3.2.3 says that this is OK, but not that using the
-- redundant slash is canonical. I'm adding it because browsers tend to
-- treat the version with the extra slash as the normalized form, and
-- the initial slash is always present in an HTTP GET request.
if self:path() == "" then self:path("/") end
return self
end
Util.uri_part_not_allowed(M, "userinfo")
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,9 @@
local M = { _NAME = "uri.https" }
local Util = require "uri._util"
local Http = require "uri.http"
Util.subclass_of(M, Http)
function M.default_port () return 443 end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,111 @@
local M = { _NAME = "uri.pop" }
local URI = require "uri"
local Util = require "uri._util"
Util.subclass_of(M, URI)
-- This is the set of characters must be encoded in a POP userinfo, which
-- unlike for other schemes includes the ';' character.
local _POP_USERINFO_ENCODE = "^A-Za-z0-9%-._~%%!$&'()*+,=:"
function M.default_port () return 110 end
local function _update_userinfo (self, old, new)
if new then
local _, _, user, auth = new:find("^(.*);[Aa][Uu][Tt][Hh]=(.*)$")
if not user then user = new end
if user == "" then return "pop user name must not be empty" end
user = Util.uri_encode(user, _POP_USERINFO_ENCODE)
if auth then
if auth == "" then return "pop auth type must not be empty" end
if auth == "*" then auth = nil end
auth = Util.uri_encode(auth, _POP_USERINFO_ENCODE)
end
new = user .. (auth and ";auth=" .. auth or "")
end
if new ~= old then M._SUPER.userinfo(self, new) end
return nil
end
function M.init (self)
if M._SUPER.path(self) ~= "" then
return nil, "pop URIs must have an empty path"
end
local userinfo = M._SUPER.userinfo(self)
local err = _update_userinfo(self, userinfo, userinfo)
if err then return nil, err end
return self
end
function M.userinfo (self, ...)
local old = M._SUPER.userinfo(self)
if select('#', ...) > 0 then
local new = ...
local err = _update_userinfo(self, old, new)
if err then error(err, 2) end
end
return old
end
function M.path (self, new)
if new and new ~= "" then error("POP URIs must have an empty path", 2) end
return ""
end
local function _decode_userinfo (self)
local old = M._SUPER.userinfo(self)
if not old then return nil, nil end
local _, _, old_user, old_auth = old:find("^(.*);auth=(.*)$")
if not old_user then old_user = old end
return old_user, old_auth
end
function M.pop_user (self, ...)
local old_user, old_auth = _decode_userinfo(self)
if select('#', ...) > 0 then
local new = ...
if new == "" then error("pop user name must not be empty", 2) end
if not new and old_auth then
error("pop user name required when an auth type is specified", 2)
end
if new then
new = Util.uri_encode(new, _POP_USERINFO_ENCODE)
if old_auth then new = new .. ";auth=" .. old_auth end
end
M._SUPER.userinfo(self, new)
end
return Util.uri_decode(old_user)
end
function M.pop_auth (self, ...)
local old_user, old_auth = _decode_userinfo(self)
if select('#', ...) > 0 then
local new = ...
if not new or new == ""
then error("pop auth type must not be empty", 2)
end
if new == "*" then new = nil end
if new and not old_user then
error("pop auth type can't be specified without user name", 2)
end
if new then
new = old_user .. ";auth=" ..
Util.uri_encode(new, _POP_USERINFO_ENCODE)
else
new = old_user
end
M._SUPER.userinfo(self, new)
end
return old_auth and Util.uri_decode(old_auth) or "*"
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,9 @@
local M = { _NAME = "uri.rtsp" }
local Util = require "uri._util"
local HttpURI = require "uri.http"
Util.subclass_of(M, HttpURI)
function M.default_port () return 554 end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,7 @@
local M = { _NAME = "uri.rtspu" }
local Util = require "uri._util"
local RtspURI = require "uri.rtsp"
Util.subclass_of(M, RtspURI)
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,39 @@
local M = { _NAME = "uri.telnet" }
local Util = require "uri._util"
local LoginURI = require "uri._login"
Util.subclass_of(M, LoginURI)
function M.default_port () return 23 end
function M.init (self)
local err
self, err = M._SUPER.init_base(self)
if not self then return nil, err end
-- RFC 4248 does not discuss what a path longer than '/' might mean, and
-- there are no examples with anything significant in the path, so I'm
-- assuming that extra information in the path is not allowed.
local path = M._SUPER.path(self)
if path ~= "" and path ~= "/" then
return nil, "superfluous information in path of telnet URI"
end
-- RFC 4248 section 2 says that the '/' can be omitted. I chose to
-- normalize to having it there, since the example shown in the RFC has
-- it, and this is consistent with the way I treat HTTP URIs.
if path == "" then self:path("/") end
return self
end
-- The path is always '/', so setting it won't do anything, but we do throw
-- an exception on an attempt to set it to anything invalid.
function M.path (self, new)
if new and new ~= "" and new ~= "/" then
error("invalid path for telnet URI", 2)
end
return "/"
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,133 @@
local M = { _NAME = "uri.urn" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)
-- This implements RFC 2141, and attempts to change the class of the URI object
-- to one of its subclasses for further validation and normalization of the
-- namespace-specific string.
-- Check NID syntax matches RFC 2141 section 2.1.
local function _valid_nid (nid)
if nid == "" then return nil, "missing completely" end
if nid:len() > 32 then return nil, "too long" end
if not nid:find("^[A-Za-z0-9][-A-Za-z0-9]*$") then
return nil, "contains illegal character"
end
if nid:lower() == "urn" then return nil, "'urn' is reserved" end
return true
end
-- Check NSS syntax matches RFC 2141 section 2.2.
local function _valid_nss (nss)
if nss == "" then return nil, "can't be empty" end
if nss:find("[^A-Za-z0-9()+,%-.:=@;$_!*'/%%]") then
return nil, "contains illegal character"
end
return true
end
local function _validate_and_normalize_path (path)
local _, _, nid, nss = path:find("^([^:]+):(.*)$")
if not nid then return nil, "illegal path syntax for URN" end
local ok, msg = _valid_nid(nid)
if not ok then
return nil, "invalid namespace identifier (" .. msg .. ")"
end
ok, msg = _valid_nss(nss)
if not ok then
return nil, "invalid namespace specific string (" .. msg .. ")"
end
return nid:lower() .. ":" .. nss
end
-- TODO - this should check that percent-encoded bytes are valid UTF-8
function M.init (self)
if M._SUPER.query(self) then
return nil, "URNs may not have query parts"
end
if M._SUPER.host(self) then
return nil, "URNs may not have authority parts"
end
local path, msg = _validate_and_normalize_path(self:path())
if not path then return nil, msg end
M._SUPER.path(self, path)
local nid_class
= Util.attempt_require("uri.urn." .. self:nid():gsub("%-", "_"))
if nid_class then
setmetatable(self, nid_class)
if self.init ~= M.init then return self:init() end
end
return self
end
function M.nid (self, new)
local _, _, old = self:path():find("^([^:]+)")
if new then
new = new:lower()
if new ~= old then
local ok, msg = _valid_nid(new)
if not ok then
error("invalid namespace identifier (" .. msg .. ")", 2)
end
end
Util.do_class_changing_change(self, M, "NID", new, function (uri, new)
M._SUPER.path(uri, new .. ":" .. uri:nss())
end)
end
return old
end
function M.nss (self, new)
local _, _, old = self:path():find(":(.*)")
if new and new ~= old then
local ok, msg = _valid_nss(new)
if not ok then
error("invalid namespace specific string (" .. msg .. ")", 2)
end
M._SUPER.path(self, self:nid() .. ":" .. new)
end
return old
end
function M.path (self, new)
local old = M._SUPER.path(self)
if new and new ~= old then
local path, msg = _validate_and_normalize_path(new)
if not path then
error("invalid path for URN '" .. new .. "' (" ..msg .. ")", 2)
end
local _, _, newnid, newnss = path:find("^([^:]+):(.*)")
if not newnid then error("bad path for URN, no NID part found", 2) end
local ok, msg = _valid_nid(newnid)
if not ok then
error("invalid namespace identifier (" .. msg .. ")", 2)
end
if newnid:lower() == self:nid() then
self:nss(newnss)
else
Util.do_class_changing_change(self, M, "path", path,
function (uri, new) M._SUPER.path(uri, new) end)
end
end
return old
end
Util.uri_part_not_allowed(M, "userinfo")
Util.uri_part_not_allowed(M, "host")
Util.uri_part_not_allowed(M, "port")
Util.uri_part_not_allowed(M, "query")
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,67 @@
local M = { _NAME = "uri.urn.isbn" }
local Util = require "uri._util"
local URN = require "uri.urn"
Util.subclass_of(M, URN)
-- This implements the 'isbn' NID defined in RFC 3187, and is consistent
-- with the same NID suggested in RFC 2288.
local function _valid_isbn (isbn)
if not isbn:find("^[-%d]+[%dXx]$") then return nil, "invalid character" end
local ISBN = Util.attempt_require("isbn")
if ISBN then return ISBN:new(isbn) end
return isbn
end
local function _normalize_isbn (isbn)
isbn = isbn:gsub("%-", ""):upper()
local ISBN = Util.attempt_require("isbn")
if ISBN then return tostring(ISBN:new(isbn)) end
return isbn
end
function M.init (self)
local nss = self:nss()
local ok, msg = _valid_isbn(nss)
if not ok then return nil, "invalid ISBN value (" .. msg .. ")" end
self:nss(_normalize_isbn(nss))
return self
end
function M.nss (self, new)
local old = M._SUPER.nss(self)
if new then
local ok, msg = _valid_isbn(new)
if not ok then
error("bad ISBN value '" .. new .. "' (" .. msg .. ")", 2)
end
M._SUPER.nss(self, _normalize_isbn(new))
end
return old
end
function M.isbn_digits (self, new)
local old = self:nss():gsub("%-", "")
if new then
local ok, msg = _valid_isbn(new)
if not ok then
error("bad ISBN value '" .. new .. "' (" .. msg .. ")", 2)
end
self._SUPER.nss(self, _normalize_isbn(new))
end
return old
end
function M.isbn (self, new)
local ISBN = require "isbn"
local old = ISBN:new(self:nss())
if new then self:nss(tostring(new)) end
return old
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,65 @@
local M = { _NAME = "uri.urn.issn" }
local Util = require "uri._util"
local URN = require "uri.urn"
Util.subclass_of(M, URN)
local function _parse_issn (issn)
local _, _, nums1, nums2, checksum
= issn:find("^(%d%d%d%d)-?(%d%d%d)([%dxX])$")
if checksum == "x" then checksum = "X" end
return nums1, nums2, checksum
end
local function _valid_issn (issn)
local nums1, nums2, actual_checksum = _parse_issn(issn)
if not nums1 then return nil, "invalid ISSN syntax" end
local nums = nums1 .. nums2
local expected_checksum = 0
for i = 1, 7 do
expected_checksum = expected_checksum + tonumber(nums:sub(i, i)) * (9 - i)
end
expected_checksum = (11 - expected_checksum % 11) % 11
expected_checksum = (expected_checksum == 10) and "X"
or tostring(expected_checksum)
if actual_checksum ~= expected_checksum then
return nil, "wrong checksum, expected " .. expected_checksum
end
return true
end
local function _normalize_issn (issn)
local nums1, nums2, checksum = _parse_issn(issn)
return nums1 .. "-" .. nums2 .. checksum
end
function M.init (self)
local nss = self:nss()
local ok, msg = _valid_issn(nss)
if not ok then return nil, "bad NSS value for ISSN URI (" .. msg .. ")" end
M._SUPER.nss(self, _normalize_issn(nss))
return self
end
function M.nss (self, new)
local old = M._SUPER.nss(self)
if new then
local ok, msg = _valid_issn(new)
if not ok then
error("bad ISSN value '" .. new .. "' (" .. msg .. ")", 2)
end
M._SUPER.nss(self, _normalize_issn(new))
end
return old
end
function M.issn_digits (self, new)
local old = self:nss(new)
return old:sub(1, 4) .. old:sub(6, 9)
end
return M
-- vi:ts=4 sw=4 expandtab

@ -0,0 +1,64 @@
local M = { _NAME = "uri.urn.oid" }
local Util = require "uri._util"
local URN = require "uri.urn"
Util.subclass_of(M, URN)
-- This implements RFC 3061.
local function _valid_oid (oid)
if oid == "" then return nil, "OID can't be zero-length" end
if not oid:find("^[.0-9]*$") then return nil, "bad character in OID" end
if oid:find("%.%.") then return nil, "missing number in OID" end
if oid:find("^0[^.]") or oid:find("%.0[^.]") then
return nil, "OID numbers shouldn't have leading zeros"
end
return true
end
function M.init (self)
local nss = self:nss()
local ok, msg = _valid_oid(nss)
if not ok then return nil, "bad NSS value for OID URI (" .. msg .. ")" end
return self
end
function M.nss (self, new)
local old = M._SUPER.nss(self)
if new then
local ok, msg = _valid_oid(new)
if not ok then
error("bad OID value '" .. new .. "' (" .. msg .. ")", 2)
end
M._SUPER.nss(self, new)
end
return old
end
function M.oid_numbers (self, new)
local old = Util.split("%.", self:nss())
for i = 1, #old do old[i] = tonumber(old[i]) end
if new then
if type(new) ~= "table" then error("expected array of numbers", 2) end
local nss = ""
for _, n in ipairs(new) do
if type(n) == "string" and n:find("^%d+$") then n = tonumber(n) end
if type(n) ~= "number" then
error("bad type for number in OID", 2)
end
n = n - n % 1
if n < 0 then error("negative numbers not allowed in OID", 2) end
if nss ~= "" then nss = nss .. "." end
nss = nss .. n
end
if nss == "" then error("no numbers in new OID value", 2) end
self:nss(nss)
end
return old
end
return M
-- vi:ts=4 sw=4 expandtab
Loading…
Cancel
Save