From 7e2af0e4f5cb125016c7ea444e247825adf547fc Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Sun, 2 Nov 2025 20:36:33 +0800 Subject: [PATCH] fix --- .../shared/validator/custom_validators.go | 171 +----------------- 1 file changed, 2 insertions(+), 169 deletions(-) diff --git a/internal/shared/validator/custom_validators.go b/internal/shared/validator/custom_validators.go index 7a2543f..0195d5f 100644 --- a/internal/shared/validator/custom_validators.go +++ b/internal/shared/validator/custom_validators.go @@ -1,11 +1,7 @@ package validator import ( - "bytes" - "context" "fmt" - "io" - "net/http" "net/url" "regexp" "strconv" @@ -468,12 +464,6 @@ func validateLuhn(cardNumber string) bool { } // validateAuthorizationURL 授权书URL验证器 -// 验证URL格式、可访问性和文件类型 -// 安全措施: -// 1. 仅允许http/https协议 -// 2. 设置超时时间防止阻塞 -// 3. 限制重定向次数 -// 4. 检查Content-Type和文件签名 func validateAuthorizationURL(fl validator.FieldLevel) bool { urlStr := fl.Field().String() if urlStr == "" { @@ -492,7 +482,7 @@ func validateAuthorizationURL(fl validator.FieldLevel) bool { return false } - // 检查协议(仅允许http和https) + // 检查协议 if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { return false } @@ -507,165 +497,8 @@ func validateAuthorizationURL(fl validator.FieldLevel) bool { break } } - if !hasValidExtension { - return false - } - // 验证URL可访问性和文件类型 - return validateURLAccessibility(parsedURL) -} - -// validateURLAccessibility 验证URL可访问性和文件类型 -func validateURLAccessibility(parsedURL *url.URL) bool { - // 创建带超时的context(5秒超时,避免阻塞验证流程) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // 创建HTTP客户端,设置安全参数 - client := &http.Client{ - Timeout: 5 * time.Second, - // 限制重定向次数,防止重定向攻击 - CheckRedirect: func(req *http.Request, via []*http.Request) error { - // 最多允许3次重定向 - if len(via) >= 3 { - return fmt.Errorf("重定向次数过多") - } - // 检查重定向后的URL是否仍然是http/https - if req.URL.Scheme != "http" && req.URL.Scheme != "https" { - return fmt.Errorf("不允许重定向到非HTTP协议") - } - return nil - }, - } - - // 先尝试HEAD请求(更高效,不下载文件内容) - req, err := http.NewRequestWithContext(ctx, "HEAD", parsedURL.String(), nil) - if err != nil { - return false - } - - // 设置User-Agent,避免某些服务器拒绝请求 - req.Header.Set("User-Agent", "TYAPI-Validator/1.0") - - // 发送HEAD请求 - resp, err := client.Do(req) - if err != nil { - // HEAD请求失败,尝试GET请求(某些服务器不支持HEAD) - return validateWithGETRequest(ctx, client, parsedURL) - } - defer resp.Body.Close() - - // 检查HTTP状态码 - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return false - } - - // 验证Content-Type - if !isValidContentType(resp.Header.Get("Content-Type")) { - // Content-Type无效,尝试读取文件签名验证 - return validateWithGETRequest(ctx, client, parsedURL) - } - - return true -} - -// validateWithGETRequest 使用GET请求验证文件(仅在HEAD失败时使用) -func validateWithGETRequest(ctx context.Context, client *http.Client, parsedURL *url.URL) bool { - req, err := http.NewRequestWithContext(ctx, "GET", parsedURL.String(), nil) - if err != nil { - return false - } - - req.Header.Set("User-Agent", "TYAPI-Validator/1.0") - // 只读取部分内容,不下载整个文件 - req.Header.Set("Range", "bytes=0-1023") // 只读取前1024字节 - - resp, err := client.Do(req) - if err != nil { - return false - } - defer resp.Body.Close() - - // 检查HTTP状态码(206是部分内容,200是完整内容) - if resp.StatusCode != 200 && resp.StatusCode != 206 { - return false - } - - // 验证Content-Type - contentType := resp.Header.Get("Content-Type") - if isValidContentType(contentType) { - return true - } - - // 读取文件签名(magic bytes)验证文件类型 - return validateFileSignature(resp.Body) -} - -// isValidContentType 检查Content-Type是否有效 -func isValidContentType(contentType string) bool { - contentType = strings.ToLower(strings.TrimSpace(contentType)) - if contentType == "" { - return false - } - - // 移除charset等参数,只检查主类型 - if idx := strings.Index(contentType, ";"); idx != -1 { - contentType = contentType[:idx] - } - contentType = strings.TrimSpace(contentType) - - // 允许的Content-Type列表 - validContentTypes := []string{ - "application/pdf", // PDF - "image/jpeg", // JPEG - "image/jpg", // JPG - "image/png", // PNG - "image/bmp", // BMP - "image/x-ms-bmp", // BMP (另一种MIME类型) - } - - for _, validType := range validContentTypes { - if contentType == validType { - return true - } - } - - return false -} - -// validateFileSignature 通过文件签名(magic bytes)验证文件类型 -func validateFileSignature(body io.Reader) bool { - // 读取文件前16字节(足够识别所有支持的文件类型) - buffer := make([]byte, 16) - n, err := body.Read(buffer) - if err != nil && err != io.EOF { - return false - } - if n < 4 { - return false - } - - // PDF签名: %PDF (前4字节) - if n >= 4 && bytes.Equal(buffer[0:4], []byte("%PDF")) { - return true - } - - // JPEG签名: FF D8 FF (前3字节) - if n >= 3 && buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF { - return true - } - - // PNG签名: 89 50 4E 47 0D 0A 1A 0A (前8字节) - if n >= 8 && bytes.Equal(buffer[0:8], []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}) { - return true - } - - // BMP签名: BM (前2字节: 42 4D) - if n >= 2 && bytes.Equal(buffer[0:2], []byte("BM")) { - return true - } - - return false + return hasValidExtension } // validateUniqueID 唯一标识验证器(小于等于32位字符串)