validate([ 'target' => ['required', 'in:user,room'], 'target_id' => ['required'], 'options' => ['required', 'array'], 'options.icon' => ['nullable', 'string', 'max:20'], 'options.title' => ['nullable', 'string', 'max:50'], 'options.name' => ['nullable', 'string', 'max:100'], 'options.body' => ['nullable', 'string', 'max:500'], 'options.sub' => ['nullable', 'string', 'max:200'], 'options.gradient' => ['nullable', 'array', 'max:5'], 'options.titleColor' => ['nullable', 'string', 'max:30'], 'options.autoClose' => ['nullable', 'integer', 'min:0', 'max:30000'], 'options.buttons' => ['nullable', 'array', 'max:4'], ]); // 对可能包含用户输入的字段进行 HTML 净化(防 XSS) $opts = $validated['options']; foreach (['title', 'name', 'body', 'sub'] as $field) { if (isset($opts[$field])) { $opts[$field] = strip_tags($opts[$field], '
'); } } // 按钮 label 不允许 HTML if (! empty($opts['buttons'])) { $opts['buttons'] = array_map(function ($btn) { $btn['label'] = strip_tags($btn['label'] ?? ''); $btn['color'] = preg_replace('/[^a-z0-9#(),\s.%rgba\/]/i', '', $btn['color'] ?? '#10b981'); // action 只允许预定义值,防止注入任意 JS $btn['action'] = in_array($btn['action'] ?? '', ['close', 'add_friend', 'remove_friend', 'link']) ? $btn['action'] : 'close'; return $btn; }, $opts['buttons']); } broadcast(new BannerNotification( target: $validated['target'], targetId: $validated['target_id'], options: $opts, )); return response()->json(['status' => 'success', 'message' => '广播已发送']); } }