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 }