Fix-Runde 1: bind-hosts schreibt keine Rueckverweise mehr in die .env
apply() ersetzte eine vorhandene Zeile per preg_replace($pattern, $line, ...) - $line kommt von der Befehlszeile, und preg_replace deutet $1/\1/\\ im ERSATZ als Rueckverweis, auch ohne Klammern im Muster. Ein Hostname mit solchen Zeichen wuerde still verstuemmelt in die Datei geschrieben, die jedes Geheimnis der Installation haelt - und EnvFileEditor::isValidLine() kann das nicht fangen, das Ergebnis ist syntaktisch weiter KEY=value. apply() arbeitet jetzt zeilenweise ohne jede Regex im Ersatzpfad. Zusaetzlich: EnvFileEditor::write() wirft InvalidEnvContentException, wenn der neue Inhalt nicht parst - das war ungefangen und zeigte dem Betreiber einen Stapelabzug auf der Zugangsdatendatei statt eines Satzes wie jeder andere Fehlerpfad in diesem Befehl. Neuer Test deckt den Rueckverweis-Fall ab, ueber die bereits vorhandene (leere) SITE_HOST-Zeile - der Pfad, den preg_replace tatsaechlich traf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>feat/versandtakt
parent
5c9ee2c6d5
commit
6bb705e566
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Env\EnvFileEditor;
|
||||
use App\Services\Env\InvalidEnvContentException;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
|
|
@ -95,7 +96,16 @@ class BindHosts extends Command
|
|||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$backup = $env->write($this->apply($content, $missing));
|
||||
try {
|
||||
$backup = $env->write($this->apply($content, $missing));
|
||||
} catch (InvalidEnvContentException $e) {
|
||||
// Abgelehnt heißt hier: NICHTS geschrieben, und die Sicherung ist
|
||||
// erst gar nicht angelegt worden — der Editor prüft vor beidem.
|
||||
// Der Betreiber soll das als Satz erfahren, nicht als Stapelabzug.
|
||||
$this->error('Die Datei wurde nicht geschrieben: '.$e->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('Geschrieben. Die vorherige Fassung liegt unter '.$backup);
|
||||
$this->line('Danach: php artisan config:cache && php artisan route:cache');
|
||||
|
|
@ -124,6 +134,12 @@ class BindHosts extends Command
|
|||
/**
|
||||
* Vorhandene Zeile ersetzen, sonst anhängen.
|
||||
*
|
||||
* Zeilenweise und ohne `preg_replace`: der Wert kommt von der Befehlszeile,
|
||||
* und `preg_replace` deutet `$1`, `\1` und `\\` im ERSATZ als Rückverweise.
|
||||
* Aus `--site 'www.example.test$1'` würde still `SITE_HOST=www.example.test`
|
||||
* — und diese Datei hält jedes Geheimnis dieser Installation. Ein leise
|
||||
* verstümmelter Wert darin ist schlimmer als eine Fehlermeldung.
|
||||
*
|
||||
* Ersetzen und nicht nur anhängen, weil ein leerer Schlüssel als fehlend
|
||||
* gilt: `APP_HOST=` steht dann schon da, und ein zweites `APP_HOST=…`
|
||||
* darunter wäre eine Datei mit zwei Antworten auf dieselbe Frage.
|
||||
|
|
@ -132,15 +148,24 @@ class BindHosts extends Command
|
|||
*/
|
||||
private function apply(string $content, array $values): string
|
||||
{
|
||||
foreach ($values as $key => $value) {
|
||||
$line = $key.'='.$value;
|
||||
$pattern = '/^'.preg_quote($key, '/').'=.*$/m';
|
||||
$lines = preg_split('/\R/', rtrim($content, "\r\n"));
|
||||
|
||||
$content = preg_match($pattern, $content) === 1
|
||||
? preg_replace($pattern, $line, $content, 1)
|
||||
: rtrim($content, "\n")."\n".$line."\n";
|
||||
foreach ($values as $key => $value) {
|
||||
$replaced = false;
|
||||
|
||||
foreach ($lines as $index => $line) {
|
||||
if (str_starts_with($line, $key.'=')) {
|
||||
$lines[$index] = $key.'='.$value;
|
||||
$replaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $replaced) {
|
||||
$lines[] = $key.'='.$value;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
return implode("\n", $lines)."\n";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,3 +110,21 @@ it('sagt es, wenn schon alles steht, statt eine Sicherung anzulegen', function (
|
|||
|
||||
expect(glob($this->envPath.'.bak-*'))->toBeEmpty();
|
||||
});
|
||||
|
||||
it('schreibt einen Wert wörtlich, auch wenn er wie ein Rückverweis aussieht', function () {
|
||||
// SITE_HOST steht schon leer da — genau das, was .env.example wirklich
|
||||
// ausliefert, und der Pfad, auf dem apply() eine vorhandene Zeile per
|
||||
// preg_replace ersetzt statt sie nur anzuhängen.
|
||||
file_put_contents($this->envPath, "APP_URL=https://app.example.test\nSITE_HOST=\n");
|
||||
|
||||
// `$1` und `\1` sind das, was preg_replace im ERSATZ als Rückverweis
|
||||
// deutet und still verschluckt. Diese Datei hält jedes Geheimnis der
|
||||
// Installation — was hier ankommt, muss ankommen wie eingegeben.
|
||||
$this->artisan('clupilot:bind-hosts', [
|
||||
'--site' => 'www.example.test$1,foo\1bar',
|
||||
'--force' => true,
|
||||
])->assertSuccessful();
|
||||
|
||||
expect(file_get_contents($this->envPath))
|
||||
->toContain('SITE_HOST=www.example.test$1,foo\1bar');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue