Skip to content

Commit 3f20fa4

Browse files
authored
Merge pull request #33 from FriendsOfREDAXO/skerbis-patch-3
better and secure file handling
2 parents 6b6c044 + 0da29fd commit 3f20fa4

File tree

2 files changed

+235
-2
lines changed

2 files changed

+235
-2
lines changed

README.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,206 @@ Plugins werden automatisch in das entsprechende Verzeichnis des zugehörigen Add
5555
* PHP >= 8.1
5656
* PHP-Erweiterungen: zip, fileinfo
5757

58+
## API Dokumentation für die `ZipInstall` Klasse
59+
60+
Diese Dokumentation beschreibt die `ZipInstall` Klasse, die zum Installieren von REDAXO Addons und Plugins aus ZIP-Archiven verwendet wird. Die Klasse bietet Funktionen zum Hochladen von ZIP-Dateien, zum Herunterladen von ZIP-Dateien von URLs (inkl. GitHub), sowie zum Extrahieren und Installieren der Addons/Plugins.
61+
62+
**Klassenname:** `ZipInstall`
63+
64+
**Namespace:** `FriendsOfRedaxo\ZipInstall`
65+
66+
### Konstruktor
67+
68+
```php
69+
public function __construct()
70+
```
71+
72+
**Beschreibung:**
73+
74+
Initialisiert die `ZipInstall` Klasse. Erstellt einen temporären Ordner im Cache-Verzeichnis des Addons, falls dieser nicht existiert.
75+
76+
**Parameter:**
77+
78+
* Keine
79+
80+
**Rückgabewert:**
81+
82+
* Keiner
83+
84+
### Methoden
85+
86+
#### `handleFileUpload()`
87+
88+
```php
89+
public function handleFileUpload(): string
90+
```
91+
92+
**Beschreibung:**
93+
94+
Verarbeitet den Upload einer ZIP-Datei, die über ein HTML-Formular hochgeladen wurde.
95+
96+
**Parameter:**
97+
98+
* Keine
99+
100+
**Rückgabewert:**
101+
102+
* `string`: Gibt einen HTML-String für eine Erfolgs- oder Fehlermeldung zurück.
103+
104+
**Funktionsweise:**
105+
106+
1. Prüft, ob eine Datei über `$_FILES['zip_file']` hochgeladen wurde.
107+
2. Überprüft den MIME-Type der hochgeladenen Datei (erlaubt sind `application/zip` und `application/octet-stream`).
108+
3. Überprüft die Dateigröße anhand der Konfigurationseinstellung `upload_max_size`.
109+
4. Verschiebt die hochgeladene Datei in den temporären Ordner mit einem eindeutigen Dateinamen.
110+
5. Ruft die Methode `installZip()` auf, um die Installation durchzuführen.
111+
112+
**Fehlermeldungen:**
113+
114+
* `zip_install_upload_failed`: Upload fehlgeschlagen.
115+
* `zip_install_mime_error`: Ungültiger Dateityp. Bitte laden Sie eine ZIP-Datei hoch.
116+
* `zip_install_size_error`: Die Dateigröße überschreitet das Limit von `%%size%%` MB.
117+
118+
#### `handleUrlInput()`
119+
120+
```php
121+
public function handleUrlInput(string $url): string
122+
```
123+
124+
**Beschreibung:**
125+
126+
Verarbeitet eine URL, die auf eine ZIP-Datei oder ein GitHub-Repository verweist.
127+
128+
**Parameter:**
129+
130+
* `$url` (`string`): Die URL, die verarbeitet werden soll.
131+
132+
**Rückgabewert:**
133+
134+
* `string`: Gibt einen HTML-String für eine Erfolgs- oder Fehlermeldung zurück.
135+
136+
**Funktionsweise:**
137+
138+
1. Überprüft, ob die URL nicht leer ist.
139+
2. Entfernt den abschließenden Slash von der URL.
140+
3. Prüft, ob die URL ein GitHub-Repository ist und generiert die Download-URL der ZIP-Datei.
141+
4. Lädt die ZIP-Datei in den temporären Ordner mit einem eindeutigen Dateinamen herunter.
142+
5. Ruft die Methode `installZip()` auf, um die Installation durchzuführen.
143+
144+
**Fehlermeldungen:**
145+
146+
* `zip_install_invalid_url`: Ungültige URL.
147+
* `zip_install_url_file_not_loaded`: Die Datei konnte von der angegebenen URL nicht geladen werden.
148+
149+
#### `installZip()`
150+
151+
```php
152+
protected function installZip(string $tmpFile): string
153+
```
154+
155+
**Beschreibung:**
156+
157+
Extrahiert und installiert ein Addon oder Plugin aus einer temporären ZIP-Datei.
158+
159+
**Parameter:**
160+
161+
* `$tmpFile` (`string`): Der Pfad zur temporären ZIP-Datei.
162+
163+
**Rückgabewert:**
164+
165+
* `string`: Gibt einen HTML-String für eine Erfolgs- oder Fehlermeldung zurück.
166+
167+
**Funktionsweise:**
168+
169+
1. Öffnet die ZIP-Datei mit der `ZipArchive`-Klasse.
170+
2. Sucht die `package.yml` Datei im ZIP-Archiv.
171+
3. Extrahiert den Inhalt des ZIP-Archivs in einen temporären Ordner.
172+
4. Liest die `package.yml` Datei, um die Addon-/Plugin-Informationen zu erhalten.
173+
5. Kopiert die Dateien an den entsprechenden Speicherort im REDAXO-System.
174+
6. Löscht den temporären Ordner und die temporäre ZIP-Datei.
175+
176+
**Fehlermeldungen:**
177+
178+
* `zip_install_invalid_addon`: Das Addon/Plugin ist ungültig oder konnte nicht installiert werden.
179+
* `zip_install_plugin_parent_missing`: Das Parent-Addon für dieses Plugin ist nicht vorhanden.
180+
181+
#### `getGitHubRepos()`
182+
183+
```php
184+
public function getGitHubRepos(string $username): array
185+
```
186+
187+
**Beschreibung:**
188+
189+
Holt eine Liste von GitHub-Repositories für einen bestimmten Benutzer oder eine Organisation.
190+
191+
**Parameter:**
192+
193+
* `$username` (`string`): Der GitHub-Benutzername oder Name der Organisation.
194+
195+
**Rückgabewert:**
196+
197+
* `array<int, array{name: string, description: ?string, url: string, download_url: string, default_branch: string}>`: Gibt ein Array von GitHub-Repositories zurück. Jedes Repository enthält Name, Beschreibung, URL, Download-URL und den Default-Branch.
198+
199+
**Funktionsweise:**
200+
201+
1. Erstellt eine API-Anfrage an GitHub für die Repositories.
202+
2. Filtert Fork, archivierte und deaktivierte Repositories heraus.
203+
3. Formatiert die Repositories in ein einfach zu handhabendes Array.
204+
205+
#### `isValidUrl()`
206+
207+
```php
208+
protected function isValidUrl(string $url): bool
209+
```
210+
211+
**Beschreibung:**
212+
213+
Überprüft, ob eine URL gültig und erreichbar ist.
214+
215+
**Parameter:**
216+
217+
* `$url` (`string`): Die zu überprüfende URL.
218+
219+
**Rückgabewert:**
220+
221+
* `bool`: Gibt `true` zurück, wenn die URL gültig und erreichbar ist, sonst `false`.
222+
223+
**Funktionsweise:**
224+
225+
1. Führt eine `get_headers()` Anfrage durch.
226+
2. Überprüft, ob der Statuscode `200` enthalten ist.
227+
228+
#### `downloadFile()`
229+
230+
```php
231+
protected function downloadFile(string $url, string $destination): bool
232+
```
233+
234+
**Beschreibung:**
235+
236+
Lädt eine Datei von einer URL herunter und speichert sie auf dem Server.
237+
238+
**Parameter:**
239+
240+
* `$url` (`string`): Die URL der herunterzuladenden Datei.
241+
* `$destination` (`string`): Der Dateipfad zum Speichern der heruntergeladenen Datei.
242+
243+
**Rückgabewert:**
244+
245+
* `bool`: Gibt `true` zurück, wenn die Datei erfolgreich heruntergeladen und gespeichert wurde, sonst `false`.
246+
247+
**Funktionsweise:**
248+
249+
1. Verwendet `file_get_contents()` um den Inhalt der URL abzurufen.
250+
2. Speichert den Inhalt in die angegebene Datei mit `rex_file::put()`.
251+
252+
### Zusammenfassung
253+
254+
Die `ZipInstall` Klasse bietet eine umfassende Möglichkeit zur Installation von REDAXO Addons und Plugins per ZIP-Upload oder URL. Sie enthält Sicherheitsvorkehrungen (MIME-Type Überprüfung, eindeutige Dateinamen), um das Risiko von Sicherheitslücken zu minimieren und die Stabilität der Installation zu gewährleisten.
255+
256+
257+
58258
## Lizenz
59259

60260
MIT Lizenz, siehe [LICENSE.md](LICENSE.md)

lib/zip_install.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,48 @@ public function handleFileUpload(): string
5959
/** @var array{name: string, type: string, tmp_name: string, error: int, size: int} $uploadedFile */
6060
$uploadedFile = $_FILES['zip_file'];
6161

62+
// Validate file extension
63+
$fileExtension = strtolower(pathinfo($uploadedFile['name'], PATHINFO_EXTENSION));
64+
if ($fileExtension !== 'zip') {
65+
return rex_view::error(rex_i18n::msg('zip_install_extension_error'));
66+
}
67+
68+
// Check mime type (as before)
69+
$allowedMimeTypes = ['application/zip', 'application/octet-stream'];
70+
if (!in_array($uploadedFile['type'], $allowedMimeTypes)) {
71+
72+
// Check actual mime type with fileinfo extension
73+
if (function_exists('finfo_open')) {
74+
$finfo = finfo_open(FILEINFO_MIME_TYPE);
75+
$actualMimeType = finfo_file($finfo, $uploadedFile['tmp_name']);
76+
finfo_close($finfo);
77+
if (!in_array($actualMimeType, $allowedMimeTypes)) {
78+
return rex_view::error(rex_i18n::msg('zip_install_mime_error'));
79+
}
80+
}
81+
else {
82+
return rex_view::error(rex_i18n::msg('zip_install_mime_error'));
83+
}
84+
}
6285

6386
// Check filesize
6487
$maxSize = $this->addon->getConfig('upload_max_size', 20) * 1024 * 1024; // Convert MB to bytes
6588
if ($uploadedFile['size'] > $maxSize) {
6689
return rex_view::error(rex_i18n::msg('zip_install_size_error', $this->addon->getConfig('upload_max_size', 20)));
6790
}
6891

69-
$tmpFile = $this->tmpFolder . '/temp.zip';
92+
$tmpFile = $this->tmpFolder . '/' . uniqid('upload_') . '.zip'; // Generate unique filename
93+
7094
try {
95+
96+
// Verify file content before moving
97+
$zip = new ZipArchive();
98+
if ($zip->open($uploadedFile['tmp_name']) !== true) {
99+
throw new Exception(rex_i18n::msg('zip_install_invalid_zip'));
100+
}
101+
$zip->close();
102+
103+
71104
if (!move_uploaded_file($uploadedFile['tmp_name'], $tmpFile)) {
72105
throw new Exception(rex_i18n::msg('zip_install_upload_failed'));
73106
}
@@ -114,7 +147,7 @@ public function handleUrlInput(string $url): string
114147
}
115148

116149
// Download file
117-
$tmpFile = $this->tmpFolder . '/download.zip';
150+
$tmpFile = $this->tmpFolder . '/' . uniqid('download_') . '.zip'; // Generate unique filename
118151
if (!$this->downloadFile($url, $tmpFile)) {
119152
return rex_view::error(rex_i18n::msg('zip_install_url_file_not_loaded'));
120153
}

0 commit comments

Comments
 (0)