Fixes to urltest binary, added and tweaked tests for 100% coverage of URL library (fixed a few URL library bugs in the process, too)

Change-Id: I24a1f014cb21b8ec0062ee79e3e6ba717b392496
This commit is contained in:
Thulinma 2023-01-26 09:17:32 +01:00
parent 90321887cc
commit ff36880cc8
4 changed files with 210 additions and 142 deletions

View file

@ -12,100 +12,132 @@ websockettest = executable('websockettest', 'websocket.cpp', dependencies: libmi
# Actual unit tests
urltest = executable('urltest', 'url.cpp', dependencies: libmist_dep)
test('url test google', urltest,
env : ['Protocol=https', 'Host=google.com', 'Local=No', 'Port=443', 'Path=directory/', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://google.com/directory/subdirectory/..'])
test('url test schmerkel', urltest,
env : ['Protocol=http', 'Host=root', 'Local=No', 'Port=80', 'Path=home/mo','Query=', 'Fragment=', 'Username=', 'Password='],
args : ['http://root/home/schmerkel/../mo'])
test('url test relpath', urltest,
env : ['Protocol=https', 'Host=relpath.com', 'Local=No', 'Port=443', 'Path=','Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://relpath.com/rel/lang/../..'])
test('url test relpath missing dot', urltest,
env : ['Protocol=https', 'Host=relpath.com', 'Local=No', 'Port=443', 'Path=', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://relpath.com/rel/./..'])
test('url test relpath 1', urltest,
env : ['Protocol=https', 'Host=relpath.com', 'Local=No', 'Port=443', 'Path=','Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://relpath.com/1/..'])
test('url test relpath 123', urltest,
env : ['Protocol=https', 'Host=relpath.com', 'Local=No', 'Port=443', 'Path=','Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://relpath.com/123/..'])
test('url test relpath 2', urltest,
env : ['Protocol=https', 'Host=relpath.com', 'Local=No', 'Port=443', 'Path=','Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://relpath.com/rel/../..'])
test('url test relpath langer pad', urltest,
env : ['Protocol=https', 'Host=relpath.com', 'Local=No', 'Port=443', 'Path=langer/', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['https://relpath.com/langer/pad/..'])
test('url test authority 3000', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=3000', 'Path=path/to/some/file.cpp', 'Query=bool=true&int=3', 'Fragment=frag', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl:3000/path/to/err/../some/file.cpp?bool=true&int=3#frag'])
test('url test authority path', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=path/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/path/weird/./..'])
test('url test authority path wierd', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=path/weird/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/path/weird/a/..'])
test('url test authority p&at#h/', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=p&at#h/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/p%26at%23h/'])
test('url test authority p at!h/', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=p at!h/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/p%20at%21h/'])
# KATRI TODO Cannot test pass as env ' %27 , need escape char for meson string ''
test('url test authority !"#$%&()/', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path= !"#$%&()/','Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/%20%21%22%23%24%25%26%28%29/'])
test('url test authority *+,-.//', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=*+,-.//', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/%2A%2B%2C%2D%2E%2F/'])
test('url test authority 0123456789/', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=0123456789/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/%30%31%32%33%34%35%36%37%38%39/'])
test('url test authority _;<=>?@/', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=:;<=>?@/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/%3A%3B%3C%3D%3E%3F%40/'])
test('url test authority ABCDEFGHI/', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=ABCDEFGHI/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/%41%42%43%44%45%46%47%48%49/'])
test('url test doe/iets', urltest,
env : ['Protocol=file', 'Host=google.com', 'Local=No', 'Port=0', 'Path=doe/iets', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['file://google.com/doe/iets'])
test('url test sub1/sub2/', urltest,
env : ['Protocol=file', 'Host=google.com', 'Local=No', 'Port=0', 'Path=sub1/sub2/', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['file://google.com/sub1/sub2/'])
test('url test sub1/sub2/ anders', urltest,
env : ['Protocol=file', 'Host=google.com', 'Local=No', 'Port=0', 'Path=sub1/sub2/www.wiki.com/anders', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['file://google.com/sub1/sub2/www.wiki.com/anders'])
test('url test sub1/sub2/ relatief', urltest,
env : ['Protocol=file', 'Host=google.com', 'Local=No', 'Port=0', 'Path=sub1/sub2/www.wiki.com/relatief/', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['file://google.com/sub1/sub2/www.wiki.com/relatief/'])
test('url test absoluut', urltest,
env : ['Protocol=file', 'Host=google.com', 'Local=No', 'Port=0', 'Path=absoluut/', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['file://google.com/absoluut/'])
test('url test absoluut relatiefFile', urltest,
env : ['Protocol=file', 'Host=google.com', 'Local=No', 'Port=0', 'Path=absoluut/relatiefFile', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['file://google.com/absoluut/relatiefFile'])
test('url test path empty', urltest,
env : ['Protocol=http', 'Host=www.wiki.com', 'Local=No', 'Port=80', 'Path=', 'Query=', 'Fragment=', 'Username=', 'Password='],
args : ['http://www.wiki.com/'])
test('url test path emoticons', urltest,
env : ['Protocol=protocol', 'Host=authority.nl', 'Local=No', 'Port=0', 'Path=😋😄😡😵/', 'Query=', 'Fragment=', 'Username=uname', 'Password=pwd'],
args : ['protocol://uname:pwd@authority.nl/😋😄😡😵/'])
test('url test rtmp 1', urltest,
env : ['Protocol=https', 'Host=2001:db8::1', 'Local=No', 'Port=159', 'Path=', 'Query=', 'Fragment=', 'Username=', 'Password='],
urltest_vm = {'T_PATH':'', 'T_QUERY':'', 'T_FRAG':'', 'T_USER':'', 'T_PASS':'', 'T_NORM':'', 'T_EXT':''}
urltest_v = urltest_vm + {'T_PROTO':'', 'T_HOST':'', 'T_PORT':'0'}
test('HTTPS URL with parent directory', urltest, suite: 'URL parser',
env: urltest_v + {'T_PROTO':'https', 'T_HOST':'google.com', 'T_PORT':'443', 'T_PATH':'directory/', 'T_NORM':'https://google.com/directory/'},
args: ['https://google.com/directory/subdirectory/..'])
test('Partially cancelled out path', urltest, suite: 'URL parser',
env: urltest_v + {'T_PROTO':'http', 'T_HOST':'root', 'T_PORT':'80', 'T_PATH':'home/mo', 'T_NORM':'http://root/home/mo'},
args: ['http://root/home/schmerkel/../mo'])
urltest_relpath = urltest_v + {'T_PROTO':'https', 'T_HOST':'relpath.com', 'T_PORT':'443', 'T_NORM':'https://relpath.com/'}
test('Fully cancelled out path', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/rel/lang/../..'])
test('Current directory reference stripping', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/rel/./..'])
test('Empty directory stripping', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/////'])
test('Numerical path deletion', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/123/..'])
test('Leading single char path deletion', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/a/..'])
test('Trailing current dir path deletion', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/.'])
test('Trailing current dir path deletion', urltest, suite: 'URL parser',
env: urltest_v + {'T_PATH':'bla/', 'T_NORM':'/bla/'},
args: ['///bla/.'])
test('Extension', urltest, suite: 'URL parser',
env: urltest_v + {'T_EXT':'mp4', 'T_PATH':'test.mp4', 'T_NORM':'/test.mp4'},
args: ['///test.mp4'])
test('Parent of root directory', urltest, suite: 'URL parser',
env: urltest_relpath,
args: ['https://relpath.com/rel/../..'])
test('All URL components present', urltest, suite: 'URL parser',
env: {'T_PROTO':'prot', 'T_HOST':'a.bc', 'T_PORT':'3000', 'T_PATH':'path/to/some/file.cpp', 'T_QUERY':'bool=true&int=3', 'T_FRAG':'frag', 'T_USER':'uname', 'T_PASS':'pwd', 'T_NORM':'prot://uname:pwd@a.bc:3000/path/to/some/file.cpp?bool=true&int=3#frag'},
args: ['prot://uname:pwd@a.bc:3000/path/to/err/../some/file.cpp?bool=true&int=3#frag'])
test('Unknown port for custom protocol', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'authority.nl', 'T_PATH':'path/', 'T_NORM':'prot://authority.nl/path/'},
args : ['prot://authority.nl/path/weird/./..'])
test('Query/fragment chars before ? char (escaped)', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'authority.nl', 'T_PATH':'p&a=t#h/', 'T_NORM':'prot://authority.nl/p%26a=t%23h/'},
args : ['prot://authority.nl/p%26a=t%23h/'])
test('Query/fragment chars before ? char (unescaped)', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'authority.nl', 'T_PATH':'p&a=t', 'T_FRAG':'h/', 'T_NORM':'prot://authority.nl/p%26a=t#h/'},
args : ['prot://authority.nl/p&a=t#h/'])
test('Path with spaces', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'authority.nl', 'T_PATH':'p at!h/', 'T_NORM':'prot://authority.nl/p+at!h/'},
args : ['prot://authority.nl/p%20at%21h/'])
test('Escaped characters', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'authority.nl', 'T_PATH':' !"#$%&()/*+,-.//0123456789/:;<=>?@/', 'T_NORM':'prot://authority.nl/+!%22%23$%25%26()/*%2b,-.//0123456789/:;%3c=%3e%3f@/'},
args : ['prot://authority.nl/%20%21%22%23%24%25%26%28%29/%2A%2B%2C%2D%2E%2F/%30%31%32%33%34%35%36%37%38%39/%3A%3B%3C%3D%3E%3F%40/'])
test('Linking / Absolute', urltest, suite: 'URL parser',
env : urltest_vm + {'T_PATH':'sub1/sub2/', 'T_NORM':'/sub1/sub2/'},
args : ['file://google.com/doe/iets', '/sub1/sub2/'])
test('Linking / Relative', urltest, suite: 'URL parser',
env : urltest_vm + {'T_PATH':'sub1/sub2/www.wiki.com/anders', 'T_NORM':'/sub1/sub2/www.wiki.com/anders'},
args : ['file://google.com/doe/iets', '/sub1/sub2/', 'www.wiki.com/anders'])
test('Linking / Relative with current and parent dir', urltest, suite: 'URL parser',
env : urltest_vm + {'T_PATH':'sub1/sub2/relatief/', 'T_NORM':'/sub1/sub2/relatief/'},
args : ['file://google.com/doe/iets', '/sub1/sub2/', 'www.wiki.com/anders', './../relatief/'])
test('Linking / Relative file in absolute directory', urltest, suite: 'URL parser',
env : urltest_vm + {'T_PATH':'absoluut/relatiefFile', 'T_NORM':'/absoluut/relatiefFile'},
args : ['file://google.com/doe/iets', '/sub1/sub2/', 'www.wiki.com/anders', './../relatief/', '/absoluut/', 'relatiefFile'])
test('Linking / Protocol switch', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'http', 'T_HOST':'www.wiki.com', 'T_PORT':'80', 'T_NORM':'http://www.wiki.com/'},
args : ['file://google.com/doe/iets', '/absoluut/', 'relatiefFile', 'http://www.wiki.com'])
test('Linking / Protocol absolute', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'http', 'T_HOST':'example.com', 'T_PORT':'80', 'T_NORM':'http://example.com/'},
args : ['http://www.wiki.com', '//example.com'])
test('UTF-8 emoji in path', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'authority.nl', 'T_PATH':'😋😄😡😵/', 'T_NORM':'prot://authority.nl/%f0%9f%98%8b%f0%9f%98%84%f0%9f%98%a1%f0%9f%98%b5/'},
args : ['prot://authority.nl/😋😄😡😵/'])
test('UTF-8 reverse char in path', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'prot', 'T_HOST':'a.nl', 'T_PATH':'reversereverse/', 'T_NORM':'prot://a.nl/reverse%e2%80%8freverse/'},
args : ['prot://a.nl/reverse%E2%80%8Freverse/'])
test('IPv6 address', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'https', 'T_HOST':'2001:db8::1', 'T_PORT':'159', 'T_NORM':'https://[2001:db8::1]:159/'},
args : ['https://[2001:db8::1]:159/'])
test('url test rtpm 2', urltest,
env : ['Protocol=rtmp', 'Host=2001:db8::1', 'Local=No', 'Port=1935', 'Path=','Query=', 'Fragment=', 'Username=', 'Password='],
test('IPv6 address with non-numeric port', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'https', 'T_HOST':'2001:db8::aa', 'T_PORT':'443', 'T_NORM':'https://[2001:db8::aa]/'},
args : ['https://[2001:db8:]:aa/'])
test('Bare IPv6 address', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'https', 'T_HOST':'2001:db8::1', 'T_PORT':'159', 'T_NORM':'https://[2001:db8::1]:159/'},
args : ['https://2001:db8::1:159/'])
test('Bare hexadecimal IPv6 address', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'https', 'T_HOST':'2001:db8::aa', 'T_PORT':'443', 'T_NORM':'https://[2001:db8::aa]/'},
args : ['https://2001:db8::aa/'])
test('Username but no password', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'http', 'T_HOST':'b', 'T_PORT':'80', 'T_USER':'a', 'T_NORM':'http://a@b/'},
args : ['http://a@b'])
test('Username and password linking', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'http', 'T_HOST':'c', 'T_PORT':'80', 'T_PASS':'b', 'T_USER':'a', 'T_NORM':'http://a:b@c/test', 'T_PATH':'test'},
args : ['http://a:b@c', 'test'])
test('Username but no password linking', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'http', 'T_HOST':'c', 'T_PORT':'80', 'T_USER':'a', 'T_NORM':'http://a@c/test', 'T_PATH':'test'},
args : ['http://a@c', 'test'])
test('Protocol-absolute base', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'', 'T_HOST':'test', 'T_NORM':'//test/'},
args : ['//test'])
test('Bare port number', urltest, suite: 'URL parser',
env : urltest_v + {'T_PORT':'42', 'T_NORM':'//:42/'},
args : ['42'])
test('Bare port number linking', urltest, suite: 'URL parser',
env : urltest_v + {'T_PORT':'42', 'T_NORM':'//:42/test', 'T_PATH':'test'},
args : ['42', 'test'])
test('Query string without path', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'', 'T_HOST':'test', 'T_QUERY':'test=test', 'T_NORM':'//test/?test=test'},
args : ['//test?test=test'])
test('RTMP IPv6 address', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'rtmp', 'T_HOST':'2001:db8::1', 'T_PORT':'1935', 'T_NORM':'rtmp://[2001:db8::1]/'},
args : ['rtmp://[2001:db8::1]/'])
test('url test rtpms', urltest,
env : ['Protocol=rtmps', 'Host=2001:db8::1', 'Local=No', 'Port=443', 'Path=', 'Query=', 'Fragment=', 'Username=', 'Password='],
test('RTMPS', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'rtmps', 'T_HOST':'2001:db8::1', 'T_PORT':'443', 'T_NORM':'rtmps://[2001:db8::1]/'},
args : ['rtmps://[2001:db8::1]/'])
test('url test rtpm 2', urltest,
env : ['Protocol=dtsc', 'Host=2001:db8::1', 'Local=No', 'Port=4200', 'Path=', 'Query=', 'Fragment=', 'Username=', 'Password='],
test('DTSC', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'dtsc', 'T_HOST':'2001:db8::1', 'T_PORT':'4200', 'T_NORM':'dtsc://[2001:db8::1]/'},
args : ['dtsc://[2001:db8::1]/'])
test('url test rtsp', urltest,
env : ['Protocol=rtsp', 'Host=2001:db8::1', 'Local=No', 'Port=554', 'Path=', 'Query=', 'Fragment=', 'Username=', 'Password='],
test('RTSP', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'rtsp', 'T_HOST':'2001:db8::1', 'T_PORT':'554', 'T_NORM':'rtsp://[2001:db8::1]/'},
args : ['rtsp://[2001:db8::1]/'])
test('RTSP linking', urltest, suite: 'URL parser',
env : urltest_v + {'T_PROTO':'rtsp', 'T_HOST':'2001:db8::1', 'T_PORT':'554', 'T_NORM':'rtsp://[2001:db8::1]/test', 'T_PATH':'test'},
args : ['rtsp://[2001:db8::1]/', 'test'])

View file

@ -1,7 +1,34 @@
#include "../lib/http_parser.cpp"
#include <cassert>
#include <mist/url.h>
#include <mist/http_parser.h>
#include <mist/json.h>
#include <iostream>
/// Helper function that compares an environment variable against a string
int checkStr(const char * envVar, const std::string & str){
//Ignore test when no expected value set
if (!getenv(envVar)){return 0;}
//Environment value exists, do check
if (str != getenv(envVar)){
//Print error message on mismatch, detailing problem
std::cerr << "ERROR: Value of " << envVar << " should be '" << getenv(envVar) << "' but was '" << str << "'" << std::endl;
return 1;
}
return 0;
}
/// Helper function that compares an environment variable against an integer
int checkInt(const char * envVar, const uint64_t i){
//Ignore test when no expected value set
if (!getenv(envVar)){return 0;}
//Environment value exists, do check
if (i != JSON::Value(getenv(envVar)).asInt()){
//Print error message on mismatch, detailing problem
std::cerr << "ERROR: Value of " << envVar << " should be '" << getenv(envVar) << "' but was '" << i << "'" << std::endl;
return 1;
}
return 0;
}
int main(int argc, char **argv){
if (argc < 2){
std::cout << "Usage: " << argv[0] << " URL" << std::endl;
@ -9,34 +36,38 @@ int main(int argc, char **argv){
}
HTTP::URL u(argv[1]);
for (int i = 1; i < argc; ++i){
HTTP::URL prev = u;
if (i > 1){u = u.link(argv[i]);}
std::cout << argv[i] << " -> " << u.getUrl() << std::endl;
std::cout << argv[i] << " -> " << (u.isLocalPath()?u.getFilePath():u.getUrl()) << std::endl;
if (i > 1){
std::cout << "Link from previous: " << u.getLinkFrom(prev) << std::endl;
}
std::cout << "Proxied URL: " << u.getProxyUrl() << std::endl;
std::cout << "Protocol: " << u.protocol << std::endl;
std::cout << "Host: " << u.host << " (Local: " << (Socket::isLocalhost(u.host) ? "Yes" : "No")
<< ")" << std::endl;
std::cout << "Port: " << u.getPort() << std::endl;
std::cout << "Path: " << u.path << std::endl;
std::cout << "Extension: " << u.getExt() << std::endl;
std::cout << "Query: " << u.args << std::endl;
std::cout << "Fragment: " << u.frag << std::endl;
std::cout << "Username: " << u.user << std::endl;
std::cout << "Password: " << u.pass << std::endl;
std::cout << std::endl;
assert(u.protocol == std::getenv("Protocol"));
assert(u.host == std::getenv("Host"));
std::string ulocal;
Socket::isLocalhost(u.host) ? ulocal = "Yes" : ulocal = "No";
assert(ulocal == std::getenv("Local"));
uint16_t uport = 0;
std::stringstream ss(std::getenv("Port"));
ss >> uport;
assert(u.getPort() == uport);
assert(u.path == std::getenv("Path"));
assert(u.args == std::getenv("Query"));
assert(u.frag == std::getenv("Fragment"));
assert(u.user == std::getenv("Username"));
assert(u.pass == std::getenv("Password"));
}
return 0;
int ret = 0;
//These checks only run when the environment variable corresponding to them is set
ret += checkStr("T_PROTO", u.protocol);
ret += checkStr("T_HOST", u.host);
ret += checkInt("T_PORT", u.getPort());
ret += checkStr("T_PATH", u.path);
ret += checkStr("T_QUERY", u.args);
ret += checkStr("T_FRAG", u.frag);
ret += checkStr("T_USER", u.user);
ret += checkStr("T_PASS", u.pass);
ret += checkStr("T_EXT", u.getExt());
ret += checkStr("T_NORM", u.isLocalPath()?u.getFilePath():u.getUrl());
return ret;
}