mirror of https://github.com/sipwise/lua-uri.git
commit
ed606df300
@ -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>
|
@ -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/
|
@ -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…
Reference in new issue