MAIL HARDENING
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
# Next Steps
|
||||
|
||||
## Issue Drafts (Ready for Gitea/GitHub)
|
||||
|
||||
- #TODO Define unified error strategy
|
||||
- #TODO Unified error strategy (Definition)
|
||||
- Aufwand: `M`
|
||||
- Labels: `quality`, `api`
|
||||
- Ziel: Einheitliches Verhalten bei Fehlern.
|
||||
@@ -10,15 +8,12 @@
|
||||
- ADR/kurze Doku: wann `null/false`, wann Exception.
|
||||
- `sql.php`, `link-meta.php`, `troy-api.php` folgen derselben Strategie.
|
||||
- Mindestens 3 Beispiele in `README.md` dokumentiert.
|
||||
|
||||
- #TODO Expand README with runnable examples
|
||||
- Aufwand: `S`
|
||||
- Labels: `docs`
|
||||
- Ziel: Schnellere Nutzung ohne Code-Lesen.
|
||||
- Akzeptanzkriterien:
|
||||
- Pro Modul mindestens 1 kurzes Copy/Paste-Beispiel.
|
||||
- Abschnitt "Konfiguration" mit `secret.php`-Feldern vorhanden.
|
||||
- Abschnitt "Known limitations" ergaenzt.
|
||||
- Festlegung:
|
||||
- Exceptions fuer interne/unerwartete Fehler (Konfiguration fehlt, DB/HTTP/JSON-Fehler, Parsing-Fehler, invalide Argumente).
|
||||
- `null` nur fuer "kein Ergebnis" als erwarteter Zustand (z. B. URL ohne OG-Metadaten).
|
||||
- `false` nur fuer boolesche Checks/Operationen mit reinem Erfolg-Flag; keine Detailfehler ueber `false`.
|
||||
- Keine Mischung pro Funktion: jede Funktion dokumentiert exakt einen Fehlerkanal in PHPDoc/README.
|
||||
- Alle gecatchten Exceptions werden mit Kontext weitergeworfen (ohne Secrets), nicht still geschluckt.
|
||||
|
||||
- #TODO Complete `secret.php.example`
|
||||
- Aufwand: `S`
|
||||
@@ -29,7 +24,7 @@
|
||||
- Jede Variable hat kurzen Kommentar.
|
||||
- Dateiformat entspricht direkt nutzbarer Vorlage.
|
||||
|
||||
### 5) Remove `@` error suppression incrementally
|
||||
- #TODO Remove `@` error suppression incrementally
|
||||
- Aufwand: `M`
|
||||
- Labels: `quality`, `safety`
|
||||
- Ziel: Fehler sichtbar und kontrolliert behandeln.
|
||||
@@ -38,9 +33,9 @@
|
||||
- Ersetzungen mit explizitem Error-Handling umgesetzt.
|
||||
- Keine neue `@`-Verwendung in geaenderten Dateien.
|
||||
|
||||
## 2) Sicherheit und Robustheit
|
||||
- #TODO Sicherheit und Robustheit
|
||||
|
||||
### 6) Harden URL fetching against SSRF
|
||||
- #TODO Harden URL fetching against SSRF
|
||||
- Aufwand: `M`
|
||||
- Labels: `security`, `network`
|
||||
- Akzeptanzkriterien:
|
||||
@@ -48,15 +43,7 @@
|
||||
- Optionales Host-Allowlist-Feature vorhanden.
|
||||
- Tests fuer geblockte und erlaubte Ziele vorhanden.
|
||||
|
||||
### 7) Prevent mail header injection
|
||||
- Aufwand: `S`
|
||||
- Labels: `security`, `mail`
|
||||
- Akzeptanzkriterien:
|
||||
- Empfaenger/Betreff/From werden validiert.
|
||||
- CRLF-Injection wird abgefangen.
|
||||
- Fehlerfall ist dokumentiert.
|
||||
|
||||
### 8) Centralize HTTP limits (timeout/redirect/size)
|
||||
- #TODO Centralize HTTP limits (timeout/redirect/size)
|
||||
- Aufwand: `S`
|
||||
- Labels: `robustness`, `network`
|
||||
- Akzeptanzkriterien:
|
||||
@@ -64,7 +51,7 @@
|
||||
- `og.php` und `link-meta.php` nutzen dieselben Limits.
|
||||
- Default-Werte sind in README dokumentiert.
|
||||
|
||||
### 9) Improve SQL error handling + logging
|
||||
- #TODO Improve SQL error handling + logging
|
||||
- Aufwand: `M`
|
||||
- Labels: `sql`, `robustness`
|
||||
- Akzeptanzkriterien:
|
||||
@@ -72,7 +59,7 @@
|
||||
- Fehler enthalten Query-Kontext ohne Secrets.
|
||||
- Verhalten entspricht der definierten Error-Strategie.
|
||||
|
||||
### 10) Replace fragile HTML allowlist sanitizer
|
||||
- #TODO Replace fragile HTML allowlist sanitizer
|
||||
- Aufwand: `M`
|
||||
- Labels: `security`, `string`
|
||||
- Akzeptanzkriterien:
|
||||
@@ -80,15 +67,15 @@
|
||||
- Erlaubte Tags sind konfigurierbar dokumentiert.
|
||||
- Regression-Tests fuer typische Eingaben vorhanden.
|
||||
|
||||
## 3) Code-Qualitaet
|
||||
- #TODO Code-Qualitaet
|
||||
|
||||
- Sammel-Issue: Naming-Konvention, SQL-Binding-Refactor, Legacy-Markierung, Markdown-Konsolidierung, klare Modulgrenzen.
|
||||
- Aufwand: `L`
|
||||
- Empfehlung: in 3-5 Unter-Issues aufteilen.
|
||||
|
||||
## 4) Tests und Tooling
|
||||
- #TODO Tests und Tooling
|
||||
|
||||
### 11) Bootstrap test/tooling baseline
|
||||
- #TODO Bootstrap test/tooling baseline
|
||||
- Aufwand: `M`
|
||||
- Labels: `testing`, `ci`
|
||||
- Akzeptanzkriterien:
|
||||
@@ -96,9 +83,7 @@
|
||||
- PHPStan/Psalm auf niedriger Stufe integriert.
|
||||
- CI fuehrt mindestens Lint + Tests bei Push aus.
|
||||
|
||||
## 5) Mittelfristige Architektur
|
||||
|
||||
### 12) Prepare Composer + namespace migration path
|
||||
- #TODO Prepare Composer + namespace migration path
|
||||
- Aufwand: `L`
|
||||
- Labels: `architecture`
|
||||
- Akzeptanzkriterien:
|
||||
|
||||
137
README.md
137
README.md
@@ -24,7 +24,7 @@ Danach je nach Bedarf einzelne Dateien einbinden oder zentral ueber `_func.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/lib/_func.php';
|
||||
include_once __DIR__ . '/_func.php';
|
||||
|
||||
echo shortener("Ein sehr langer Text", 10); // "Ein sehr..."
|
||||
echo decade(12345); // "12.345 K" (je nach PHP-Konvertierung)
|
||||
@@ -44,25 +44,144 @@ echo decade(12345); // "12.345 K" (je nach PHP-Konvertie
|
||||
|
||||
## Konfiguration
|
||||
|
||||
Einige Module erwarten ein lokales `secret.php` (siehe `secret.php.example`), z. B. fuer:
|
||||
Einige Module erwarten ein lokales `secret.php` (siehe `secret.php.example`).
|
||||
Folgende Felder werden verwendet:
|
||||
|
||||
- SQL-Zugangsdaten in `sql.php`
|
||||
- optionale Absenderadresse in `mail.php`
|
||||
- Gitea-Parameter in `troy-api.php`
|
||||
- `$_m['host']`, `$_m['user']`, `$_m['pass']`, `$_m['data']`, `$_m['pre']`, `$_m['salt']` fuer `sql.php`
|
||||
- `$_sendermail`, optional `$_smtp['srv']`, `$_smtp['user']`, `$_smtp['pw']` fuer `mail.php`
|
||||
- `$giteaUrl`, `$giteaOwner`, `$giteaRepo`, `$giteaToken` fuer `troy-api.php`
|
||||
|
||||
## Beispiel: Seitenmetadaten lesen
|
||||
Beispiel:
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/lib/link-meta.php';
|
||||
// secret.php im selben Verzeichnis wie die Bibliothek ablegen
|
||||
if (!defined('SQL_LOG')) define('SQL_LOG', 0);
|
||||
$giteaUrl = 'https://git.example.org';
|
||||
$giteaOwner = 'org';
|
||||
$giteaRepo = 'repo';
|
||||
$giteaToken = 'token';
|
||||
```
|
||||
|
||||
## Runnable Examples
|
||||
|
||||
### `string.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/string.php';
|
||||
|
||||
echo shortener('Ein sehr langer Text', 12) . PHP_EOL;
|
||||
echo onlyAlpha('Hi! #42?') . PHP_EOL;
|
||||
echo linkify('Mehr Infos: https://example.org') . PHP_EOL;
|
||||
```
|
||||
|
||||
### `numbers.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/numbers.php';
|
||||
|
||||
echo decade(15320) . PHP_EOL;
|
||||
echo onlyNumeric('EUR -12.50') . PHP_EOL;
|
||||
```
|
||||
|
||||
### `sql.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
if (!defined('SQL_LOG')) define('SQL_LOG', 0);
|
||||
include_once __DIR__ . '/sql.php';
|
||||
|
||||
$sql = new SQL();
|
||||
$row = $sql->single('SELECT 1 AS ok');
|
||||
var_export($row);
|
||||
```
|
||||
|
||||
### `mail.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/mail.php';
|
||||
|
||||
send_mail('user@example.org', 'Test', 'Hallo Welt', 'ok', 'error');
|
||||
```
|
||||
|
||||
### `link-meta.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/string.php';
|
||||
include_once __DIR__ . '/link-meta.php';
|
||||
|
||||
$info = getPageInfo('https://example.org');
|
||||
if ($info['ok']) {
|
||||
echo $info['title'];
|
||||
echo $info['title'] . PHP_EOL;
|
||||
echo $info['description'] . PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### `og.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/og.php';
|
||||
|
||||
$og = scanOG('https://example.org');
|
||||
print_r($og);
|
||||
```
|
||||
|
||||
### `troy-api.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/troy-api.php';
|
||||
|
||||
$res = sendToTroy(['msg' => 'hello']);
|
||||
var_dump($res);
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/troy-api.php';
|
||||
|
||||
try {
|
||||
$issue = sendToGitea('Test issue', 'Automatisch erstellt.');
|
||||
print_r($issue);
|
||||
} catch (Exception $e) {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
### `debug.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/debug.php';
|
||||
|
||||
debugCookie(true);
|
||||
debug(['foo' => 'bar']);
|
||||
```
|
||||
|
||||
### `markdown.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
include_once __DIR__ . '/string.php';
|
||||
include_once __DIR__ . '/markdown.php';
|
||||
|
||||
echo md("! Titel\n\n* Punkt A\n* Punkt B");
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- Kein Composer/Autoload; Includes muessen manuell gesetzt werden.
|
||||
- `sql.php` erwartet `secret.php` im Bibliotheksverzeichnis und nutzt `mysqli`.
|
||||
- Netzwerkfunktionen (`link-meta.php`, `og.php`, `troy-api.php`) nutzen `file_get_contents` und haben keine SSRF-Allowlist.
|
||||
- Mehrere Funktionen sind historisch gewachsen und nutzen teils inkonsistentes Error-Handling (`false`, `null`, Exceptions).
|
||||
- `markdown.php` und `onlySimpleHTML()` sind einfache Parser/Sanitizer, nicht vollstaendige Markdown- oder HTML-Sicherheitsloesungen.
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Die Bibliothek ist bewusst leichtgewichtig und ohne Composer-Setup gehalten.
|
||||
- Einzelne Funktionen sind historisch gewachsen; fuer geplante Verbesserungen siehe `next_steps.md`.
|
||||
- Fuer geplante Verbesserungen siehe `NEXT_STEPS.md`.
|
||||
|
||||
23
mail.php
23
mail.php
@@ -1,6 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
function mail_contains_header_injection(string $value): bool {
|
||||
return strpbrk($value, "\r\n\0") !== false;
|
||||
}
|
||||
|
||||
function mail_is_valid_email(string $value): bool {
|
||||
if (mail_contains_header_injection($value)) {
|
||||
return false;
|
||||
}
|
||||
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
function send_mail(string $an, string $betreff, string $text, string $ok = '', string $error = ''): void {
|
||||
global $absender;
|
||||
$sender = 'noreply@troy-grunt.de';
|
||||
@@ -12,6 +23,10 @@ function send_mail(string $an, string $betreff, string $text, string $ok = '', s
|
||||
$sender = $_sendermail;
|
||||
}
|
||||
}
|
||||
if (!mail_is_valid_email($an) || !mail_is_valid_email($sender) || mail_contains_header_injection($betreff)) {
|
||||
echo $error;
|
||||
return;
|
||||
}
|
||||
$header = 'From: ' . $sender . "\r\n";
|
||||
$header .= 'To: ' . $an . "\r\n";
|
||||
$header .= 'Content-Type:text/html' . "\r\n";
|
||||
@@ -36,6 +51,10 @@ function send_html_mail(string $an, string $betreff, string $text, string $ok =
|
||||
$sender = $_sendermail;
|
||||
}
|
||||
}
|
||||
if (!mail_is_valid_email($an) || !mail_is_valid_email($sender) || mail_contains_header_injection($betreff)) {
|
||||
echo $error;
|
||||
return;
|
||||
}
|
||||
$boundary = md5($an.$betreff.$text.time());
|
||||
|
||||
$header = 'From: ' . $sender . "\n";
|
||||
@@ -65,6 +84,10 @@ function send_php_mail(string $an, string $betreff, string $text, string $ok = '
|
||||
if (isset ( $_sendermail )) {
|
||||
$sender = $_sendermail;
|
||||
}
|
||||
if (!mail_is_valid_email($an) || !mail_is_valid_email($sender) || mail_contains_header_injection($betreff)) {
|
||||
echo $error;
|
||||
return;
|
||||
}
|
||||
include 'php-mailer/PHPMailer.php';
|
||||
$mail = new PHPMailer();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user