From ff36880cc82b5524b9c5e17947180333dbfa6579 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Thu, 26 Jan 2023 09:17:32 +0100 Subject: [PATCH] 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 --- lib/url.cpp | 72 ++++++++-------- lib/url.h | 1 + test/meson.build | 210 +++++++++++++++++++++++++++-------------------- test/url.cpp | 69 +++++++++++----- 4 files changed, 210 insertions(+), 142 deletions(-) diff --git a/lib/url.cpp b/lib/url.cpp index 18cafebf..89118121 100644 --- a/lib/url.cpp +++ b/lib/url.cpp @@ -75,18 +75,20 @@ HTTP::URL::URL(const std::string &url){ if (path.substr(0, 2) == "./"){path.erase(0, 2);} if (path.substr(0, 3) == "../"){path.erase(0, 3);} //RFC 2396 sec 5.2: check if URL ends with /.. -> remove iff name != .. - if (path.length() == 2 && path == "..") - path = ""; - if (path.length() > 2 && path.substr(path.length() - 2) == ".."){ - // || == 1, so != '..' - if (path.length() == 4){ - path.erase(path.length() - 4, path.length()); + if (path.length() == 2 && path == ".."){path.clear();} + if (path.length() == 1 && path == "."){path.clear();} + if (path.length() > 2 && path.substr(path.length() - 3) == "/.."){ + if (path.length() <= 4){ + path.clear(); } else if (path.length() > 4 && path.substr(path.length() - 5) != "../.."){ size_t prevslash = path.rfind('/', path.length() - 4); path.erase(prevslash + 1, path.length()); } } + if (path.length() > 1 && path.substr(path.length() - 2) == "/."){ + path.erase(path.length()-1); + } if (!isLocalPath()){ path = Encodings::URL::decode(path); } @@ -143,6 +145,7 @@ HTTP::URL::URL(const std::string &url){ port = ""; } } + if (host.find(':') != std::string::npos){IPv6Addr = true;} } // if the host is numeric, assume it is a port, instead if (host.size() && is_numeric(host.c_str())){ @@ -174,7 +177,11 @@ uint16_t HTTP::URL::getDefaultPort() const{ /// Returns the file extension of the URL, or an empty string if none. std::string HTTP::URL::getExt() const{ + //No dot? No extension. if (path.rfind('.') == std::string::npos){return "";} + //No dot before directory change? No extension. + if (path.rfind('/') != std::string::npos && path.rfind('/') > path.rfind('.')){return "";} + //Otherwise, anything behind the last dot return path.substr(path.rfind('.') + 1); } @@ -187,7 +194,11 @@ std::string HTTP::URL::getUrl() const{ ret = "//"; } if (user.size() || pass.size()){ - ret += Encodings::URL::encode(user) + ":" + Encodings::URL::encode(pass) + "@"; + if (!pass.size()){ + ret += Encodings::URL::encode(user) + "@"; + }else{ + ret += Encodings::URL::encode(user) + ":" + Encodings::URL::encode(pass) + "@"; + } } if (IPv6Addr){ ret += "[" + host + "]"; @@ -196,13 +207,7 @@ std::string HTTP::URL::getUrl() const{ } if (port.size() && getPort() != getDefaultPort()){ret += ":" + port;} ret += "/"; - if (protocol == "rtsp"){ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]#?&");} - }else if (isLocalPath()){ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]+ ");} - }else{ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]");} - } + ret += getEncodedPath(); if (args.size()){ret += "?" + args;} if (frag.size()){ret += "#" + Encodings::URL::encode(frag, "/:=@[]#?&");} return ret; @@ -213,6 +218,17 @@ std::string HTTP::URL::getFilePath() const{ return "/" + path; } +std::string HTTP::URL::getEncodedPath() const{ + if (protocol == "rtsp"){ + if (path.size()){return Encodings::URL::encode(path, "/:=@[]#?&");} + }else if (isLocalPath()){ + if (path.size()){return Encodings::URL::encode(path, "/:=@[]+ ");} + }else{ + if (path.size()){return Encodings::URL::encode(path, "/:=@[]");} + } + return ""; +} + /// Returns whether the URL is probably pointing to a local file bool HTTP::URL::isLocalPath() const{ //Anything with a "file" protocol is explicitly a local file @@ -240,13 +256,7 @@ std::string HTTP::URL::getProxyUrl() const{ } if (port.size() && getPort() != getDefaultPort()){ret += ":" + port;} ret += "/"; - if (protocol == "rtsp"){ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]#?&");} - }else if (isLocalPath()){ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]+ ");} - }else{ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]");} - } + ret += getEncodedPath(); if (args.size()){ret += "?" + args;} return ret; } @@ -260,7 +270,11 @@ std::string HTTP::URL::getBareUrl() const{ ret = "//"; } if (user.size() || pass.size()){ - ret += Encodings::URL::encode(user) + ":" + Encodings::URL::encode(pass) + "@"; + if (!pass.size()){ + ret += Encodings::URL::encode(user) + "@"; + }else{ + ret += Encodings::URL::encode(user) + ":" + Encodings::URL::encode(pass) + "@"; + } } if (IPv6Addr){ ret += "[" + host + "]"; @@ -269,13 +283,7 @@ std::string HTTP::URL::getBareUrl() const{ } if (port.size() && getPort() != getDefaultPort()){ret += ":" + port;} ret += "/"; - if (protocol == "rtsp"){ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]#?&");} - }else if (isLocalPath()){ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]+ ");} - }else{ - if (path.size()){ret += Encodings::URL::encode(path, "/:=@[]");} - } + ret += getEncodedPath(); return ret; } @@ -288,11 +296,7 @@ std::string HTTP::URL::getBase() const{ tmpUrl = getBareUrl(); } size_t slashPos = tmpUrl.rfind('/'); - if (slashPos == std::string::npos){ - tmpUrl += "/"; - }else{ - tmpUrl.erase(slashPos + 1); - } + tmpUrl.erase(slashPos + 1); return tmpUrl; } diff --git a/lib/url.h b/lib/url.h index 934f78da..8e2c9b42 100644 --- a/lib/url.h +++ b/lib/url.h @@ -18,6 +18,7 @@ namespace HTTP{ std::string getExt() const; std::string getUrl() const; std::string getFilePath() const; + std::string getEncodedPath() const; std::string getBase() const; std::string getBareUrl() const; std::string getProxyUrl() const; diff --git a/test/meson.build b/test/meson.build index 93faf467..18b465b4 100644 --- a/test/meson.build +++ b/test/meson.build @@ -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':'reverse‏reverse/', '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']) diff --git a/test/url.cpp b/test/url.cpp index 3cb625d3..31329ed6 100644 --- a/test/url.cpp +++ b/test/url.cpp @@ -1,7 +1,34 @@ -#include "../lib/http_parser.cpp" -#include +#include +#include +#include #include +/// 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; }