xpzouying/xiaohongshu-mcp · error

failed to detect file type

Error message

failed to detect file type

What it means

DownloadImage validates downloaded bytes with h2non/filetype before saving. filetype.Match fails when it cannot recognize the magic-number signature of the response body — i.e. the body is empty, truncated, or not a known binary file format. The library throws this so corrupt/HTML error pages never get written to disk with a guessed extension.

Source

Thrown at pkg/downloader/images.go:82

	if err != nil {
		return "", errors.Wrapf(err, "failed to download image from %s", imageURL)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("download failed with status %d for URL: %s", resp.StatusCode, imageURL)
	}

	// 读取图片数据
	imageData, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", errors.Wrap(err, "failed to read image data")
	}

	// 检测图片格式
	kind, err := filetype.Match(imageData)
	if err != nil {
		return "", errors.Wrap(err, "failed to detect file type")
	}

	if !filetype.IsImage(imageData) {
		return "", errors.New("downloaded file is not a valid image")
	}

	// 生成唯一文件名
	fileName := d.generateFileName(imageURL, kind.Extension)
	filePath := filepath.Join(d.savePath, fileName)

	// 如果文件已存在,直接返回路径
	if _, err := os.Stat(filePath); err == nil {
		return filePath, nil
	}

	// 保存到文件
	if err := os.WriteFile(filePath, imageData, 0644); err != nil {
		return "", errors.Wrap(err, "failed to save image")

View on GitHub (pinned to 332d196854)

Solutions

  1. Log the first bytes / resp.Content-Type of the body to see what was actually returned; if it is HTML, the CDN blocked the request — add valid cookies/UA/Referer or re-login
  2. Re-download the image; transient truncation often resolves on retry
  3. Check the h2non/filetype version and upgrade it if the source serves a newer format (e.g. AVIF)
  4. Verify the URL with curl -I / curl -o to confirm it actually returns image bytes before blaming the downloader

Example fix

// before
kind, err := filetype.Match(imageData)
if err != nil {
	return "", errors.Wrap(err, "failed to detect file type")
}
// after
if len(imageData) == 0 {
	return "", errors.New("empty image body (possibly blocked by CDN)")
}
kind, err := filetype.Match(imageData)
if err != nil {
	return "", errors.Wrapf(err, "failed to detect file type (first bytes: %x, content-type: %s)", imageData[:min(8, len(imageData))], contentType)
}
Defensive patterns

Strategy: validation

Validate before calling

// 校验 URL 指向的内容确实是图片
resp, err := http.Head(imageURL)
if err != nil || resp.StatusCode != 200 {
	return fmt.Errorf("URL unreachable: %v", err)
}
if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "image/") {
	return fmt.Errorf("not an image content-type: %s", ct)
}

Type guard

func isLikelyImageData(b []byte) bool {
	return len(b) > 16 && (bytes.HasPrefix(b, []byte{0xFF, 0xD8}) || // jpeg
		bytes.HasPrefix(b, []byte{0x89, 'P', 'N', 'G'}) || bytes.HasPrefix(b, []byte{'R', 'I', 'F', 'F'}))
}

Try / catch

path, err := d.DownloadImage(url)
if err != nil {
	if strings.Contains(err.Error(), "failed to detect file type") {
		log.Printf("skipping %s: body is not a recognizable image (blocked or empty)", url)
		return "" // 跳过该图,不中断批量下载
	}
	return "", err
}

Prevention

When it happens

Trigger: Calling DownloadImage on a URL whose HTTP 200 body is not a real image: an empty body, an HTML anti-bot/login page served with 200, a zero-byte CDN placeholder, or a format filetype does not know (e.g. AVIF in old versions of the library).

Common situations: Xiaohongshu/CDN returns a captcha or 'verify' HTML page instead of the image; URL points at a redirect target that ends in an empty response; network truncates the transfer mid-body; remote server upgrades to a newer image format unsupported by the pinned filetype library version.

Related errors


AI-assisted analysis of xpzouying/xiaohongshu-mcp@332d196854 (2026-09-05). Data as JSON: /api/errors/fbe9b20f35a190b5. Report an issue: GitHub.