@@ -14,13 +14,26 @@ class Manifests
1414 * @var \Tatter\Assets\Config\Assets
1515 */
1616 protected $ config ;
17+
18+ /**
19+ * Messages for CLI.
20+ *
21+ * @var array [text, color]
22+ */
23+ protected $ messages = [];
1724
1825 public function __construct ($ config = null )
1926 {
2027 // Save the configuration
2128 $ this ->config = $ config ;
2229 }
2330
31+ // Scan all namespaces for manifest files
32+ public function getMessages (): array
33+ {
34+ return $ this ->messages ;
35+ }
36+
2437 // Scan all namespaces for manifest files
2538 public function locate (): array
2639 {
@@ -29,11 +42,28 @@ public function locate(): array
2942 $ files = $ locator ->listFiles ('Manifests ' );
3043
3144 // Filter by .json extension
32- return preg_grep ("/.+\.json$/i " , $ files );
45+ return array_unique ( preg_grep ("/.+\.json$/i " , $ files) );
3346 }
3447
3548 // Publish assets from a single manifest
3649 public function publish ($ path ): bool
50+ {
51+ // Verify the manifest
52+ $ manifest = $ this ->manifestFromFile ($ path );
53+ if ($ manifest === null )
54+ {
55+ return false ;
56+ }
57+
58+ // Verify the destination
59+ if (! $ this ->secureDestination ($ manifest ->destination ))
60+ {
61+ return false ;
62+ }
63+ }
64+
65+ // Read in and verify a manifest from a file path
66+ protected function manifestFromFile ($ path ): ?object
3767 {
3868 // Make sure the file is valid and accessible
3969 $ file = new File ($ path );
@@ -43,7 +73,7 @@ public function publish($path): bool
4373 if ($ this ->config ->silent )
4474 {
4575 log_message ('warning ' , lang ('Files.fileNotFound ' , [$ path ]));
46- return false ;
76+ return ;
4777 }
4878
4979 throw FileNotFoundException::forFileNotFound ($ path );
@@ -52,21 +82,144 @@ public function publish($path): bool
5282 // Make sure the file is JSON
5383 $ manifest = file_get_contents ($ file ->getRealPath ());
5484 $ manifest = json_decode ($ manifest );
55-
5685 if ($ manifest === NULL )
5786 {
5887 $ errornum = json_last_error ();
5988
6089 if ($ this ->config ->silent )
6190 {
62- log_message ('warning ' , 'JSON Error # ' . $ errornum );
63- log_message ('warning ' , lang ('Manifests.invalidFileFormat ' , [$ path ]));
64- return false ;
91+ $ error = 'JSON Error # ' . $ errornum . '. ' . lang ('Manifests.invalidFileFormat ' , [$ path ]);
92+ log_message ('warning ' , $ error ));
93+ $ this ->messages [] = [$ error , 'red ' ];
94+ return ;
6595 }
6696
6797 throw ManifestsException::forInvalidFileFormat ($ path );
6898 }
6999
70- //WIP
100+ // Verify necessary fields
101+ foreach (['source ' , 'destination ' , 'paths ' ] as $ field )
102+ {
103+ if (empty ($ manifest ->{$ field }))
104+ {
105+ if ($ this ->config ->silent )
106+ {
107+ $ error = lang ('Manifests.fieldMissingFromFile ' , [$ field , $ path ]);
108+ log_message ('warning ' , $ error ));
109+ $ this ->messages [] = [$ error , 'red ' ];
110+ return ;
111+ }
112+
113+ throw ManifestsException::forFieldMissingFromFile ($ field , $ path );
114+ }
115+ }
116+
117+ return $ manifest ;
118+ }
119+
120+ // Verify or create a destination folder and all folders up to it
121+ protected function secureDestination ($ path ): ?object
122+ {
123+ $ directory = rtrim ($ this ->config ->fileBase , '/ ' );
124+
125+ $ segments = explode ('/ ' , $ path );
126+ if ($ segments [0 ] != '' )
127+ {
128+ $ segments = array_unshift ($ segments , '' );
129+ }
130+
131+ // Secure each directory up to the destination
132+ foreach ($ segments as $ segment )
133+ {
134+ $ directory .= $ segment . '/ ' ;
135+
136+ if ($ this ->ensureDirectory ($ directory ))
137+ {
138+ $ this ->addIndexToDirectory ($ directory );
139+ }
140+ else
141+ {
142+ return ;
143+ }
144+ }
145+ }
146+
147+ // Make sure a directory exists and is writable
148+ protected function ensureDirectory ($ directory ): bool
149+ // Check for existence
150+ if (! file_exists($ directory))
151+ {
152+ mkdir ($ directory , 0644 , true );
153+ }
154+
155+ // Make sure its a directory
156+ if (! is_dir ($ directory ))
157+ {
158+ $ error = lang ('Manifests.cannotCreateDirectory ' , [$ directory ]);
159+ log_message ('warning ' , $ error ));
160+ $ this ->messages [] = [$ error , 'red ' ];
161+ return false ;
162+ }
163+
164+ // Make sure it is writable
165+ if (! is_writable ($ directory ))
166+ {
167+ $ error = lang ('Manifests.directoryNotWritable ' , [$ directory ]);
168+ log_message ('warning ' , $ error ));
169+ $ this ->messages [] = [$ error , 'red ' ];
170+ return false ;
171+ }
172+
173+ return true ;
71174 }
175+
176+ // Create index.html in the destination to prevent list access
177+ protected function addIndexToDirectory ($ directory ): bool
178+ {
179+ $ path = $ directory . 'index.html ' ;
180+ $ file = new File ($ path );
181+
182+ // Check for existing file
183+ if ($ file ->isFile ())
184+ {
185+ return true ;
186+ }
187+
188+ // Directory should be writable but jsut in case...
189+ if (! $ file ->isWritable )
190+ {
191+ $ error = lang ('Manifests.directoryNotWritable ' , [$ directory ]);
192+ log_message ('warning ' , $ error ));
193+ $ this ->messages [] = [$ error , 'red ' ];
194+ return false ;
195+ }
196+
197+ // Do it
198+ $ file = $ file ->openFile ('w ' );
199+ if (! $ file ->fwrite ($ this ->getIndexHtml ))
200+ {
201+ $ error = lang ('Manifests.cannotCreateIndexFile ' , [$ path ]);
202+ log_message ('warning ' , $ error ));
203+ $ this ->messages [] = [$ error , 'red ' ];
204+ return false ;
205+ }
206+
207+ return true ;
208+ }
209+
210+ // Generate content for index.html
211+ protected function getIndexHtml (): string
212+ {
213+ return '<!DOCTYPE html>
214+ <html>
215+ <head>
216+ <title>403 Forbidden</title>
217+ </head>
218+ <body>
219+
220+ <p>Directory access is forbidden.</p>
221+
222+ </body>
223+ </html>
224+ ' ;
72225}
0 commit comments