1 /** 2 * Copyright © DiamondMVC 2019 3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module diamond.io.file; 7 8 import std.file; 9 import std..string : toLower, strip, stripLeft, stripRight; 10 import std.algorithm : startsWith; 11 12 private 13 { 14 alias dwrite = std.file.write; 15 alias dappend = std.file.append; 16 alias dread = std.file.read; 17 alias dreadText = std.file.readText; 18 alias dexists = std.file.exists; 19 alias dremove = std.file.remove; 20 } 21 22 import diamond.errors; 23 24 /// Enumeration of file access security. 25 enum FileAccessSecurity 26 { 27 /// Allows system path access. 28 systemAccess, 29 /// Allows web-root path access only. 30 webRootAccess, 31 /// Allows static file route path access only. 32 staticFileAccess, 33 /// Allows white-listed path access only. 34 whiteListAccess 35 } 36 37 /// Collection of white-listed paths. 38 private __gshared string[] _whiteList; 39 40 /** 41 * Adds a path to the white-list. 42 * Params: 43 * path = The path to add. 44 */ 45 void addPathToWhiteList(string path) 46 { 47 _whiteList ~= path; 48 } 49 50 /// The root path of the web application. 51 private string _webRootPath; 52 53 @property 54 { 55 /// Gets the root path of the web application. 56 string webRootPath() 57 { 58 if (!_webRootPath) 59 { 60 import std.algorithm : startsWith; 61 import std.file : thisExePath; 62 import std.path : absolutePath, dirName; 63 64 _webRootPath = absolutePath(dirName(thisExePath)); 65 } 66 67 return _webRootPath; 68 } 69 } 70 71 /** 72 * Transform a path into the correct access security path and also validates it. 73 * Params: 74 * security = The access security. 75 * path = The path to transform and validate. 76 * Returns: 77 * Returns the transformed path. 78 */ 79 private string transformPath(FileAccessSecurity security, string path) 80 { 81 static const char backSlash = cast(char)0x5c; 82 83 enforce(path, "Cannot transform an empty path."); 84 85 path = path.strip(); 86 87 if (security != FileAccessSecurity.systemAccess && security != FileAccessSecurity.whiteListAccess) 88 { 89 version (Windows) 90 { 91 if (path[0] != '/' && path[0] != backSlash) 92 { 93 path = "/" ~ path; 94 } 95 } 96 else 97 { 98 if (path[0] != '/') 99 { 100 path = "/" ~ path; 101 } 102 } 103 } 104 105 switch (security) 106 { 107 case FileAccessSecurity.webRootAccess: 108 { 109 if (!path.toLower().startsWith(webRootPath.toLower())) 110 { 111 path = webRootPath ~ path; 112 } 113 break; 114 } 115 116 case FileAccessSecurity.staticFileAccess: 117 { 118 import diamond.core.webconfig; 119 120 bool isStaticPath; 121 122 foreach (staticFileRoute; webConfig.staticFileRoutes) 123 { 124 if (path.startsWith(staticFileRoute.strip().stripLeft([backSlash, '/']))) 125 { 126 isStaticPath = true; 127 } 128 } 129 130 if (!isStaticPath) 131 { 132 throw new FileSecurityException("The path is not a static file path"); 133 } 134 135 path = webRootPath ~ path; 136 break; 137 } 138 139 case FileAccessSecurity.whiteListAccess: 140 { 141 bool isWhiteListPath; 142 143 if (_whiteList) 144 { 145 foreach (whiteListPath; _whiteList) 146 { 147 if (path.startsWith(whiteListPath.strip().stripLeft([backSlash, '/']))) 148 { 149 isWhiteListPath = true; 150 } 151 } 152 } 153 154 if (!isWhiteListPath) 155 { 156 throw new FileSecurityException("The path is not white-listed"); 157 } 158 break; 159 } 160 161 default: break; 162 } 163 164 return path; 165 } 166 167 168 /** 169 * Writes to a file securely. 170 * Params: 171 * security = The file security access. 172 * file = The file to write. 173 * content = The content of the file. 174 */ 175 void write(FileAccessSecurity security, string file, string content) 176 { 177 auto path = transformPath(security, file); 178 179 dwrite(path, content); 180 } 181 182 /** 183 * Appends to a file securely. 184 * Params: 185 * security = The file security access. 186 * file = The file to append. 187 * content = The content of the file. 188 */ 189 void append(FileAccessSecurity security, string file, string content) 190 { 191 auto path = transformPath(security, file); 192 193 dappend(path, content); 194 } 195 196 /** 197 * Reads the content of a file securely. 198 * Params: 199 * security = The file security access. 200 * file = The file to read. 201 * Returns: 202 * Returns the content of the file. 203 */ 204 string readText(FileAccessSecurity security, string file) 205 { 206 auto path = transformPath(security, file); 207 208 return dreadText(file); 209 } 210 211 /** 212 * Reads the buffer of a file securely. 213 * Params: 214 * security = The file security access. 215 * file = The file to read. 216 * Returns: 217 * Returns the buffer of the file. 218 */ 219 ubyte[] readBuffer(FileAccessSecurity security, string file) 220 { 221 auto path = transformPath(security, file); 222 223 return cast(ubyte[])dread(file); 224 } 225 226 /** 227 * Checks whether a file or directory exists securely. 228 * Params: 229 * security = The path access security. 230 * path = The path to validate for existence. 231 */ 232 bool exists(FileAccessSecurity security, string path) 233 { 234 path = transformPath(security, path); 235 236 return dexists(path); 237 } 238 239 /** 240 * Removes a file securely. 241 * Params: 242 * security = The files access security. 243 * file = The file to remove. 244 */ 245 void remove(FileAccessSecurity security, string file) 246 { 247 file = transformPath(security, file); 248 249 dremove(file); 250 } 251 252 /** 253 * Makes a directory securely. 254 * Params: 255 * security = The directory security access. 256 * path = The path of the directory to make. 257 */ 258 void makeDir(FileAccessSecurity security, string path) 259 { 260 path = transformPath(security, path); 261 262 mkdir(path); 263 } 264 265 /** 266 * Removes a directory securely. 267 * Params: 268 * security = The directory security access. 269 * path = The path of the directory to remove. 270 */ 271 void removeDir(FileAccessSecurity security, string path) 272 { 273 path = transformPath(security, path); 274 275 rmdirRecurse(path); 276 }