1 module myip.public_;
2 
3 import std.datetime : Clock, dur;
4 import std.file : tempDir, exists, isFile, read, write;
5 import std.path : buildPath;
6 import std.socket : Socket, SocketException, TcpSocket, Address, InternetAddress, Internet6Address, AddressFamily, SocketOption, SocketOptionLevel;
7 import std.string : indexOf, strip;
8 
9 private immutable string cache;
10 
11 shared static this() {
12 	cache = buildPath(tempDir(), ".dub.my-ip");
13 }
14 
15 struct Service {
16 
17 	enum ipify = Service("api.ipify.org", "/");							/// https://www.ipify.org/
18 	enum plain_text_ip = Service("plain-text-ip.com", "/");				/// http://about.plain-text-ip.com/
19 	enum icanhazip = Service("icanhazip.com", "/");						/// http://icanhazip.com/
20 	enum whatismyipaddress = Service("bot.whatismyipaddress.com", "/");	/// https://whatismyipaddress.com/api
21 	enum amazonws = Service("checkip.amazonaws.com", "/");				/// http://checkip.amazonaws.com/
22 
23 	string host;
24 	string path;
25 
26 }
27 
28 @safe string publicAddressImpl(Service service, AddressFamily addressFamily) {
29 
30 	Address address = {
31 		switch(addressFamily) {
32 			case AddressFamily.INET: return cast(Address)new InternetAddress(service.host, 80);
33 			case AddressFamily.INET6: return cast(Address)new Internet6Address(service.host, 80);
34 			default: throw new SocketException("Invalid address family");
35 		}
36 	}();
37 
38 	Socket socket = new TcpSocket(addressFamily);
39 	socket.blocking = true;
40 	socket.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, dur!"seconds"(5));
41 	socket.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, dur!"seconds"(5));
42 	socket.connect(address);
43 	scope(exit) socket.close();
44 
45 	if(socket.send("GET " ~ service.path ~ " HTTP/1.1\r\nHost: " ~ service.host ~ "\r\nAccept: text/plain\r\n\r\n") != Socket.ERROR) {
46 	
47 		char[] buffer = new char[512];
48 		ptrdiff_t recv, body_;
49 
50 		if((recv = socket.receive(buffer)) != Socket.ERROR && (body_ = buffer[0..recv].indexOf("\r\n\r\n")) != -1) return buffer[body_+4..recv].idup.strip;
51 
52 	}
53 
54 	throw new SocketException("Could not send or receive data");
55 
56 }
57 
58 @trusted string publicAddress(Service service=Service.ipify, AddressFamily addressFamily=AddressFamily.INET) {
59 	if(exists(cache) && isFile(cache)) {
60 		void[] data = read(cache);
61 		if(data.length > 4) {
62 			if((cast(int[])data[0..4])[0] + 60 * 60 > Clock.currTime.toUnixTime!int) {
63 				// cached less that one hour ago
64 				return cast(string)data[4..$];
65 			}
66 		}
67 	}
68 	try {
69 		string ret = publicAddressImpl(service, addressFamily);
70 		write(cache, cast(void[])[Clock.currTime.toUnixTime!int] ~ cast(void[])ret);
71 		return ret;
72 	} catch(SocketException) {
73 		return "";
74 	}
75 }
76 
77 @safe string publicAddress(AddressFamily addressFamily) {
78 	return publicAddress(addressFamily);
79 }
80 
81 @safe string publicAddress4(Service service=Service.ipify) {
82 	return publicAddress(service, AddressFamily.INET);
83 }
84 
85 @safe string publicAddress6(Service service=Service.ipify) {
86 	return publicAddress(service, AddressFamily.INET6);
87 }