fix(qr): wifi/vcard injection protection, remove dead code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
d3b777d8e8
commit
15f4b2c0d8
|
|
@ -18,8 +18,6 @@ class GenerateQrCode
|
|||
User $creator,
|
||||
?int $linkId = null,
|
||||
): QrCode {
|
||||
$data = $this->renderer->buildPayload($type, $payload);
|
||||
|
||||
return QrCode::create([
|
||||
'workspace_id' => $workspaceId,
|
||||
'link_id' => $linkId,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class QrCode extends Model
|
|||
protected $casts = [
|
||||
'payload' => 'array',
|
||||
'style' => 'array',
|
||||
'ulid' => 'string',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
|
|
|
|||
|
|
@ -35,14 +35,27 @@ class QrRenderer
|
|||
return match ($type) {
|
||||
'url' => $data['url'],
|
||||
'text' => $data['text'],
|
||||
'wifi' => "WIFI:T:{$data['encryption']};S:{$data['ssid']};P:{$data['password']};;",
|
||||
'wifi' => sprintf(
|
||||
'WIFI:T:%s;S:%s;P:%s;;',
|
||||
$this->escapeWifiField($data['encryption'] ?? ''),
|
||||
$this->escapeWifiField($data['ssid'] ?? ''),
|
||||
$this->escapeWifiField($data['password'] ?? ''),
|
||||
),
|
||||
'vcard' => $this->buildVcard($data),
|
||||
default => $data['url'] ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
private function escapeWifiField(string $value): string
|
||||
{
|
||||
return str_replace(['\\', ';', ',', '"'], ['\\\\', '\\;', '\\,', '\\"'], $value);
|
||||
}
|
||||
|
||||
private function buildVcard(array $d): string
|
||||
{
|
||||
return "BEGIN:VCARD\nVERSION:3.0\nFN:{$d['name']}\nEMAIL:{$d['email']}\nTEL:{$d['phone']}\nEND:VCARD";
|
||||
$name = str_replace(["\r", "\n"], '', $d['name'] ?? '');
|
||||
$email = str_replace(["\r", "\n"], '', $d['email'] ?? '');
|
||||
$phone = str_replace(["\r", "\n"], '', $d['phone'] ?? '');
|
||||
return "BEGIN:VCARD\nVERSION:3.0\nFN:{$name}\nEMAIL:{$email}\nTEL:{$phone}\nEND:VCARD";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,3 +16,29 @@ it('generates png as base64', function () {
|
|||
|
||||
expect($png)->toStartWith('data:image/png;base64,');
|
||||
});
|
||||
|
||||
it('builds url payload', function () {
|
||||
$renderer = new QrRenderer;
|
||||
expect($renderer->buildPayload('url', ['url' => 'https://example.com']))
|
||||
->toBe('https://example.com');
|
||||
});
|
||||
|
||||
it('builds wifi payload', function () {
|
||||
$renderer = new QrRenderer;
|
||||
expect($renderer->buildPayload('wifi', [
|
||||
'encryption' => 'WPA',
|
||||
'ssid' => 'My Network',
|
||||
'password' => 'pass;word',
|
||||
]))->toBe('WIFI:T:WPA;S:My Network;P:pass\\;word;;');
|
||||
});
|
||||
|
||||
it('sanitizes vcard injection', function () {
|
||||
$renderer = new QrRenderer;
|
||||
$payload = $renderer->buildPayload('vcard', [
|
||||
'name' => "Evil\nINJECTED:property",
|
||||
'email' => 'x@example.com',
|
||||
'phone' => '123',
|
||||
]);
|
||||
// Newline stripped so "INJECTED:property" cannot appear as a separate vCard property line
|
||||
expect($payload)->not->toContain("\nINJECTED");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue