Files
sysops bb963a796f security: Zufallspasswörter beim Erststart, kryptographisch sichere JTI-Generierung
- seedDefaultUsers: generiert kryptographisch zufällige Passwörter (crypto/rand)
  statt hartkodiertes "archivmailrockz" — Passwörter werden einmalig im Terminal
  angezeigt und können danach nicht wiederhergestellt werden
- generateJTI: verwendet crypto/rand (16 Byte, hex) statt time.UnixNano XOR deadbeef

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:19:24 +01:00

150 lines
3.4 KiB
Go

//go:build xapian
package index
/*
#cgo pkg-config: xapian-core
#cgo LDFLAGS: -lstdc++
#include "xapian_wrapper.h"
#include <stdlib.h>
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)
type xapianIndex struct {
db *C.XapianDB
}
func newXapian(dir string) (Indexer, error) {
cdir := C.CString(dir)
defer C.free(unsafe.Pointer(cdir))
var cerr *C.char
db := C.xapian_open(cdir, 1, &cerr)
if db == nil {
msg := C.GoString(cerr)
C.xapian_free_string(cerr)
return nil, fmt.Errorf("xapian open: %s", msg)
}
return &xapianIndex{db: db}, nil
}
func (x *xapianIndex) IndexSync(doc MailDocument) error {
cid := C.CString(doc.ID)
defer C.free(unsafe.Pointer(cid))
cfrom := C.CString(doc.From)
defer C.free(unsafe.Pointer(cfrom))
cto := C.CString(doc.To)
defer C.free(unsafe.Pointer(cto))
csubj := C.CString(doc.Subject)
defer C.free(unsafe.Pointer(csubj))
cbody := C.CString(doc.Body)
defer C.free(unsafe.Pointer(cbody))
hasAttach := C.int(0)
if doc.HasAttachment {
hasAttach = C.int(1)
}
var cerr *C.char
rc := C.xapian_index(x.db, cid, cfrom, cto, csubj, cbody, C.longlong(doc.Date.Unix()), hasAttach, &cerr)
if rc != 0 {
msg := C.GoString(cerr)
C.xapian_free_string(cerr)
return fmt.Errorf("xapian index: %s", msg)
}
return nil
}
func (x *xapianIndex) Delete(id string) error {
cid := C.CString(id)
defer C.free(unsafe.Pointer(cid))
var cerr *C.char
rc := C.xapian_delete(x.db, cid, &cerr)
if rc != 0 {
msg := C.GoString(cerr)
C.xapian_free_string(cerr)
return fmt.Errorf("xapian delete: %s", msg)
}
return nil
}
func (x *xapianIndex) Search(req SearchRequest) (*SearchResult, error) {
cquery := C.CString(req.Query)
defer C.free(unsafe.Pointer(cquery))
cfrom := C.CString(req.From)
defer C.free(unsafe.Pointer(cfrom))
cown := C.CString(req.OwnEmail)
defer C.free(unsafe.Pointer(cown))
cto := C.CString(req.To)
defer C.free(unsafe.Pointer(cto))
var dateFrom, dateTo C.longlong
if req.DateFrom != nil {
dateFrom = C.longlong(req.DateFrom.Unix())
}
if req.DateTo != nil {
dateTo = C.longlong(req.DateTo.Unix())
}
page := req.Page
if page < 1 {
page = 1
}
offset := C.int((page - 1) * req.PageSize)
limit := C.int(req.PageSize)
if limit <= 0 {
limit = 25
}
// Sort mode: 0=relevance, 1=date_desc (default), 2=date_asc
sortMode := C.int(1)
switch req.Sort {
case "relevance":
sortMode = C.int(0)
case "date_asc":
sortMode = C.int(2)
}
// Attachment filter: 0=all, 1=only with, -1=only without
attachFilter := C.int(0)
if req.HasAttachment != nil {
if *req.HasAttachment {
attachFilter = C.int(1)
} else {
attachFilter = C.int(-1)
}
}
var cerr *C.char
cresult := C.xapian_search(x.db, cquery, cfrom, cown, cto, dateFrom, dateTo, offset, limit, sortMode, attachFilter, &cerr)
if cresult == nil {
msg := C.GoString(cerr)
C.xapian_free_string(cerr)
return nil, fmt.Errorf("xapian search: %s", msg)
}
defer C.xapian_free_string(cresult)
jsonStr := C.GoString(cresult)
var raw struct {
Total int `json:"total"`
Hits []struct {
ID string `json:"id"`
Score float64 `json:"score"`
} `json:"hits"`
}
if err := json.Unmarshal([]byte(jsonStr), &raw); err != nil {
return nil, fmt.Errorf("xapian parse result: %w", err)
}
hits := make([]Hit, len(raw.Hits))
for i, h := range raw.Hits {
hits[i] = Hit{ID: h.ID, Score: h.Score}
}
return &SearchResult{Total: raw.Total, Hits: hits}, nil
}
func (x *xapianIndex) Close() error {
C.xapian_close(x.db)
return nil
}