Skip to content

Commit 1d09153

Browse files
committed
Begin Manifests
1 parent 6002529 commit 1d09153

File tree

3 files changed

+170
-8
lines changed

3 files changed

+170
-8
lines changed

src/Exceptions/ManifestsException.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ public static function forInvalidFileFormat(string $path)
99
{
1010
return new static(lang('Manifests.invalidFileFormat', [$path]));
1111
}
12+
13+
public static function forFieldMissingFromFile(string $field, string $path)
14+
{
15+
return new static(lang('Manifests.fieldMissingFromFile', [$field, $path]));
16+
}
1217
}

src/Language/en/Manifests.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?php
22

33
return [
4-
'invalidFileFormat' => 'Unsupported file format: "{0}"',
4+
'invalidFileFormat' => 'Unsupported file format: "{0}"',
5+
'fieldMissingFromFile' => 'Invalid manfiest, the {0} field is missing: {1}',
6+
'cannotCreateIndexFile' => 'Unable to create the index file: {0}',
7+
'cannotCreateDirectory' => 'Unable to create the directroy: {0}',
8+
'directoryNotWritable' => 'The destination is not writable: {0}',
59
];

src/Libraries/Manifests.php

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)