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.security.validation.file;
7 
8 /// Alias to a file validator delegate.
9 public alias FileValidator = bool delegate(ubyte[]);
10 
11 /// Collection of custom file validators.
12 private static __gshared FileValidator[string] _validators;
13 
14 /**
15 * Adds a custom file validator.
16 * Params:
17 *   extension = The file extension to validate.
18 *   handler =   The handler that validates the file.
19 */
20 void addCustomFileValidator(string extension, FileValidator handler)
21 {
22   _validators[extension] = handler;
23 }
24 
25 /**
26 * Checks whether specified file data matches an extension.
27 * Currently this supports ".jpg, .jpeg, .gif, .png, .pdf"
28 * Params:
29 *   extension = The extension of the file.
30 *   data =      The data to validate.
31 * Returns:
32 *   True if the data is valid for the extension given, false otherwise. Unhandled extensions returns true.
33 */
34 bool isValidFile(string extension, ubyte[] data)
35 {
36   import diamond.errors.checks;
37 
38   enforce(data && data.length, "No data to validate.");
39 
40   auto customValidator = _validators.get(extension, null);
41 
42   if (customValidator)
43   {
44     return customValidator(data);
45   }
46 
47   switch (extension)
48   {
49     case ".jpg":
50     case ".jpeg":
51     {
52       return data.length > 4 &&
53              data[0] == 0xff && data[1] == 0xd8 &&
54              data[$-2] == 0xff && data[$-1] == 0xd9;
55     }
56 
57     case ".gif":
58     {
59       if (data.length < 6)
60       {
61         return false;
62       }
63 
64       auto start = cast(string)data[0 .. 6];
65 
66       return start == "GIF87a" || start == "GIF89a";
67     }
68 
69     case ".png":
70     {
71       if (data.length < 8)
72       {
73         return false;
74       }
75 
76       auto png = data[1 .. 4];
77 
78       if (png != "PNG")
79       {
80         return false;
81       }
82 
83       return data[0] == 0x89 &&
84              data[4] == 0x0d && data[5] == 0x0a &&
85              data[6] == 0x1a &&
86              data[7] == 0x0a;
87     }
88 
89     case ".pdf":
90     {
91       return data.length > 5 &&
92              data[0] == 0x25 &&
93              data[1] == 0x50 && data[2] == 0x44 && data[3] == 0x46 &&
94              data[4] == 0x2d;
95     }
96 
97 
98     default: return true;
99   }
100 }