Files
BaaRai e233c02314 Translate all Chinese text to English
Comments, doc comments, UI labels, log and error messages, LLM prompt
strings, markdown docs, test data and assertions. Identifiers, enum
keys, routes, placeholders and code structure are unchanged.

Notes:
- docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md
- tests that exercised CJK specific handling (GBK zip names, Chinese
  full text search) now use ASCII equivalents; go build passes and the
  test suite matches the pre translation baseline (remaining failures
  need a PostgreSQL DSN and fail identically on the original)
- frontend mention syntax now uses English kinds (finding, asset, ...)
  matching the backend, chat mentions tests pass
2026-10-08 23:14:34 +02:00

127 lines
3.2 KiB
Go

package traffic
import (
"bufio"
"bytes"
"context"
"database/sql"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// EvidenceExchange exposes complete captured bodies only during the callback.
// Readers are closed before releasing the writer lock, so host deletion cannot
// remove a blob between its index lookup and its evidence copy.
type EvidenceExchange struct {
ID, URL, Method, ContentType string
TS int64
Status int
ReqHead, RespHead string
ReqLen, RespLen int64
ReqHash, RespHash string
Request, Response io.Reader
}
func (t *Traffic) ReadEvidence(ctx context.Context, ids []string, consume func(EvidenceExchange) error) error {
if t == nil {
return errors.New("traffic recording store unavailable")
}
t.wmu.Lock()
defer t.wmu.Unlock()
for _, id := range ids {
if err := ctx.Err(); err != nil {
return err
}
if err := t.readEvidence(id, consume); err != nil {
return fmt.Errorf("traffic %s: %w", id, err)
}
}
return nil
}
func (t *Traffic) readEvidence(id string, consume func(EvidenceExchange) error) error {
e := EvidenceExchange{ID: id}
var legacy string
if err := t.db.QueryRow(`SELECT ts,url,method,status,content_type,req_len,resp_len,path FROM exchanges WHERE id=?`, id).
Scan(&e.TS, &e.URL, &e.Method, &e.Status, &e.ContentType, &e.ReqLen, &e.RespLen, &legacy); err != nil {
return err
}
var req, resp []byte
var reqBlob, respBlob sql.NullString
err := t.db.QueryRow(`SELECT req_head,req_body,req_blob,resp_head,resp_body,resp_blob FROM exchange_bodies WHERE id=?`, id).
Scan(&e.ReqHead, &req, &reqBlob, &e.RespHead, &resp, &respBlob)
if errors.Is(err, sql.ErrNoRows) && legacy != "" {
if !filepath.IsLocal(legacy) {
return errors.New("invalid legacy traffic path")
}
r, err := os.Open(filepath.Join(t.dir, legacy, "request.http"))
if err != nil {
return err
}
defer r.Close()
p, err := os.Open(filepath.Join(t.dir, legacy, "response.http"))
if err != nil {
return err
}
defer p.Close()
e.ReqHead, e.Request, err = splitLegacyEvidence(r)
if err != nil {
return err
}
e.RespHead, e.Response, err = splitLegacyEvidence(p)
if err != nil {
return err
}
return consume(e)
}
if err != nil {
return err
}
e.Request = bytes.NewReader(req)
e.Response = bytes.NewReader(resp)
if reqBlob.Valid && reqBlob.String != "" {
f, _, err := t.Blob(reqBlob.String)
if err != nil {
return err
}
defer f.Close()
e.Request = f
e.ReqHash = reqBlob.String
}
if respBlob.Valid && respBlob.String != "" {
f, _, err := t.Blob(respBlob.String)
if err != nil {
return err
}
defer f.Close()
e.Response = f
e.RespHash = respBlob.String
}
return consume(e)
}
func splitLegacyEvidence(r io.Reader) (string, io.Reader, error) {
b := bufio.NewReader(r)
var head strings.Builder
for {
line, err := b.ReadString('\n')
if line == "\n" || line == "\r\n" {
return head.String(), b, nil
}
head.WriteString(line)
if head.Len() > 1<<20 {
return "", nil, errors.New("legacy traffic header too large")
}
if errors.Is(err, io.EOF) {
return head.String(), b, nil
}
if err != nil {
return "", nil, err
}
}
}