fish2018/pansou · warning
转义序列不完整
Error message
转义序列不完整
What it means
decodeHexEscape parses a fixed-length run of hex digits from a JS single-quoted string's \xNN escape in the haitunsou plugin. It throws 转义序列不完整 (incomplete escape sequence) when the computed slice [start, start+length) extends past the input data, meaning the escape started but not enough bytes remain to complete it (e.g. a truncated \x at end of input).
Solutions
- Log the input around `start` to inspect the malformed escape before changing code
- Pre-validate the input string: confirm it ends with a complete escape sequence before decoding
- Treat the decode error as a decode failure for that item: fall back to the raw title or skip the item instead of failing the whole search
- If the site systematically truncates payloads, re-fetch the page or use a different endpoint
Example fix
// before
value, n, err := decodeHexEscape(data, i+2, 2)
if err != nil {
return "", err
}
// after
value, n, err := decodeHexEscape(data, i+2, 2)
if err != nil {
log.Printf("haitunsou: bad escape at %d, using raw title", i)
return string(data), nil // graceful fallback
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go: check remaining bytes before the call
if start < 0 || start+length > len(data) {
return string(data), nil // use raw, skip escape decoding
} Type guard
func hasCompleteHexEscape(data []byte, start, length int) bool {
return start >= 0 && start+length <= len(data)
} Try / catch
value, n, err := decodeHexEscape(data, i+2, 2)
if err != nil {
log.Printf("bad escape at %d: %v", i, err)
return string(data), nil // fallback to raw string
} Prevention
- Fuzz the decoder with truncated inputs
- Always validate slice bounds before decoding escapes
- Prefer per-item fallback over failing the whole search
When it happens
Trigger: Parsing a JS string where a \x escape (or \u escape feeding decodeHexEscape) appears with fewer than `length` hex-capable bytes left: data ends immediately after \x, or after \x with only one hex digit.
Common situations: Scraping haitunsou pages whose embedded JavaScript was truncated by the upstream site, cut off by a proxy/CDN, or whose encoder emitted a malformed \x escape at the very end of a string literal.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/79ab64f3ed47ea7f.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/haitunsou/haitunsou.go:254
if r >= 0xD800 && r <= 0xDBFF && next+6 <= len(encoded) && encoded[next] == '\\' && encoded[next+1] == 'u' {
low, lowNext, lowErr := decodeHexEscape(encoded, next+2, 4)
if lowErr == nil && low >= 0xDC00 && low <= 0xDFFF {
r = utf16.DecodeRune(r, rune(low))
index = lowNext - 1
}
}
decoded = utf8.AppendRune(decoded, r)
default:
decoded = append(decoded, encoded[index])
}
}
return decoded, nil
}
func decodeHexEscape(data []byte, start, length int) (uint64, int, error) {
end := start + length
if start < 0 || end > len(data) {
return 0, start, fmt.Errorf("转义序列不完整")
}
value, err := strconv.ParseUint(string(data[start:end]), 16, 16)
if err != nil {
return 0, start, fmt.Errorf("无效十六进制转义: %w", err)
}
return value, end, nil
}
func convertItem(item searchItem) (model.SearchResult, bool) {
rawTitle := cleanText(item.Title)
if rawTitle == "" {
rawTitle = cleanText(item.Name)
}
if rawTitle == "" {
return model.SearchResult{}, false
}
title, description := splitTitleAndDescription(rawTitle)
if title == "" {View on GitHub (pinned to beaa561337)