1 /// Written in the D programming language.
2 
3 module semitwistWeb.util;
4 
5 import std.array;
6 import std.conv;
7 import std.digest.md;
8 import std.digest.sha;
9 import std.file;
10 import std.range;
11 import std..string;
12 import std.traits;
13 import std.typecons;
14 
15 import vibe.vibe;
16 import vibe.utils.dictionarylist;
17 
18 import arsd.dom;
19 import mustacheLib = mustache;
20 import mysql.db;
21 import semitwist.util.all;
22 import semitwistWeb.conf;
23 
24 alias mustacheLib.MustacheEngine!string Mustache;
25 bool mustacheInited = false;
26 private Mustache _mustache;
27 @property ref Mustache mustache()
28 {
29 	if(!mustacheInited)
30 	{
31 		_mustache.ext     = "html";
32 		_mustache.path    = getExecPath()~"../res/templates/";
33 		_mustache.level   = Mustache.CacheLevel.once;
34 		_mustache.handler((tagName) => onMustacheError(tagName));
35 	}
36 	
37 	return _mustache;
38 }
39 
40 private string onMustacheError(string tagName)
41 {
42 	throw new Exception("Unknown Mustache variable name: "~tagName);
43 }
44 
45 void stLog
46 	(LogLevel level, /*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)
47 	(auto ref T args)
48 {
49 	log!(level, /*__MODULE__, __FUNCTION__,*/ __FILE__, __LINE__)( "%s", text(args)	);
50 }
51 void stLogTrace     (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.trace     /*, mod, func*/, file, line)(args); }
52 void stLogDebugV    (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.debugV    /*, mod, func*/, file, line)(args); }
53 void stLogDebug     (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.debug_    /*, mod, func*/, file, line)(args); }
54 void stLogDiagnostic(/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.diagnostic/*, mod, func*/, file, line)(args); }
55 void stLogInfo      (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.info      /*, mod, func*/, file, line)(args); }
56 void stLogWarn      (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.warn      /*, mod, func*/, file, line)(args); }
57 void stLogError     (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.error     /*, mod, func*/, file, line)(args); }
58 void stLogCritical  (/*string mod = __MODULE__, string func = __FUNCTION__,*/ string file = __FILE__, int line = __LINE__, T...)(auto ref T args) { stLog!(LogLevel.critical  /*, mod, func*/, file, line)(args); }
59 
60 string createUnsafeScoped(string typeName, string varName, string ctorParams="")
61 {
62 	if(ctorParams != "")
63 		ctorParams = ", "~ctorParams;
64 
65 	return q{
66 		enum $VAR_NAME_bufSize =
67 			__traits(classInstanceSize, $TYPE_NAME) +
68 			classInstanceAlignment!($TYPE_NAME);
69 		ubyte[$VAR_NAME_bufSize] $VAR_NAME_buf;
70 		auto $VAR_NAME = emplace!($TYPE_NAME)($VAR_NAME_buf $CTOR_PARAMS);
71 	}
72 	.replace("$VAR_NAME", varName)
73 	.replace("$CTOR_PARAMS", ctorParams)
74 	.replace("$TYPE_NAME", typeName);
75 }
76 
77 string insertDashes(string str)
78 {
79 	string ret;
80 
81 	int i;
82 	for(i = 0; i+5 < str.length; i += 5)
83 	{
84 		if(i > 0)
85 			ret ~= '-';
86 
87 		ret ~= str[i..i+5];
88 	}
89 
90 	if(i < str.length)
91 	{
92 		ret ~= '-';
93 		ret ~= str[i..$];
94 	}
95 
96 	return ret;
97 }
98 
99 bool isSSLReverseProxy(HTTPServerRequest req)
100 {
101 	// Newer versions of IIS automatically set this
102 	if(auto valPtr = "X-ARR-SSL" in req.headers)
103 	if((*valPtr).strip() != "")
104 		return true;
105 	
106 	// Nginx: Include this in the section of your nginx configuration file
107 	// that sets up a reverse proxy for this program:
108 	//   proxy_set_header X-SSL-Protocol $ssl_protocol;
109 	if(auto valPtr = "X-SSL-Protocol" in req.headers)
110 	if((*valPtr).strip() != "")
111 		return true;
112 	
113 	// Some reverse proxies can be configured to set this
114 	if(auto valPtr = "X-USED-SSL" in req.headers)
115 	if((*valPtr).strip().toLower() == "true")
116 		return true;
117 	
118 	return false;
119 }
120 
121 @property string clientIPs(HTTPServerRequest req)
122 {
123 	immutable headerName = "X-Forwarded-For";
124 	if(headerName in req.headers)
125 	{
126 		auto ips = req.headers[headerName].strip();
127 		if(ips != "")
128 			return ips;
129 	}
130 	
131 	return req.peer;
132 }
133 
134 string extToMime(string ext)
135 {
136 	switch(ext.toLower())
137 	{
138 	case ".txt":  return "text/plain";
139 	case ".htm":  return "text/html";
140 	case ".html": return "text/html";
141 	case ".xml":  return "application/xml";
142 	case ".xsl":  return "application/xml";
143 	case ".css":  return "text/css";
144 	case ".jpeg": return "image/jpeg";
145 	case ".jpg":  return "image/jpeg";
146 	case ".png":  return "image/png";
147 	case ".gif":  return "image/gif";
148 	case ".zip":  return "application/zip";
149 	case ".z":    return "application/x-compress";
150 	case ".wav":  return "audio/wav";
151 	case ".mp3":  return "audio/mpeg3";
152 	case ".avi":  return "video/avi";
153 	default:      return "application/octet-stream";
154 	}
155 }
156 
157 string commentToHTML(string str)
158 {
159 	return str
160 		.replace("&", "&amp;")
161 		.replace("<", "&lt;")
162 		.replace(">", "&gt;")
163 		.replace("\n", "<br />\n");
164 }
165 
166 Nullable!T paramAs(T)(HTTPServerRequest req, string name)
167 {
168 	T value;
169 
170 	try
171 		value = to!T(req.params[name]);
172 	catch(ConvException e)
173 		return Nullable!T();
174 	
175 	return Nullable!T(value);
176 }
177 
178 ubyte[8] genSalt()
179 {
180 	ubyte[8] ret;
181 	ret[] = randomBytes(8)[];
182 	return ret;
183 }
184 
185 ubyte[] hashPass(ubyte scheme, ubyte[8] salt, string pass)
186 {
187 	switch(scheme)
188 	{
189 	case 0x01:
190 		return scheme ~ salt ~ md5Of(cast(string)(salt[]) ~ pass);
191 
192 	case 0x02:
193 		return scheme ~ salt ~ sha1Of(cast(string)(salt[]) ~ pass);
194 
195 	default:
196 		throw new Exception("Unsupported password scheme: 0x%.2X".format(scheme));
197 	}
198 }
199 
200 /// Returns: Pass ok?
201 bool validatePass(string pass, ubyte[] saltedHash)
202 {
203 	auto scheme = saltedHash[0];
204 	if(
205 		scheme < 0x01 || scheme > 0x02 ||
206 		(scheme == 0x01 && saltedHash.length != 25) ||
207 		(scheme == 0x02 && saltedHash.length != 29)
208 	)
209 	{
210 		stLogWarn(
211 			"Validating against unsupported password scheme: 0x%.2X (length: %s) (Assuming not a match)",
212 			scheme, saltedHash.length
213 		);
214 		return false;
215 	}
216 		
217 	ubyte[8] salt = saltedHash[1..9];
218 	return saltedHash == hashPass(scheme, salt, pass);
219 }
220 
221 string toEmailString(SysTime st)
222 {
223 	auto dt = cast(DateTime) st;
224 
225 	static immutable char[3][12] monthStrings     = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
226 	static immutable char[3][ 7] dayOfWeekStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
227 
228 	auto tzOffset = st.timezone.utcOffsetAt(st.stdTime);
229 	auto tzAbsOffset = abs(tzOffset);
230 
231 	return "%s, %s %s %.4s %.2s:%.2s:%.2s %s%.2s%.2s".format(
232 		dayOfWeekStrings[dt.dayOfWeek],
233 		dt.day, monthStrings[dt.month - 1], dt.year,
234 		dt.hour, dt.minute, dt.second,
235 		tzOffset < seconds(0)? "-" : "+",
236 		tzAbsOffset.split!"hours",
237 		tzAbsOffset.split!"minutes"
238 	);
239 }
240 
241 //TVal getRequired(TVal, TCase)(DictionaryList!(TVal, TCase) dict, string key)
242 string getRequired(FormFields dict, string key)
243 {
244 	try
245 		return dict[key];
246 	catch(Exception e)
247 		throw new MissingKeyException(key);
248 }
249 
250 //TODO: This should go in SemiTwistDTools
251 string nullableToString(T)(Nullable!T value, string ifNull = "N/A")
252 {
253 	return value.isNull? ifNull : to!string(value.get());
254 }
255 
256 Enum toEnum(Enum)(string name) if(is(Enum == enum))
257 {
258 	import std.traits : fullyQualifiedName;
259 	foreach(value; __traits(allMembers, Enum))
260 	{
261 		if(value == name)
262 			return __traits(getMember, Enum, value);
263 	}
264 	
265 	throw new Exception("enum '"~ fullyQualifiedName!Enum ~"' doesn't have member: "~name);
266 }