Skip to content

Instantly share code, notes, and snippets.

@xentec
Created May 14, 2015 22:27
Show Gist options
  • Save xentec/552e8e1ddd68f4bdab3b to your computer and use it in GitHub Desktop.
Save xentec/552e8e1ddd68f4bdab3b to your computer and use it in GitHub Desktop.
import
std.array,
std.conv,
std.string;
version(Have_vibe_d) {
import vibe = vibe.inet.url;
}
/*
scheme://username:password@domain.tld:80/path?query#fragment
scheme:[/] [/[username[:password]@] [domain.]tld | IPv4 | IPv6 [:80]] [/[path/to/file.ext]] [?[key[=param2][[&]key2[=param2]]]] [#[fragment]]
*/
struct URL {
string scheme;
string username;
string password;
string host;
ushort port;
string path;
string query;
string fragment;
@property
{
// [username[:password]@] [domain.]tld | IPv4 | IPv6 [:8042]
// =====[userdata]=====^=================[host]===============
string authority() {
Appender!string ap;
if(username.length) {
ap.put(username);
if(password.length) {
ap.put(':');
ap.put(password);
}
ap.put('@');
}
ap.put(host);
if(port) {
ap.put(':');
ap.put(text(port));
}
return ap.data;
}
void authority(in string auth) {
long ud = auth.indexOf('@');
string userdata = auth[0 .. ud > -1 ? ud : 0];
// [username[:password]@]
// ==========^===========
if(userdata.length > 1) {
long p = userdata.indexOf(':');
if(p > -1) {
username = userdata[0 .. p];
password = userdata[p+1 .. $];
} else
username = userdata[0 .. $];
}
// [[domain.]tld | IPv4 | IPv6] [:8042]
// ============[host]============^=====
string hostdata = auth[userdata.length ? userdata.length+1 : 0 .. $];
if(hostdata.length && hostdata[0] == '@') // Retardation elimination
hostdata = hostdata[1..$];
if(hostdata.length) {
long p = hostdata.lastIndexOf(':');
long p6 = hostdata.lastIndexOf(']');
if(p > -1 && p != hostdata.length-1) {
if(p6 < p)
port = to!ushort(hostdata[p+1 .. $]);
else
p = p6+1;
}
host = hostdata[0 .. p > -1 ? p : $];
}
}
// [/[path/to/file.ext]] [?[key[=param2][&key2[=param2]][&key3...]] [#[fragment]]
// =========[path]========^============[query]================^===========
string localURI() {
Appender!string ap;
if(path.length)
ap.put(path);
if(query.length) {
ap.put('?');
ap.put(query);
}
if(fragment.length) {
ap.put('#');
ap.put(fragment);
}
return ap.data;
}
void localURI(in string left) {
if(left.length <= 1) return;
long q = -1, f = -1;
for(long p = 0; p < left.length; p++) {
if(left[p] == '?' && q == -1 && f == -1)
q = p;
if(left[p] == '#') {
f = p;
break;
}
}
long pathEnd, queryEnd = pathEnd = left.length;
if(f > -1) {
fragment = left[f+1..$];
pathEnd = queryEnd = f;
}
if(q > -1) {
query = left[q+1 .. queryEnd];
pathEnd = q;
}
path = left[0 .. pathEnd];
if(path == "/")
path = "";
}
// =====================================================
}
string[string] queryAA() {
string[string] r;
foreach(ref q; query.split("&")) {
string[] kv = q.split("=");
r[kv[0]] = kv[1];
}
return r;
}
static URL parse(in string raw_url)
in {
assert(raw_url.strip.length > 0, "Given URL is empty");
}
body {
URL url;
if(!raw_url.length) return url;
string raw = raw_url.strip();
// scheme://username:password@domain.tld:<port>/path?query#fragment
// ======^==========[authority]================^----^-----^========
int scheme;
for(int p; p < raw.length; p++) {
if(raw[p] == ':'){
scheme = p++;
break;
}
if(raw[p] == '@' || raw[p] == '/'){ // if we got here without ':' first then there is no scheme
break;
}
}
uint slashes; // remove the leading 2 (or less) slashes
for(long p = scheme; slashes < 2 && p+1 < raw.length; slashes++, p++) {
if(raw[p+1] != '/')
break;
}
if(scheme > -1) {
url.scheme = raw[0 .. scheme].toLower();
scheme += slashes;
if(scheme > 0)
scheme++;
}
// find end of athority
string a = raw[scheme .. $];
for(long p = scheme; p < raw.length; p++) {
if(raw[p] == '/' || raw[p] == '?' || raw[p] == '#'){
a = raw[scheme .. p];
break;
}
}
url.authority = a.strip();
url.localURI = raw[scheme+a.length .. $].strip();
return url;
}
string toString(byte verbose = 0) {
Appender!string ap;
switch(verbose) {
case 1:
if(scheme.length) {
ap.put("scheme: ");
ap.put(text(scheme, '\n'));
}
ap.put("authority: ");
ap.put(text(authority, '\n'));
ap.put("localURI: ");
ap.put(text(localURI, '\n'));
break;
case 2:
ap.put(text("scheme: ", scheme, '\n'));
ap.put(text("username: ", username, '\n'));
ap.put(text("password: ", password, '\n'));
ap.put(text("host: ", host, '\n'));
ap.put(text("port: ", port ? text(port) : "", '\n'));
ap.put(text("path: ", path, '\n'));
ap.put(text("query: ", query, '\n'));
ap.put(text("fragment: ", fragment, '\n'));
break;
default:
if(scheme.length) {
ap.put(scheme);
ap.put("://");
}
ap.put(authority);
ap.put(localURI);
break;
}
return ap.data;
}
version(Have_vibe_d) {
vibe.URL toVibeURL() {
vibe.URL url;
url.schema = scheme;
url.username = username;
url.password = password;
url.host = host;
url.port = port;
url.localURI = localURI;
return url;
}
}
URL OpCast(T)(T rhs) {
switch(T) {
case string:
return URL.parse(rhs);
version(Have_vibe_d) {
case vibe.URL:
URL url;
url.schema = rhs.scheme;
url.username = rhs.username;
url.password = rhs.password;
url.host = rhs.host;
url.port = rhs.port;
url.localURI = rhs.localURI;
url.query = rhs.query;
url.fragment = rhs.anchor;
return url;
}
}
}
}
unittest {
import std.stdio;
import std.datetime;
writeln("Testing ", __MODULE__);
writeln("========================================");
bool fail = false;
void test(in string raw, URL expected) {
static uint i = 1;
try {
URL t = URL.parse(raw);
if(t != expected) {
writeln(i,". \"",raw,"\": ", false);
writeln('\t'," ", t);
writeln('\t',"!=", expected);
writeln();
fail = true;
}
} catch(Throwable ex) {
writeln(i,". \"",raw,"\": false");
writeln('\t', ex.classinfo.name,": ",ex.line,": ", ex.msg,);
writeln();
fail = true;
}
i++;
}
TickDuration start, done;
start = Clock.currAppTick;
// scheme://username:password@domain.tld:80/path?query#fragment
test("foo", URL("", "", "", "foo"));
test("foo:", URL("foo"));
test("foo://", URL("foo"));
test("/usr/include/sys/unistd.h", URL("", "", "", "", 0, "/usr/include/sys/unistd.h"));
test("file:///usr/include/sys/unistd.h", URL("file", "", "", "", 0, "/usr/include/sys/unistd.h"));
test("file://webserver/var/log/httpd.log", URL("file", "", "", "webserver", 0, "/var/log/httpd.log"));
test("dev.prot://[::1]", URL("dev.prot", "", "", "[::1]"));
test("ssh://[2001:db8:85a3::8a2e:370:7334]:22", URL("ssh", "", "", "[2001:db8:85a3::8a2e:370:7334]", 22));
test("http://example", URL("http", "", "", "example"));
test("http://example.com", URL("http", "", "", "example.com"));
test("http://example.com:8042", URL("http", "", "", "example.com", 8042));
test("https://username@example.com", URL("https", "username", "", "example.com"));
test("https://username:password@example.com", URL("https", "username", "password", "example.com"));
test("ftp://@example.com", URL("ftp", "", "", "example.com", 0));
test("ftp://username:@example.com", URL("ftp", "username", "", "example.com", 0));
test("http://example.com:8042/", URL("http", "", "", "example.com", 8042));
test("http://example.com:8042?", URL("http", "", "", "example.com", 8042));
test("http://example.com:8042#", URL("http", "", "", "example.com", 8042));
test("http://example.com:8042/?#", URL("http", "", "", "example.com", 8042));
test("http://example.com:8042?some=key&Something=Else", URL("http", "", "", "example.com", 8042, "", "some=key&Something=Else"));
test("http://example.com#thing", URL("http", "", "", "example.com", 0, "", "", "thing"));
test("git+ssh://!$&'()*+,;=agent-.~:!$&'_(paranoid)*-.~@example.com:41233/secret/of/.sort.s/nope.avi?cat=internet&xf=2235_30000~222_200&sort=p#extra/path/?because=why#not",
URL("git+ssh","!$&'()*+,;=agent-.~","!$&'_(paranoid)*-.~","example.com",41233,"/secret/of/.sort.s/nope.avi","cat=internet&xf=2235_30000~222_200&sort=p","extra/path/?because=why#not"));
done = Clock.currAppTick;
if(fail)
writeln("========================================");
writefln("Took %f ms!", (done - start).to!("msecs",float));
assert(!fail, "FAILED");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment