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("&", "&") 161 .replace("<", "<") 162 .replace(">", ">") 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 }