| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- <?php
- namespace App\Module\OpenAPI\Validators;
- use UCore\Validator;
- /**
- * Webhook URL验证器
- */
- class WebhookUrlValidator extends Validator
- {
- /**
- * 验证Webhook URL是否有效
- *
- * @param mixed $value URL地址
- * @param array $data 验证数据
- * @return bool 验证是否通过
- */
- public function validate(mixed $value, array $data): bool
- {
- if (empty($value)) {
- $this->addError('Webhook URL不能为空');
- return false;
- }
- // 验证URL格式
- if (!filter_var($value, FILTER_VALIDATE_URL)) {
- $this->addError('Webhook URL格式错误');
- return false;
- }
- // 解析URL
- $urlParts = parse_url($value);
- if (!$urlParts) {
- $this->addError('无法解析Webhook URL');
- return false;
- }
- // 验证协议
- if (!$this->validateScheme($urlParts)) {
- return false;
- }
- // 验证主机
- if (!$this->validateHost($urlParts)) {
- return false;
- }
- // 验证端口
- if (!$this->validatePort($urlParts)) {
- return false;
- }
- // 验证路径
- if (!$this->validatePath($urlParts)) {
- return false;
- }
- return true;
- }
- /**
- * 验证URL协议
- *
- * @param array $urlParts
- * @return bool
- */
- protected function validateScheme(array $urlParts): bool
- {
- $scheme = $urlParts['scheme'] ?? '';
-
- if (!in_array($scheme, ['http', 'https'])) {
- $this->addError('Webhook URL必须使用HTTP或HTTPS协议');
- return false;
- }
- // 生产环境建议使用HTTPS
- if ($scheme === 'http' && app()->environment('production')) {
- $this->addWarning('生产环境建议使用HTTPS协议以确保安全');
- }
- return true;
- }
- /**
- * 验证主机名
- *
- * @param array $urlParts
- * @return bool
- */
- protected function validateHost(array $urlParts): bool
- {
- $host = $urlParts['host'] ?? '';
-
- if (empty($host)) {
- $this->addError('Webhook URL缺少主机名');
- return false;
- }
- // 验证主机名格式
- if (!$this->isValidHostname($host)) {
- $this->addError('Webhook URL主机名格式错误');
- return false;
- }
- // 检查是否是内网地址
- if ($this->isPrivateHost($host)) {
- $this->addWarning('Webhook URL指向内网地址,请确认是否正确');
- }
- // 检查是否是本地地址
- if ($this->isLocalHost($host)) {
- $this->addWarning('Webhook URL指向本地地址,可能无法正常工作');
- }
- return true;
- }
- /**
- * 验证端口
- *
- * @param array $urlParts
- * @return bool
- */
- protected function validatePort(array $urlParts): bool
- {
- $port = $urlParts['port'] ?? null;
-
- if ($port !== null) {
- if ($port < 1 || $port > 65535) {
- $this->addError('Webhook URL端口号必须在1-65535范围内');
- return false;
- }
- // 检查常见的不安全端口
- $unsafePorts = [22, 23, 25, 53, 110, 143, 993, 995];
- if (in_array($port, $unsafePorts)) {
- $this->addError('Webhook URL不能使用系统保留端口');
- return false;
- }
- }
- return true;
- }
- /**
- * 验证路径
- *
- * @param array $urlParts
- * @return bool
- */
- protected function validatePath(array $urlParts): bool
- {
- $path = $urlParts['path'] ?? '/';
-
- // 验证路径长度
- if (strlen($path) > 2000) {
- $this->addError('Webhook URL路径过长');
- return false;
- }
- // 验证路径字符
- if (!preg_match('/^[a-zA-Z0-9\/_\-\.~%]+$/', $path)) {
- $this->addError('Webhook URL路径包含无效字符');
- return false;
- }
- return true;
- }
- /**
- * 验证主机名格式
- *
- * @param string $host
- * @return bool
- */
- protected function isValidHostname(string $host): bool
- {
- // 检查是否是IP地址
- if (filter_var($host, FILTER_VALIDATE_IP)) {
- return true;
- }
- // 检查是否是有效的域名
- return filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false;
- }
- /**
- * 检查是否是内网主机
- *
- * @param string $host
- * @return bool
- */
- protected function isPrivateHost(string $host): bool
- {
- // 如果是IP地址,检查是否是私有IP
- if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
- return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false;
- }
- // 检查常见的内网域名
- $privateDomains = [
- 'localhost',
- '*.local',
- '*.internal',
- '*.corp',
- '*.lan'
- ];
- foreach ($privateDomains as $domain) {
- if ($domain === $host || (str_starts_with($domain, '*.') && str_ends_with($host, substr($domain, 1)))) {
- return true;
- }
- }
- return false;
- }
- /**
- * 检查是否是本地主机
- *
- * @param string $host
- * @return bool
- */
- protected function isLocalHost(string $host): bool
- {
- $localHosts = [
- 'localhost',
- '127.0.0.1',
- '::1',
- '0.0.0.0'
- ];
- return in_array($host, $localHosts);
- }
- }
|